tangled
alpha
login
or
join now
oeiuwq.com
/
fx-rs
2
fork
atom
Algebraic Effects System for Rust
2
fork
atom
overview
issues
pulls
pipelines
Lifting to Has (#14)
authored by
oeiuwq.com
and committed by
GitHub
8 months ago
328388ba
6b6beff7
+227
-1
2 changed files
expand all
collapse all
unified
split
crates
fx
src
core
fx.rs
tests
fx_test.rs
+52
crates/fx/src/core/fx.rs
···
1
1
+
use crate::core::has_put::{Has, HasPut};
1
2
use crate::kernel::fx::Fx;
2
3
3
4
use super::pair::Pair;
···
86
87
|t: Outer| getter(t),
87
88
|t, s, v| Fx::immediate(setter(t, s), v),
88
89
)
90
90
+
}
91
91
+
92
92
+
pub fn lift<T>(self) -> Fx<'f, T, V>
93
93
+
where
94
94
+
T: HasPut<S> + Clone + 'f,
95
95
+
S: Clone + 'f,
96
96
+
V: Clone + 'f,
97
97
+
{
98
98
+
self.contra_map(|t: T| t.get().clone(), |t, s| t.put(s))
99
99
+
}
100
100
+
101
101
+
pub fn zip<V2>(self, other: Fx<'f, S, V2>) -> Fx<'f, S, (V, V2)>
102
102
+
where
103
103
+
S: Clone + 'f,
104
104
+
V: Clone + 'f,
105
105
+
V2: Clone + 'f,
106
106
+
{
107
107
+
self.map_m(move |v| other.clone().map(move |v2| (v.clone(), v2)))
108
108
+
}
109
109
+
110
110
+
pub fn zip_left<V2>(self, other: Fx<'f, S, V2>) -> Fx<'f, S, V>
111
111
+
where
112
112
+
S: Clone + 'f,
113
113
+
V: Clone + 'f,
114
114
+
V2: Clone + 'f,
115
115
+
{
116
116
+
self.map_m(move |v| other.clone().map(move |_| v.clone()))
117
117
+
}
118
118
+
119
119
+
pub fn zip_right<V2>(self, other: Fx<'f, S, V2>) -> Fx<'f, S, V2>
120
120
+
where
121
121
+
S: Clone + 'f,
122
122
+
V: Clone + 'f,
123
123
+
V2: Clone + 'f,
124
124
+
{
125
125
+
self.map_m(move |_| other.clone())
126
126
+
}
127
127
+
}
128
128
+
129
129
+
impl<'f, S: Clone> Fx<'f, S, ()> {
130
130
+
pub fn has_pending<X, V, F>(f: F) -> Fx<'f, S, V>
131
131
+
where
132
132
+
S: Has<X> + Clone + 'f,
133
133
+
V: Clone,
134
134
+
F: FnOnce(X) -> Fx<'f, S, V> + Clone + 'f,
135
135
+
X: Clone,
136
136
+
{
137
137
+
Fx::pending(move |ctx: S| {
138
138
+
let x = Has::get(&ctx).clone();
139
139
+
f(x)
140
140
+
})
89
141
}
90
142
}
+175
-1
crates/fx/src/core/tests/fx_test.rs
···
1
1
-
use crate::kernel::fx::Fx;
1
1
+
use crate::{
2
2
+
core::has_put::{Has, Put},
3
3
+
kernel::fx::Fx,
4
4
+
};
2
5
use std::convert::identity;
3
6
4
7
#[test]
···
64
67
// fx2 always returns "hello"
65
68
assert_eq!(result, "hello");
66
69
}
70
70
+
71
71
+
#[test]
72
72
+
fn flat_map() {
73
73
+
// S = usize, T = String, P = (usize, &'static str)
74
74
+
let fx1: Fx<'_, usize, usize> = Fx::value(5);
75
75
+
let fx2 = fx1.flat_map(|n| Fx::value(format!("{} apples", n)));
76
76
+
// Provide a tuple as state, as required by the Pair trait
77
77
+
let result = fx2.provide((5, ())).eval();
78
78
+
assert_eq!(result, "5 apples");
79
79
+
}
80
80
+
81
81
+
#[test]
82
82
+
fn lifts_fx_to_larger_context() {
83
83
+
#[derive(Clone, Debug, PartialEq)]
84
84
+
struct A(i32);
85
85
+
#[derive(Clone, Debug, PartialEq)]
86
86
+
struct B(&'static str);
87
87
+
#[derive(Clone, Debug, PartialEq)]
88
88
+
struct S {
89
89
+
a: A,
90
90
+
b: B,
91
91
+
}
92
92
+
impl Has<A> for S {
93
93
+
fn get<'f>(&'f self) -> &'f A {
94
94
+
&self.a
95
95
+
}
96
96
+
}
97
97
+
impl Put<A> for S {
98
98
+
fn put(mut self, value: A) -> Self {
99
99
+
self.a = value;
100
100
+
self
101
101
+
}
102
102
+
}
103
103
+
impl Has<B> for S {
104
104
+
fn get<'f>(&'f self) -> &'f B {
105
105
+
&self.b
106
106
+
}
107
107
+
}
108
108
+
impl Put<B> for S {
109
109
+
fn put(mut self, value: B) -> Self {
110
110
+
self.b = value;
111
111
+
self
112
112
+
}
113
113
+
}
114
114
+
115
115
+
let fx_a: Fx<A, i32> = Fx::pending(|a: A| Fx::value(a.0 + 1));
116
116
+
let fx_b: Fx<B, i32> = Fx::pending(|b: B| Fx::value(b.0.len() as i32));
117
117
+
// This should work: lift to anything that Has<A>, Has<B> respectively.
118
118
+
let lifted_a: Fx<S, i32> = fx_a.lift();
119
119
+
let lifted_b: Fx<S, i32> = fx_b.lift();
120
120
+
let s = S {
121
121
+
a: A(41),
122
122
+
b: B("hi!"),
123
123
+
};
124
124
+
assert_eq!(lifted_a.provide(s.clone()).eval(), 42);
125
125
+
assert_eq!(lifted_b.provide(s.clone()).eval(), 3);
126
126
+
}
127
127
+
128
128
+
#[test]
129
129
+
fn zip_and_zip_left_and_zip_right() {
130
130
+
#[derive(Clone, Debug, PartialEq)]
131
131
+
struct S(i32);
132
132
+
let fx1: Fx<S, i32> = Fx::pending(|s: S| Fx::value(s.0 + 1));
133
133
+
let fx2: Fx<S, i32> = Fx::pending(|s: S| Fx::value(s.0 * 2));
134
134
+
let s = S(10);
135
135
+
// zip returns a tuple of both results
136
136
+
let zipped = fx1.clone().zip(fx2.clone());
137
137
+
assert_eq!(zipped.provide(s.clone()).eval(), (11, 20));
138
138
+
// zip_left returns the result of the first
139
139
+
let zipped_left = fx1.clone().zip_left(fx2.clone());
140
140
+
assert_eq!(zipped_left.provide(s.clone()).eval(), 11);
141
141
+
// zip_right returns the result of the second
142
142
+
let zipped_right = fx1.zip_right(fx2);
143
143
+
assert_eq!(zipped_right.provide(s).eval(), 20);
144
144
+
}
145
145
+
146
146
+
#[test]
147
147
+
fn zip_lifted_fx_on_struct_with_has_a_and_b() {
148
148
+
#[derive(Clone, Debug, PartialEq)]
149
149
+
struct A(i32);
150
150
+
#[derive(Clone, Debug, PartialEq)]
151
151
+
struct B(&'static str);
152
152
+
#[derive(Clone, Debug, PartialEq)]
153
153
+
struct S {
154
154
+
a: A,
155
155
+
b: B,
156
156
+
}
157
157
+
impl Has<A> for S {
158
158
+
fn get<'f>(&'f self) -> &'f A {
159
159
+
&self.a
160
160
+
}
161
161
+
}
162
162
+
impl Put<A> for S {
163
163
+
fn put(mut self, value: A) -> Self {
164
164
+
self.a = value;
165
165
+
self
166
166
+
}
167
167
+
}
168
168
+
impl Has<B> for S {
169
169
+
fn get<'f>(&'f self) -> &'f B {
170
170
+
&self.b
171
171
+
}
172
172
+
}
173
173
+
impl Put<B> for S {
174
174
+
fn put(mut self, value: B) -> Self {
175
175
+
self.b = value;
176
176
+
self
177
177
+
}
178
178
+
}
179
179
+
let fx_a: Fx<A, i32> = Fx::pending(|a: A| Fx::value(a.0 + 1));
180
180
+
let fx_b: Fx<B, i32> = Fx::pending(|b: B| Fx::value(b.0.len() as i32));
181
181
+
let lifted_a: Fx<S, i32> = fx_a.lift();
182
182
+
let lifted_b: Fx<S, i32> = fx_b.lift();
183
183
+
let s = S {
184
184
+
a: A(41),
185
185
+
b: B("hi!"),
186
186
+
};
187
187
+
let zipped = lifted_a.zip(lifted_b);
188
188
+
assert_eq!(zipped.provide(s).eval(), (42, 3));
189
189
+
}
190
190
+
191
191
+
#[test]
192
192
+
fn has_pending_tuple() {
193
193
+
#[derive(Clone)]
194
194
+
struct N(i32);
195
195
+
impl Has<N> for (N, ()) {
196
196
+
fn get(&self) -> &N {
197
197
+
&self.0
198
198
+
}
199
199
+
}
200
200
+
201
201
+
// Tuple context
202
202
+
let fx = Fx::has_pending(|x: N| Fx::value(x.0 + 1));
203
203
+
let result = fx.provide((N(2), ())).eval();
204
204
+
assert_eq!(result, 3);
205
205
+
}
206
206
+
207
207
+
#[test]
208
208
+
fn has_pending_struct() {
209
209
+
// Struct context
210
210
+
#[derive(Clone)]
211
211
+
struct Ctx {
212
212
+
x: i32,
213
213
+
}
214
214
+
impl Has<i32> for Ctx {
215
215
+
fn get<'f>(&'f self) -> &'f i32 {
216
216
+
&self.x
217
217
+
}
218
218
+
}
219
219
+
let fx = Fx::has_pending(|x: i32| Fx::value(x * 2));
220
220
+
let result = fx.provide(Ctx { x: 7 }).eval();
221
221
+
assert_eq!(result, 14);
222
222
+
}
223
223
+
224
224
+
#[test]
225
225
+
fn has_pending_composed() {
226
226
+
#[derive(Clone)]
227
227
+
struct Ctx {
228
228
+
x: i32,
229
229
+
y: i32,
230
230
+
}
231
231
+
impl Has<i32> for Ctx {
232
232
+
fn get<'f>(&'f self) -> &'f i32 {
233
233
+
&self.x
234
234
+
}
235
235
+
}
236
236
+
// Compose two has_pending calls
237
237
+
let fx = Fx::has_pending(|x: i32| Fx::has_pending(move |y: i32| Fx::value(x + y)));
238
238
+
let result = fx.provide(Ctx { x: 3, y: 4 }).eval();
239
239
+
assert_eq!(result, 6); // Only x is used, y is ignored (since Has<i32> is implemented only for x)
240
240
+
}