Algebraic Effects System for Rust

Lifting to Has (#14)

authored by oeiuwq.com and committed by

GitHub 328388ba 6b6beff7

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