├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── reader.rs └── state.rs └── src ├── lense.rs ├── lib.rs ├── reader.rs ├── semigroup.rs ├── stat.rs ├── state.rs └── validation.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | */*.swp 13 | rusty-tags.vi 14 | examples/t1.rs 15 | tags 16 | 17 | 18 | main.rs 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustz" 3 | version = "0.1.4" 4 | authors = ["Algermissen Jan "] 5 | license = "MIT/Apache-2.0" 6 | readme = "README.md" 7 | keywords = ["functional", "reader", "state"] 8 | repository = "https://github.com/algermissen/rustz" 9 | homepage = "https://github.com/algermissen/rustz" 10 | description = """ 11 | A library for functional programming in Rust. 12 | """ 13 | categories = ["data-structures"] 14 | 15 | [dependencies] 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jan Algermissen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rustz is a library for functional programming in Rust. 2 | 3 | It provides as-pure-as-useful functional data structures to improve the 4 | functional programming experience with Rust. It implements a set of 5 | instances of the foundational functional type classes (e.g. Functor, Monad) 6 | for a large number of data structures. 7 | 8 | The implementation tries to be as close to the pure functional implementations 9 | of these data structures, but also tries to remain practical given the 10 | specifics of the Rust programming language. 11 | 12 | # Status 13 | 14 | This is currently a means for myself to explore functional programming 15 | in general and also to understand what is possible within the 16 | inherent limits of Rust. If the overall approach turns out to be 17 | possible without negating Rust's design principles and performance 18 | goals the plan is definitely to move this to production grade 19 | functional library. 20 | 21 | You can track my plans and progress here. 22 | 23 | | Data Structure | Status | 24 | |------------------|--------------------------------------------------------------| 25 | | Lense | First working version | 26 | | Applicative | Early experiments | 27 | | ValidationNel | Early experiments | 28 | | State Monad | Initial attempt, working but needs refactoring on lifetimes. | 29 | | Reader Monad | Working version, needs cleanup and refactoring | 30 | | Writer Monad | Planned | 31 | | ReaderT | Planned for Option, Tokio futures, other type classes usually needed with Web service development. | 32 | | Free Monad | Tried this, but not sure if possible in Rust in a useful way. I also do not really grok Free - giving this up for now. Help welcome.. | 33 | | ... | Please open issues for anything you'd like to see here | 34 | 35 | # Like-Minded Crates 36 | 37 | - https://github.com/KitFreddura/Kinder (interesting macro based HKT approach) 38 | - https://github.com/mcoffin/rust-effect-monad (Contains trampoline impl.) 39 | - https://github.com/freebroccolo/monad.rs (Contains trampoline impl.) 40 | - https://github.com/freebroccolo/free.rs (Free monad / trampoline) 41 | - https://github.com/ludat/hado-rs (macro for do expressions / for comprehensions) 42 | - https://github.com/TeXitoi/rust-mdo (macro for do expressions / for comprehensions) 43 | - https://github.com/danslapman/rust-mdo-future (Future support for mdo) 44 | - https://github.com/ptal/partial) 45 | - https://github.com/srijs/rust-operational (Contains e.g. Kleisli) 46 | - https://github.com/freebroccolo/pipes.rs (Pipes) 47 | - https://github.com/m4rw3r/chomp (Parsing) 48 | - https://github.com/freebroccolo/tailrec.rs 49 | - https://github.com/asajeffrey/parsell (Parsing) 50 | - https://github.com/mgattozzi/curryrs 51 | 52 | 53 | # Functional Programming in Rust Discussions 54 | 55 | - http://blog.madhukaraphatak.com/functional-programming-in-rust-part-1/ 56 | - http://blog.madhukaraphatak.com/functional-programming-in-rust-part-2/ 57 | 58 | # Discussions on Impl-Trait 59 | 60 | - https://github.com/rust-lang/rust/issues/34511 61 | 62 | # Discussions on Higher Kinded Types 63 | 64 | - http://smallcultfollowing.com/babysteps/blog/2016/11/02/associated-type-constructors-part-1-basic-concepts-and-introduction/ 65 | - http://smallcultfollowing.com/babysteps/blog/2016/11/03/associated-type-constructors-part-2-family-traits/ 66 | - http://smallcultfollowing.com/babysteps/blog/2016/11/04/associated-type-constructors-part-3-what-higher-kinded-types-might-look-like/ 67 | - http://smallcultfollowing.com/babysteps/blog/2016/11/09/associated-type-constructors-part-4-unifying-atc-and-hkt/ 68 | - https://m4rw3r.github.io/rust-and-monad-trait 69 | - https://www.reddit.com/r/rust/comments/57267j/traits_vs_higher_kinded_types/ 70 | - https://users.rust-lang.org/t/does-rust-really-need-higher-kinded-types/5531/3 71 | - http://typelevel.org/blog/2016/08/21/hkts-moving-forward.html 72 | - https://internals.rust-lang.org/t/higher-kinded-types-the-difference-between-giving-up-and-moving-forward/3908 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /examples/reader.rs: -------------------------------------------------------------------------------- 1 | extern crate rustz; 2 | 3 | use rustz::Reader; 4 | use std::vec::Vec; 5 | 6 | 7 | #[derive(Debug)] 8 | struct User { 9 | id: i32, 10 | some_val: i32, 11 | } 12 | 13 | #[derive(Debug,Clone)] 14 | struct KV { 15 | //state: Box, 16 | state: i32, 17 | } 18 | 19 | impl KV { 20 | fn get(&self, space: &str, key: i32) -> i32 { 21 | key * 2 22 | } 23 | } 24 | 25 | 26 | #[derive(Debug,Clone)] 27 | struct GraphDB { 28 | //state: Box, 29 | state: i32, 30 | } 31 | impl GraphDB { 32 | fn find_all(&self, space: &str, key: i32) -> Vec { 33 | vec![1, 2] 34 | } 35 | } 36 | 37 | struct Env { 38 | kv: KV, 39 | gdb: GraphDB, 40 | } 41 | 42 | fn get_kv<'a>() -> Reader<'a, Env, KV> { 43 | Reader::new(|env: &Env| env.kv.clone()) 44 | } 45 | fn get_gdb<'a>() -> Reader<'a, Env, GraphDB> { 46 | Reader::new(|env: &Env| env.gdb.clone()) 47 | } 48 | 49 | fn get_user<'a>(user_id: i32) -> Reader<'a, Env, User> { 50 | get_kv().map(move |kv: KV| { 51 | let v = kv.get("users", user_id); 52 | User { 53 | id: user_id, 54 | some_val: v, 55 | } 56 | }) 57 | } 58 | 59 | fn get_friends<'a>(user_id: i32) -> Reader<'a, Env, Vec> { 60 | get_gdb().map(move |gdb: GraphDB| { 61 | let _ = gdb.find_all("friends", user_id); 62 | let u1 = User { id: 1, some_val: 2 }; 63 | let u2 = User { id: 2, some_val: 4 }; 64 | vec![u1, u2] 65 | }) 66 | } 67 | 68 | fn get_friends_of_user<'a>(user_id: i32) -> Reader<'a, Env, Vec> { 69 | get_user(user_id).flat_map(|user| get_friends(user.id)) 70 | } 71 | 72 | 73 | fn main() { 74 | //let kv = KV { state: Box::new(1) }; 75 | //let gdb = GraphDB { state: Box::new(1) }; 76 | // 77 | let kv = KV { state: 1 }; 78 | let gdb = GraphDB { state: 1 }; 79 | let env = Env { kv: kv, gdb: gdb }; 80 | 81 | let user_reader = get_user(10); 82 | //let friends_reader = get_friends(10).flat_map(|user| get_friends(user.id)); 83 | let friends_reader = get_friends_of_user(10); 84 | 85 | let user = user_reader.run(&env); 86 | let friends = friends_reader.run(&env); 87 | 88 | println!("User: {:?} ", user); 89 | println!("Friends: {:?} ", friends); 90 | } 91 | -------------------------------------------------------------------------------- /examples/state.rs: -------------------------------------------------------------------------------- 1 | extern crate rustz; 2 | 3 | use rustz::State; 4 | 5 | #[derive(Debug,Clone,Copy)] 6 | struct Account { 7 | balance: i32, 8 | } 9 | 10 | 11 | fn deduct(d: i32) -> State<'static,Account, i32> { 12 | State::new(move |a: Account| (Account { balance: a.balance - d }, 1)) 13 | } 14 | fn contribute(d: i32) -> State<'static,Account, i32> { 15 | State::new(move |a: Account| (Account { balance: a.balance + d }, 1)) 16 | } 17 | 18 | fn main() { 19 | let add_10_the_subtract_5 = contribute(10).flat_map(move |_: i32| deduct(5)); 20 | let account = Account { balance: 0 }; 21 | let (account2, fee) = add_10_the_subtract_5.run(account); 22 | println!("New balance: {:?} , last transaction fee {:?}", 23 | account2.balance, 24 | fee); 25 | 26 | //let q = |x: i32| State::new(move |a: Account| (Account { balance: 111 + x }, 500)); 27 | //let s = State::new(|a: Account| (Account { balance: 1 }, 5)) 28 | // .flatMap(q) 29 | // .map(|a4| 100); 30 | } 31 | -------------------------------------------------------------------------------- /src/lense.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | pub trait Lense { 4 | type Object; 5 | type Value; 6 | 7 | fn get(&self, obj: &Self::Object) -> Self::Value; 8 | fn set(&self, obj: &Self::Object, val: &Self::Value) -> Self::Object; 9 | } 10 | 11 | pub struct L { 12 | g: G, 13 | s: S, 14 | o: PhantomData, 15 | v: PhantomData, 16 | } 17 | 18 | impl L { 19 | pub fn new(x: G, y: S) -> L { 20 | L { 21 | g: x, 22 | s: y, 23 | o: PhantomData, 24 | v: PhantomData, 25 | } 26 | } 27 | } 28 | pub fn lense(x: G, y: S) -> L { 29 | L::new(x, y) 30 | } 31 | 32 | 33 | impl Lense for L 34 | where 35 | G: Fn(&O) -> V, 36 | S: Fn(&O, &V) -> O, 37 | { 38 | type Object = O; 39 | type Value = V; 40 | 41 | fn get(&self, obj: &(Self::Object)) -> Self::Value { 42 | (self.g)(obj) 43 | } 44 | fn set(&self, obj: &Self::Object, val: &Self::Value) -> Self::Object { 45 | (self.s)(obj, val) 46 | } 47 | } 48 | 49 | pub struct Compose<'composite, OUTER, INNER, VALUE, L1, L2> 50 | where 51 | L1: 'composite + Lense, 52 | L2: 'composite + Lense, 53 | { 54 | outer: &'composite L1, 55 | inner: &'composite L2, 56 | } 57 | 58 | impl<'composite, OUTER, INNER, VALUE, L1, L2> Lense 59 | for Compose<'composite, OUTER, INNER, VALUE, L1, L2> 60 | where 61 | L1: 'composite + Lense, 62 | L2: 'composite + Lense, 63 | { 64 | type Object = OUTER; 65 | type Value = VALUE; 66 | 67 | fn get(&self, obj: &Self::Object) -> Self::Value { 68 | self.inner.get(&(self.outer.get(obj))) 69 | } 70 | fn set(&self, obj: &Self::Object, val: &Self::Value) -> Self::Object { 71 | self.outer.set( 72 | obj, 73 | &(self.inner.set(&(self.outer.get(obj)), val)), 74 | ) 75 | } 76 | } 77 | 78 | pub fn compose<'composite, OUTER, INNER, VALUE, L1, L2>( 79 | outer: &'composite L1, 80 | inner: &'composite L2, 81 | ) -> Compose<'composite, OUTER, INNER, VALUE, L1, L2> 82 | where 83 | L1: 'composite + Lense, 84 | L2: 'composite + Lense, 85 | { 86 | Compose { 87 | outer: outer, 88 | inner: inner, 89 | } 90 | } 91 | 92 | 93 | mod tests { 94 | use super::*; 95 | 96 | #[test] 97 | fn it_works() { 98 | 99 | #[derive(Debug, Clone, Copy)] 100 | struct Point { 101 | x: i32, 102 | y: i32, 103 | } 104 | #[derive(Debug, Clone, Copy)] 105 | struct Turtle { 106 | id: i32, 107 | position: Point, 108 | } 109 | 110 | 111 | let turtle_position = lense(|turtle: &Turtle| turtle.position, |turtle: &Turtle, 112 | pos: &Point| { 113 | Turtle { 114 | id: turtle.id, 115 | position: *pos, 116 | } 117 | }); 118 | 119 | let point_x = lense(|point: &Point| point.x, |point: &Point, x: &i32| { 120 | Point { x: *x, y: point.y } 121 | }); 122 | 123 | let turtle_point_x = compose(&turtle_position, &point_x); 124 | 125 | let t1 = Turtle { 126 | id: 1, 127 | position: Point { x: 0, y: 0 }, 128 | }; 129 | let t11 = Turtle { 130 | id: 1, 131 | position: Point { x: 0, y: 0 }, 132 | }; 133 | let p = turtle_position.get(&t1); 134 | let t2 = turtle_position.set(&t11, &Point { x: 9, y: 9 }); 135 | 136 | let turtle_3 = Turtle { 137 | id: 3, 138 | position: Point { x: 0, y: 0 }, 139 | }; 140 | 141 | let turtle_3_moved = turtle_point_x.set(&turtle_3, &100); 142 | 143 | println!("##### Result: {:?} ", p); 144 | println!("##### Result: {:?} ", t2); 145 | println!("##### Result: {:?} ", turtle_3_moved); 146 | //println!("##### Result: {:?} ", v2); 147 | assert!(true); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod semigroup; 2 | pub use semigroup::Semigroup; 3 | 4 | pub mod validation; 5 | pub use validation::Validation; 6 | 7 | //pub mod state; 8 | //pub use state::State; 9 | pub mod stat; 10 | pub use stat::State; 11 | 12 | pub mod reader; 13 | pub use reader::Reader; 14 | 15 | pub mod lense; 16 | pub use lense::Lense; 17 | pub use lense::L; 18 | pub use lense::Compose; 19 | 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | #[test] 25 | fn it2_works() { 26 | 27 | assert!(10 == 10); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | pub struct Reader<'reader, R, A> { 4 | run: Box A + 'reader>, 5 | state_type: PhantomData, 6 | content_type: PhantomData, 7 | } 8 | 9 | impl<'reader, R: 'reader, A: 'reader> Reader<'reader, R, A> { 10 | pub fn new(f: F) -> Reader<'reader, R, A> 11 | where 12 | F: Fn(&R) -> A + 'reader, 13 | { 14 | Reader { 15 | run: Box::new(f), 16 | state_type: PhantomData, 17 | content_type: PhantomData, 18 | } 19 | } 20 | 21 | pub fn run(&self, r: &R) -> A { 22 | (self.run)(r) 23 | } 24 | 25 | pub fn map(self, f: G) -> Reader<'reader, R, B> 26 | where 27 | G: Fn(A) -> B + 'reader, 28 | { 29 | let h = move |s: &R| { 30 | let a = (self.run)(s); 31 | f(a) 32 | }; 33 | Reader::new(h) 34 | } 35 | 36 | pub fn flat_map(self, f: G) -> Reader<'reader, R, B> 37 | where 38 | G: Fn(A) -> Reader<'reader, R, B> + 'reader, 39 | { 40 | let h = move |s: &R| { 41 | let a = (self.run)(s); 42 | (f(a).run)(s) 43 | }; 44 | Reader::new(h) 45 | } 46 | } 47 | 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | 52 | use super::Reader; 53 | 54 | #[derive(Debug)] 55 | struct Connection { 56 | x: i32, 57 | } 58 | 59 | fn get_user(id: i32) -> Reader<'static, Connection, i32> { 60 | Reader::new(move |c: &Connection| id + c.x) 61 | } 62 | fn get_other(id: i32) -> Reader<'static, Connection, i32> { 63 | Reader::new(move |c: &Connection| id + c.x) 64 | } 65 | 66 | #[test] 67 | fn it_works() { 68 | 69 | let conn = Connection { x: 9 }; 70 | 71 | //let r = get_user(10).map(|a| a + 20); 72 | let r = get_user(10).flat_map(|a| get_other(a + 1000)); 73 | 74 | let u = (r.run)(&conn); 75 | 76 | 77 | println!("##### Result: {:?} ", u); 78 | assert!(true); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/semigroup.rs: -------------------------------------------------------------------------------- 1 | 2 | pub trait Semigroup { 3 | fn mappend(&self, b: Self) -> Self; 4 | } 5 | -------------------------------------------------------------------------------- /src/stat.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | pub struct State<'state, S, A> { 4 | run: Box (S, A) + 'state>, 5 | state_type: PhantomData, 6 | content_type: PhantomData, 7 | } 8 | 9 | impl<'state, S: 'state + Clone + Copy, A: 'state> State<'state, S, A> { 10 | pub fn new(f: F) -> State<'state, S, A> 11 | where 12 | F: Fn(S) -> (S, A) + 'state, 13 | { 14 | State { 15 | run: Box::new(f), 16 | state_type: PhantomData, 17 | content_type: PhantomData, 18 | } 19 | } 20 | 21 | pub fn run(&self, s: S) -> (S, A) { 22 | (self.run)(s) 23 | } 24 | 25 | pub fn map(self, f: G) -> State<'state, S, B> 26 | where 27 | G: Fn(A) -> B + 'state, 28 | { 29 | let h = move |s: S| { 30 | let (s1, a) = (self.run)(s); 31 | (s1, f(a)) 32 | }; 33 | State::new(h) 34 | } 35 | 36 | pub fn flat_map(self, f: G) -> State<'state, S, B> 37 | where 38 | G: Fn(A) -> State<'state, S, B> + 'state, 39 | { 40 | let h = move |s: S| { 41 | let (s1, a) = (self.run)(s); 42 | (f(a).run)(s1) 43 | }; 44 | State::new(h) 45 | } 46 | 47 | pub fn get(self) -> State<'state, S, S> { 48 | let f = move |s: S| { 49 | let s2 = s.clone(); 50 | (s, s2) 51 | }; 52 | State::new(f) 53 | } 54 | 55 | pub fn gets(self, f: F) -> State<'state, S, A> 56 | where 57 | F: Fn(S) -> A + 'state, 58 | { 59 | let g = move |s: S| { 60 | let s2 = s.clone(); 61 | (s, f(s2)) 62 | }; 63 | State::new(g) 64 | } 65 | 66 | pub fn put(self, s: S) -> State<'state, S, ()> { 67 | let s2 = s.clone(); 68 | let f = move |_| (s2, ()); 69 | State::new(f) 70 | } 71 | 72 | pub fn modify(self, f: F) -> State<'state, S, ()> 73 | where 74 | F: Fn(S) -> S + 'state, 75 | { 76 | let g = move |s| (f(s), ()); 77 | State::new(g) 78 | } 79 | } 80 | 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | 85 | //use super::state::State; 86 | use super::State; 87 | 88 | #[derive(Debug, Clone, Copy)] 89 | struct Account { 90 | balance: i32, 91 | } 92 | 93 | fn deduct(d: i32) -> State<'static, Account, i32> { 94 | State::new(move |a: Account| (Account { balance: a.balance - d }, 0)) 95 | } 96 | fn contribute(d: i32) -> State<'static, Account, i32> { 97 | State::new(move |a: Account| (Account { balance: a.balance + d }, 0)) 98 | } 99 | // https://youtu.be/9uRXjxy7JDE?t=10m39s 100 | // 101 | 102 | #[test] 103 | fn it_works() { 104 | 105 | let account = Account { balance: 0 }; 106 | 107 | let x = contribute(10).flat_map(move |_: i32| deduct(5)); 108 | 109 | //let q = |x: i32| State::new(move |a: Account| (Account { balance: 111 + x }, 500)); 110 | 111 | //let s = State::new(|a: Account| (Account { balance: 1 }, 5)) 112 | // .flat_map(q) 113 | // .map(|a4| 100); 114 | let (account2, fee) = (x.run)(account); 115 | 116 | println!("____ Balance: {:?} Fee {:?}", account2.balance, fee); 117 | 118 | assert!(true); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | pub struct State<'state, S, A> { 4 | run: Box (S, A) + 'state>, 5 | state_type: PhantomData, 6 | content_type: PhantomData, 7 | } 8 | 9 | impl<'state, S: 'state + Clone + Copy, A: 'state> State<'state, S, A> { 10 | pub fn new(f: F) -> State<'state, S, A> 11 | where 12 | F: Fn(S) -> (S, A) + 'state, 13 | { 14 | State { 15 | run: Box::new(f), 16 | state_type: PhantomData, 17 | content_type: PhantomData, 18 | } 19 | } 20 | 21 | pub fn run(&self, s: S) -> (S, A) { 22 | (self.run)(s) 23 | } 24 | 25 | pub fn map(self, f: G) -> State<'state, S, B> 26 | where 27 | G: Fn(A) -> B + 'state, 28 | { 29 | let h = move |s: S| { 30 | let (s1, a) = (self.run)(s); 31 | (s1, f(a)) 32 | }; 33 | State::new(h) 34 | } 35 | 36 | pub fn flat_map(self, f: G) -> State<'state, S, B> 37 | where 38 | G: Fn(A) -> State<'state, S, B> + 'state, 39 | { 40 | let h = move |s: S| { 41 | let (s1, a) = (self.run)(s); 42 | (f(a).run)(s1) 43 | }; 44 | State::new(h) 45 | } 46 | 47 | pub fn get(self) -> State<'state, S, S> { 48 | let f = move |s: S| { 49 | let s2 = s.clone(); 50 | (s, s2) 51 | }; 52 | State::new(f) 53 | } 54 | 55 | pub fn gets(self, f: F) -> State<'state, S, A> 56 | where 57 | F: Fn(S) -> A + 'state, 58 | { 59 | let g = move |s: S| { 60 | let s2 = s.clone(); 61 | (s, f(s2)) 62 | }; 63 | State::new(g) 64 | } 65 | 66 | pub fn put(self, s: S) -> State<'state, S, ()> { 67 | let s2 = s.clone(); 68 | let f = move |_| (s2, ()); 69 | State::new(f) 70 | } 71 | 72 | pub fn modify(self, f: F) -> State<'state, S, ()> 73 | where 74 | F: Fn(S) -> S + 'state, 75 | { 76 | let g = move |s| (f(s), ()); 77 | State::new(g) 78 | } 79 | } 80 | 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | 85 | //use super::state::State; 86 | use super::State; 87 | 88 | #[derive(Debug, Clone, Copy)] 89 | struct Account { 90 | balance: i32, 91 | } 92 | 93 | fn deduct(d: i32) -> State<'static, Account, i32> { 94 | State::new(move |a: Account| (Account { balance: a.balance - d }, 0)) 95 | } 96 | fn contribute(d: i32) -> State<'static, Account, i32> { 97 | State::new(move |a: Account| (Account { balance: a.balance + d }, 0)) 98 | } 99 | // https://youtu.be/9uRXjxy7JDE?t=10m39s 100 | // 101 | 102 | #[test] 103 | fn it_works() { 104 | 105 | let account = Account { balance: 0 }; 106 | 107 | let x = contribute(10).flat_map(move |_: i32| deduct(5)); 108 | 109 | //let q = |x: i32| State::new(move |a: Account| (Account { balance: 111 + x }, 500)); 110 | 111 | //let s = State::new(|a: Account| (Account { balance: 1 }, 5)) 112 | // .flat_map(q) 113 | // .map(|a4| 100); 114 | let (account2, fee) = (x.run)(account); 115 | 116 | println!("____ Balance: {:?} Fee {:?}", account2.balance, fee); 117 | 118 | assert!(true); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/validation.rs: -------------------------------------------------------------------------------- 1 | use std::collections::LinkedList; 2 | use semigroup::Semigroup; 3 | 4 | #[derive(Clone, Debug)] 5 | pub enum Validation { 6 | Success(A), 7 | Failure(E), 8 | } 9 | 10 | pub fn success(item: A) -> Validation { 11 | Validation::Success(item) 12 | } 13 | 14 | pub fn failure(e: E) -> Validation { 15 | Validation::Failure(e) 16 | } 17 | 18 | impl Validation { 19 | // This is the Applicative ap function but somehow I do not need it - this indicates 20 | // serious misunderstanding in the code below and should be fixed at some point. 21 | //pub fn ap(&self, vf: Validation) -> Validation 22 | //where 23 | // F: FnOnce(A) -> R, 24 | //{ 25 | // match self { 26 | // &Validation::Failure(ref e) => { 27 | // match vf { 28 | // Validation::Failure(e2) => Validation::Failure(e2.mappend(e.clone())), 29 | // Validation::Success(_) => Validation::Failure(e.clone()), 30 | // } 31 | // } 32 | // &Validation::Success(ref a) => { 33 | // match vf { 34 | // Validation::Failure(ref e2) => Validation::Failure(e2.clone()), 35 | // Validation::Success(f) => Validation::Success(f(a.clone())), 36 | // } 37 | // } 38 | // } 39 | //} 40 | 41 | pub fn map(&self, f: F) -> Validation 42 | where 43 | F: FnOnce(A) -> B, 44 | { 45 | match self { 46 | &Validation::Success(ref a) => Validation::Success(f(a.clone())), 47 | &Validation::Failure(ref e) => Validation::Failure::(e.clone()), 48 | } 49 | } 50 | 51 | pub fn get_or_else(self, fallback: A) -> A { 52 | match self { 53 | Validation::Success(a) => a, 54 | Validation::Failure(_) => fallback, 55 | } 56 | } 57 | 58 | pub fn unwrap(self) -> A { 59 | match self { 60 | Validation::Success(a) => a, 61 | Validation::Failure(_) => panic!("Validation is a failure"), 62 | } 63 | } 64 | pub fn is_success(&self) -> bool { 65 | match self { 66 | &Validation::Success(_) => true, 67 | &Validation::Failure(_) => false, 68 | } 69 | } 70 | pub fn is_failure(&self) -> bool { 71 | !self.is_success() 72 | } 73 | 74 | pub fn get_err(self) -> E { 75 | match self { 76 | Validation::Failure(e) => e, 77 | Validation::Success(_) => panic!("Validation is a success"), 78 | } 79 | } 80 | } 81 | 82 | fn collect_err1(a: Validation, e: E) -> E 83 | where 84 | A: Clone, 85 | E: Clone + Semigroup, 86 | { 87 | match a { 88 | Validation::Failure(x) => x.mappend(e), 89 | Validation::Success(_) => e, 90 | } 91 | } 92 | fn collect_err2(a: Validation, b: Validation, e: E) -> E 93 | where 94 | A: Clone, 95 | B: Clone, 96 | E: Clone + Semigroup, 97 | { 98 | match b { 99 | Validation::Failure(x) => x.mappend(collect_err1(a, e)), 100 | Validation::Success(_) => collect_err1(a, e), 101 | } 102 | } 103 | fn collect_err3( 104 | a: Validation, 105 | b: Validation, 106 | c: Validation, 107 | e: E, 108 | ) -> E 109 | where 110 | A: Clone, 111 | B: Clone, 112 | C: Clone, 113 | E: Clone + Semigroup, 114 | { 115 | match c { 116 | Validation::Failure(x) => x.mappend(collect_err2(a, b, e)), 117 | Validation::Success(_) => collect_err2(a, b, e), 118 | } 119 | } 120 | 121 | fn collect_err4( 122 | a: Validation, 123 | b: Validation, 124 | c: Validation, 125 | d: Validation, 126 | e: E, 127 | ) -> E 128 | where 129 | A: Clone, 130 | B: Clone, 131 | C: Clone, 132 | D: Clone, 133 | E: Clone + Semigroup, 134 | { 135 | match d { 136 | Validation::Failure(x) => x.mappend(collect_err3(a, b, c, e)), 137 | Validation::Success(_) => collect_err3(a, b, c, e), 138 | } 139 | } 140 | 141 | // Runs a function f in the success of the Validation a or passing the failure 142 | // of a through. 143 | pub fn apply2(a: Validation, b: Validation, f: F) -> Validation 144 | where 145 | A: Clone, 146 | B: Clone, 147 | R: Clone, 148 | E: Clone + Semigroup, 149 | F: FnOnce(A, B) -> R, 150 | { 151 | 152 | match b { 153 | Validation::Failure(e) => Validation::Failure(collect_err1(a, e)), 154 | Validation::Success(b2) => { 155 | let p = |a2: A| f(a2, b2); 156 | a.map(p) 157 | } 158 | } 159 | } 160 | 161 | // Runs a function f in the success of the Validations a,b,and c or passing any 162 | // failures through, accumulating errors along the way. This means that evaluation 163 | // is not short-circuited, but that all failures are collected using the supplied 164 | // semigroup error type. 165 | pub fn apply3( 166 | a: Validation, 167 | b: Validation, 168 | c: Validation, 169 | f: F, 170 | ) -> Validation 171 | where 172 | A: Clone, 173 | B: Clone, 174 | C: Clone, 175 | R: Clone, 176 | E: Clone + Semigroup, 177 | F: FnOnce(A, B, C) -> R, 178 | { 179 | 180 | match c { 181 | Validation::Failure(e) => Validation::Failure(collect_err2(a, b, e)), 182 | Validation::Success(c2) => { 183 | let p = |a2: A, b2: B| f(a2, b2, c2); 184 | apply2(a, b, p) 185 | } 186 | } 187 | } 188 | // Runs a function f in the success of the Validations a,b,c, and d or passing any 189 | // failures through, accumulating errors along the way. This means that evaluation 190 | // is not short-circuited, but that all failures are collected using the supplied 191 | // semigroup error type. 192 | pub fn apply4( 193 | a: Validation, 194 | b: Validation, 195 | c: Validation, 196 | d: Validation, 197 | f: F, 198 | ) -> Validation 199 | where 200 | A: Clone, 201 | B: Clone, 202 | C: Clone, 203 | D: Clone, 204 | R: Clone, 205 | E: Clone + Semigroup, 206 | F: FnOnce(A, B, C, D) -> R, 207 | { 208 | 209 | match d { 210 | Validation::Failure(e) => Validation::Failure(collect_err3(a, b, c, e)), 211 | Validation::Success(d2) => { 212 | let p = |a2: A, b2: B, c2: C| f(a2, b2, c2, d2); 213 | apply3(a, b, c, p) 214 | } 215 | } 216 | } 217 | // Runs a function f in the success of the Validations a,b,c,g and e or passing any 218 | // failures through, accumulating errors along the way. This means that evaluation 219 | // is not short-circuited, but that all failures are collected using the supplied 220 | // semigroup error type. 221 | pub fn apply5( 222 | a: Validation, 223 | b: Validation, 224 | c: Validation, 225 | d: Validation, 226 | g: Validation, 227 | f: F, 228 | ) -> Validation 229 | where 230 | A: Clone, 231 | B: Clone, 232 | C: Clone, 233 | D: Clone, 234 | G: Clone, 235 | R: Clone, 236 | E: Clone + Semigroup, 237 | F: FnOnce(A, B, C, D, G) -> R, 238 | { 239 | 240 | match g { 241 | Validation::Failure(e) => Validation::Failure(collect_err4(a, b, c, d, e)), 242 | Validation::Success(g2) => { 243 | let p = |a2: A, b2: B, c2: C, d2: D| f(a2, b2, c2, d2, g2); 244 | apply4(a, b, c, d, p) 245 | } 246 | } 247 | } 248 | 249 | impl Semigroup for LinkedList { 250 | fn mappend(&self, b: LinkedList) -> LinkedList { 251 | let mut cloned_list = self.clone(); 252 | for e in b.iter() { 253 | cloned_list.push_back(e.clone()); 254 | } 255 | cloned_list 256 | } 257 | } 258 | 259 | pub type ValidationNel = Validation, A>; 260 | 261 | pub fn failure_nel(e: E) -> ValidationNel { 262 | let mut li: LinkedList = LinkedList::new(); 263 | li.push_back(e); 264 | Validation::Failure(li) 265 | } 266 | 267 | pub fn success_nel(a: A) -> ValidationNel { 268 | Validation::Success(a) 269 | } 270 | 271 | #[cfg(test)] 272 | mod tests { 273 | use validation::*; 274 | use super::success; 275 | use super::failure; 276 | 277 | // Let's use i32 as error counter error type 278 | impl Semigroup for i32 { 279 | fn mappend(&self, b: i32) -> i32 { 280 | self + b 281 | } 282 | } 283 | 284 | 285 | // A function that takes two parameters 286 | fn add2(a: i32, b: i32) -> i32 { 287 | a + b 288 | } 289 | // A function that takes three parameters 290 | fn add3(a: i32, b: i32, c: i32) -> i32 { 291 | a + b + c 292 | } 293 | 294 | // A function that works in the context of validation and returns an error if 295 | // if devision by 0 would occur. 296 | fn div(s: i32, t: i32) -> ValidationNel { 297 | match t { 298 | 0 => failure_nel::("Cannot devide by 0".to_owned()), 299 | _ => success_nel::(s / t), 300 | } 301 | } 302 | 303 | #[test] 304 | fn it_works() { 305 | let a = success::(1); 306 | let b = success::(2); 307 | let r = apply2(a, b, add2); 308 | assert!(r.unwrap() == 3); 309 | 310 | let a = success::(1); 311 | let b = success::(2); 312 | let c = success::(3); 313 | let r = apply3(a, b, c, add3); 314 | assert!(r.unwrap() == 6); 315 | 316 | let a = success::(1); 317 | let b = success::(2); 318 | let e = failure::(3); 319 | let r = apply3(a, b, e, add3); 320 | assert!(r.is_failure()); 321 | 322 | let r = div(10, 0); 323 | assert!(r.is_failure()); 324 | 325 | let r = div(10, 2); 326 | assert!(r.unwrap() == 5); 327 | 328 | let r = apply3(div(10, 0), div(10, 1), div(0, 0), add3); 329 | assert!(r.get_err().len() == 2); 330 | 331 | let r = apply3( 332 | failure::(1), 333 | failure::(1), 334 | failure::(1), 335 | add3, 336 | ); 337 | assert!(r.get_err() == 3); 338 | } 339 | 340 | } 341 | --------------------------------------------------------------------------------