├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── applicative.rs ├── data ├── id.rs ├── list.rs ├── mod.rs ├── option.rs └── result.rs ├── functor.rs ├── lib.rs └── monad.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Install Rust 15 | run: rustup install stable 16 | 17 | - uses: actions/cache@v3 18 | with: 19 | # Refer from: https://github.com/actions/cache/blob/8bec1e4cc329270e6364af0aee38d62e50012e62/examples.md#rust---cargo 20 | path: | 21 | ~/.cargo/bin/ 22 | ~/.cargo/registry/index/ 23 | ~/.cargo/registry/cache/ 24 | ~/.cargo/git/db/ 25 | target/ 26 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 27 | 28 | - name: Build 29 | run: cargo build 30 | 31 | - name: Test 32 | run: cargo test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "rusty-monad" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty-monad" 3 | version = "0.1.0" 4 | authors = ["yytyd "] 5 | edition = "2021" 6 | rust-version = "1.65" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rusty-monad 2 | 3 | An implementation for Monad in Rust as a prototype. 4 | 5 | This repository requires Rust nightly 1.50 or the later version. 6 | 7 | This repository includes following things: 8 | 9 | - Type classes emulation: `Functor`, `Applicative`, `Monad` 10 | - Examples for data types: `Option`, `Result`, `Id`, `List` (underconstruction) 11 | - Do notation emulation by macro 12 | 13 | Type classes implementations are inspired by [this post](https://www.fpcomplete.com/blog/monads-gats-nightly-rust/). Thank you for the awesome post! Other implementations are by myself. 14 | -------------------------------------------------------------------------------- /src/applicative.rs: -------------------------------------------------------------------------------- 1 | use crate::functor::Pointed; 2 | 3 | pub(crate) trait Applicative: Pointed { 4 | fn apply(self, b: Self::Lifted, f: F) -> Self::Lifted 5 | where 6 | F: FnMut(Self::A, B) -> C; 7 | } 8 | -------------------------------------------------------------------------------- /src/data/id.rs: -------------------------------------------------------------------------------- 1 | use crate::{applicative::Applicative, functor::Functor, functor::Pointed, monad::Monad}; 2 | 3 | #[derive(Debug)] 4 | pub(crate) struct Id(pub M); 5 | 6 | impl Monad for Id { 7 | fn bind(self, mut f: F) -> Id 8 | where 9 | F: FnMut(A) -> Id, 10 | { 11 | f(self.0) 12 | } 13 | } 14 | 15 | impl Pointed for Id { 16 | fn pure(t: A) -> Id { 17 | Id(t) 18 | } 19 | } 20 | 21 | impl Applicative for Id { 22 | fn apply(self, b: Id, mut f: F) -> Id 23 | where 24 | F: FnMut(A, B) -> C, 25 | { 26 | Id(f(self.0, b.0)) 27 | } 28 | } 29 | 30 | impl Functor for Id { 31 | type A = A; 32 | type Lifted = Id; 33 | 34 | fn map(self, mut f: F) -> Id 35 | where 36 | F: FnMut(A) -> B, 37 | { 38 | Id(f(self.0)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/data/list.rs: -------------------------------------------------------------------------------- 1 | use crate::{applicative::Applicative, functor::Functor, functor::Pointed, monad::Monad}; 2 | 3 | // pub enum List { 4 | // Cons { head: T, tail: Box> }, 5 | // Nil, 6 | // } 7 | 8 | // impl Pointed for List { 9 | // fn pure(t: A) -> List { 10 | // List::Cons { 11 | // head: t, 12 | // tail: Box::new(List::Nil), 13 | // } 14 | // } 15 | // } 16 | 17 | // impl Functor for List { 18 | // type A = A; 19 | // type Lifted = Functor; 20 | 21 | // fn map(self, f: F) -> List 22 | // where 23 | // F: Fn(A) -> B, 24 | // { 25 | // todo!() 26 | // // match self { 27 | // // List::Cons(head, tail) 28 | // // } 29 | // } 30 | // } 31 | 32 | // #[test] 33 | // fn test_create_cons_cell() { 34 | // use crate::data::list::List::*; 35 | // let cell: List = Cons { 36 | // head: 1, 37 | // tail: Box::new(Cons { 38 | // head: 2, 39 | // tail: Box::new(Cons { 40 | // head: 3, 41 | // tail: Box::new(Nil), 42 | // }), 43 | // }), 44 | // }; 45 | // } 46 | -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod id; 2 | pub mod list; 3 | pub mod option; 4 | pub mod result; 5 | -------------------------------------------------------------------------------- /src/data/option.rs: -------------------------------------------------------------------------------- 1 | use crate::{applicative::Applicative, functor::Functor, functor::Pointed, monad::Monad}; 2 | 3 | impl Monad for Option { 4 | fn bind(self, mut f: F) -> Option 5 | where 6 | F: FnMut(A) -> Option, 7 | { 8 | self.and_then(f) 9 | } 10 | } 11 | 12 | impl Pointed for Option { 13 | fn pure(t: A) -> Option { 14 | Some(t) 15 | } 16 | } 17 | 18 | impl Applicative for Option { 19 | fn apply(self, b: Option, mut f: F) -> Option 20 | where 21 | F: FnMut(A, B) -> C, 22 | { 23 | let a = self?; 24 | let b = b?; 25 | Some(f(a, b)) 26 | } 27 | } 28 | 29 | impl Functor for Option { 30 | type A = A; 31 | type Lifted = Option; 32 | 33 | fn map(self, mut f: F) -> Option 34 | where 35 | F: FnMut(A) -> B, 36 | { 37 | self.map(f) 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod option_test { 43 | use super::*; 44 | 45 | #[test] 46 | fn test_option_monad() { 47 | struct Employee { 48 | name: String, 49 | department: String, 50 | } 51 | 52 | fn lookup_by_name(name: &str) -> Option { 53 | Some(Employee { 54 | name: name.to_string(), 55 | department: "some_department".to_string(), 56 | }) 57 | } 58 | 59 | fn no_one_look_up(_name: &str) -> Option { 60 | None 61 | } 62 | 63 | let actual = lookup_by_name("joe").map(|employee| employee.department); 64 | assert_eq!(actual, Some("some_department".to_string())); 65 | 66 | let actual = no_one_look_up("joe").map(|employee| employee.department); 67 | assert_eq!(actual, None); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/data/result.rs: -------------------------------------------------------------------------------- 1 | use crate::{applicative::Applicative, functor::Functor, functor::Pointed, monad::Monad}; 2 | 3 | impl Monad for Result { 4 | fn bind(self, f: F) -> Result 5 | where 6 | F: FnMut(A) -> Result, 7 | { 8 | self.and_then(f) 9 | } 10 | } 11 | 12 | impl Pointed for Result { 13 | fn pure(t: A) -> Result { 14 | Ok(t) 15 | } 16 | } 17 | 18 | impl Applicative for Result { 19 | fn apply(self, b: Result, mut f: F) -> Result 20 | where 21 | F: FnMut(A, B) -> C, 22 | { 23 | let a = self?; 24 | let b = b?; 25 | Ok(f(a, b)) 26 | } 27 | } 28 | 29 | impl Functor for Result { 30 | type A = A; 31 | type Lifted = Result; 32 | 33 | fn map(self, mut f: F) -> Result 34 | where 35 | F: FnMut(A) -> B, 36 | { 37 | self.map(f) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/functor.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait Functor { 2 | type A; 3 | type Lifted: Functor; 4 | 5 | fn map(self, f: F) -> Self::Lifted 6 | where 7 | F: FnMut(Self::A) -> B; 8 | } 9 | 10 | pub(crate) trait Pointed: Functor { 11 | fn pure(t: Self::A) -> Self::Lifted; 12 | } 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod applicative; 2 | pub mod data; 3 | pub mod functor; 4 | pub mod monad; 5 | 6 | macro_rules! mdo { 7 | ($i:ident <- $e:expr; $($t:tt)*) => { 8 | $e.bind(move |$i| mdo!($($t)*)) 9 | }; 10 | ($e:expr; $($t:tt)*) => { 11 | $e.bind(move |()| mdo!($($t)*)) 12 | }; 13 | (ret $e:expr) => { 14 | $e 15 | }; 16 | } 17 | 18 | mod test_do { 19 | use crate::data::option::*; 20 | use crate::{applicative::Applicative, functor::Functor, functor::Pointed, monad::Monad}; 21 | 22 | #[test] 23 | fn test_do_notation() { 24 | let actual = mdo! { 25 | x <- Option::pure(1); 26 | y <- Option::pure(2); 27 | ret Option::pure(x + y) 28 | }; 29 | assert_eq!(actual, Some(3)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/monad.rs: -------------------------------------------------------------------------------- 1 | use crate::applicative::Applicative; 2 | 3 | pub(crate) trait Monad: Applicative { 4 | fn bind(self, f: F) -> Self::Lifted 5 | where 6 | F: FnMut(Self::A) -> Self::Lifted; 7 | } 8 | --------------------------------------------------------------------------------