diff --git a/fp-core/src/applicative.rs b/fp-core/src/applicative.rs index 9151a7b..ab0b7e7 100644 --- a/fp-core/src/applicative.rs +++ b/fp-core/src/applicative.rs @@ -1,8 +1,11 @@ use crate::apply::Apply; use crate::pure::Pure; -pub trait Applicative: Apply + Pure {} +pub trait Applicative: Apply + Pure {} -impl Applicative for Option {} +impl Applicative for Option {} +impl Applicative for Vec {} -impl Applicative for Result {} +// Note on trait bound: look in apply.rs with the +// impl Apply for Result +impl Applicative for Result {} diff --git a/fp-core/src/apply.rs b/fp-core/src/apply.rs index 992a054..be1d140 100644 --- a/fp-core/src/apply.rs +++ b/fp-core/src/apply.rs @@ -1,20 +1,34 @@ use crate::functor::Functor; use crate::hkt::HKT; -type Applicator> = >::Current) -> B>>>::Target; +// This is the type for a container of functions we can apply over. +// In haskell, we say `apply::f a -> f (a -> b) -> f b`, however, in rust +// most `f (a -> b)` also need a size for the `a -> b`, so we box it. +// Furthermore, in rust, the HKT trait knows `a`: ` as HKT>::Current` is `A`. +// Saying this in every place we'd have to was looking unwieldy, so this type does it for us. +// `Applicator>` is `Option T>>`. +pub type Applicator> = >::Current) -> B>>>::Target; -pub trait Apply: Functor + HKT>::Current) -> B>> { - fn ap(self, f: Applicator) -> >::Target; +pub trait Apply: Functor + HKT>::Current) -> B>> { + fn ap(self, f: &Applicator) -> >::Target; } impl Apply for Option { - fn ap(self, f: Applicator) -> >::Target { - self.and_then(|v| f.map(|z| z(v))) + fn ap(self, f: &Applicator) -> >::Target { + self.and_then(|v| f.as_ref().map(|z| z(&v))) } } -impl Apply for Result { - fn ap(self, f: Applicator) -> >::Target { - self.and_then(|v| f.map(|z| z(v))) +// TODO(when as_deref is stable): remove the Copy restruction and replace the +// `or_else` with `as_deref_err`. +impl Apply for Result { + fn ap(self, f: &Applicator) -> >::Target { + self.and_then(|v| f.as_ref().map(|z| z(&v)).or_else(move |e| Result::Err(*e))) + } +} + +impl Apply for Vec { + fn ap(self, f: &Applicator) -> >::Target { + self.into_iter().flat_map(move |v| f.into_iter().map(move |z| z(&v))).collect() } } diff --git a/fp-core/src/empty.rs b/fp-core/src/empty.rs index 30f7d78..707a2a3 100644 --- a/fp-core/src/empty.rs +++ b/fp-core/src/empty.rs @@ -37,3 +37,15 @@ impl Empty for String { "".to_string() } } + +impl Empty for Option { + fn empty() -> Option { + Option::None + } +} + +impl Empty for Result { + fn empty() -> Result { + Result::Err(E::empty()) + } +} diff --git a/fp-core/src/foldable.rs b/fp-core/src/foldable.rs index 8f8b376..887c2a2 100644 --- a/fp-core/src/foldable.rs +++ b/fp-core/src/foldable.rs @@ -1,6 +1,8 @@ use crate::hkt::HKT; use crate::monoid::Monoid; +use std::fmt::Debug; + // Cheating: all HKT instances exist for any B, // so HKT here isn't about Self having any meaning, // it's about folding on some Self -- the HKT lets us @@ -47,3 +49,35 @@ impl Foldable for Vec { self.iter().rev().fold(b, |x, y| fa(y, x)) } } + +impl Foldable for Option { + fn reduce(self, b: B, fa: F) -> B + where + F: Fn(B, &A) -> B, + { + self.as_ref().and_then(|v| Option::Some(fa(b, v))).unwrap() + } + + fn reduce_right(self, b: B, fa: F) -> B + where + F: Fn(&A, B) -> B, + { + self.as_ref().and_then(|v| Option::Some(fa(v, b))).unwrap() + } +} + +impl Foldable for Result { + fn reduce(self, b: B, fa: F) -> B + where + F: Fn(B, &A) -> B, + { + self.as_ref().and_then(|v| Result::Ok(fa(b, v))).unwrap() + } + + fn reduce_right(self, b: B, fa: F) -> B + where + F: Fn(&A, B) -> B, + { + self.as_ref().and_then(|v| Result::Ok(fa(v, b))).unwrap() + } +} diff --git a/fp-core/src/functor.rs b/fp-core/src/functor.rs index a5b7730..c440989 100644 --- a/fp-core/src/functor.rs +++ b/fp-core/src/functor.rs @@ -3,24 +3,33 @@ use crate::hkt::HKT; pub trait Functor: HKT { fn fmap(self, f: F) -> Self::Target where - F: FnOnce(Self::Current) -> B; + F: Fn(Self::Current) -> B; } impl Functor for Option { fn fmap(self, f: F) -> Self::Target where // A is Self::Current - F: FnOnce(A) -> B, + F: Fn(A) -> B, { self.map(f) } } +impl Functor for Vec { + fn fmap(self, f: F) -> Self::Target + where + F: Fn(>::Current) -> B, + { + self.into_iter().map(f).collect::>() + } +} + impl Functor for Result { fn fmap(self, f: F) -> Self::Target where // A is Self::Current - F: FnOnce(A) -> B, + F: Fn(A) -> B, { self.map(f) } diff --git a/fp-core/src/hkt.rs b/fp-core/src/hkt.rs index 697689a..4fa8192 100644 --- a/fp-core/src/hkt.rs +++ b/fp-core/src/hkt.rs @@ -17,6 +17,10 @@ macro_rules! derive_hkt { }; } +// A nice way of saying G> purely through HKT. +// TODO(after declarative macros stabilize): make a macro to define arbitrary lists of these. +pub type HktInHkt = >::Target>>::Target; + derive_hkt!(Option); derive_hkt!(Vec); diff --git a/fp-core/src/lib.rs b/fp-core/src/lib.rs index 9e80cd4..432ddfa 100644 --- a/fp-core/src/lib.rs +++ b/fp-core/src/lib.rs @@ -16,3 +16,4 @@ pub mod monoid; pub mod pure; pub mod semigroup; pub mod setoid; +pub mod traverse; diff --git a/fp-core/src/monad.rs b/fp-core/src/monad.rs index 2e18204..b3215e4 100644 --- a/fp-core/src/monad.rs +++ b/fp-core/src/monad.rs @@ -1,8 +1,8 @@ use crate::applicative::Applicative; use crate::chain::Chain; -pub trait Monad: Chain + Applicative {} +pub trait Monad: Chain + Applicative {} -impl Monad for Option {} +impl Monad for Option {} -impl Monad for Result {} +impl Monad for Result {} diff --git a/fp-core/src/monoid.rs b/fp-core/src/monoid.rs index 9e403d5..a96f6a8 100644 --- a/fp-core/src/monoid.rs +++ b/fp-core/src/monoid.rs @@ -1,9 +1,13 @@ use crate::empty::Empty; use crate::semigroup::Semigroup; +use std::fmt::Debug; + pub trait Monoid: Empty + Semigroup {} impl Monoid for i32 {} impl Monoid for i64 {} impl Monoid for Vec {} impl Monoid for String {} +impl Monoid for Option {} +impl Monoid for Result {} diff --git a/fp-core/src/pure.rs b/fp-core/src/pure.rs index cd9b43b..16c07ce 100644 --- a/fp-core/src/pure.rs +++ b/fp-core/src/pure.rs @@ -1,17 +1,23 @@ use crate::hkt::HKT; pub trait Pure: HKT { - fn of(c: Self::Current) -> Self::Target; + fn of(c: A) -> Self::Target; } -impl Pure for Option { - fn of(a: A) -> Self::Target { +impl Pure for Option { + fn of(a: A) -> >::Target { Some(a) } } -impl Pure for Result { +impl Pure for Vec { fn of(a: A) -> Self::Target { + vec![a] + } +} + +impl Pure for Result { + fn of(a: A) -> >::Target { Ok(a) } } diff --git a/fp-core/src/semigroup.rs b/fp-core/src/semigroup.rs index 0f96b44..6b2adc5 100644 --- a/fp-core/src/semigroup.rs +++ b/fp-core/src/semigroup.rs @@ -1,3 +1,7 @@ +use crate::empty::Empty; + +use std::fmt::Debug; + pub trait Semigroup { fn combine(self, other: Self) -> Self; } @@ -27,3 +31,28 @@ impl Semigroup for String { format!("{}{}", self, other) } } + +impl Semigroup for Option +where + A: Semigroup, +{ + fn combine(self, other: Self) -> Self { + if self.is_some() && other.is_some() { + return Option::Some(self.unwrap().combine(other.unwrap())); + } + Option::None + } +} + +impl Semigroup for Result +where + A: Semigroup, + E: Empty + Debug, +{ + fn combine(self, other: Self) -> Self { + if self.is_ok() && other.is_ok() { + return Result::Ok(self.unwrap().combine(other.unwrap())); + } + Result::Err(E::empty()) + } +} diff --git a/fp-core/src/traverse.rs b/fp-core/src/traverse.rs new file mode 100644 index 0000000..9c30af7 --- /dev/null +++ b/fp-core/src/traverse.rs @@ -0,0 +1,76 @@ +use crate::applicative::{Applicative, Applicator}; +use crate::foldable::Foldable; +use crate::functor::Functor; +use crate::hkt::{HktInHkt, HKT}; +use crate::pure::Pure; + +use std::fmt::Debug; + +// Re-statement of the Haskell definition +// (http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Traversable.html) +// that uses sequence as the basis for traverse, that is: +// class (Functor t, Foldable t) => Traverse t where +// sequence::(Applicative f) => t (f b) -> f (t b) +// +// Unfortunately, Rust doesn't handle a "for all" in the same way, so to Rust, +// we must know that T is functorial over F and just B and F is Applicative +// for B and T. +pub trait Traversable: + Functor + Foldable + Functor<>::Target> + Foldable<>::Target> +where + F: Applicative + Applicative<>::Target>, + Self: + Functor + Foldable + Functor<>::Target> + Foldable<>::Target>, +{ + fn sequence(tfb: HktInHkt) -> HktInHkt; +} + +impl Traversable for Option +where + F: Applicative + Applicative> + Pure>>, +{ + fn sequence(tbf: HktInHkt) -> HktInHkt { + match tbf { + Option::None => F::of(Option::None), + Option::Some(fb) => fb.ap(F::of(Box::new(|v| Option::Some(v)))), + } + } +} + +impl Traversable for Vec +where + F: Applicative + Applicative> + Pure>>, +{ + fn traverse(&self, traverser: Box) -> HktInHkt + where + AFB: Fn(A) -> >::Target, + { + // Interestingly only uses that Self is a monoid. + let acc = F::of(vec![]); + for item in self { + let t = traverser(*item); + acc = acc.ap(|acc_list| acc_list + vec![t]); + } + return acc; + } + + fn sequence(tbf: HktInHkt) -> HktInHkt { + let acc = F::of(vec![]); + for item in tbf { + acc = item.ap(F::of(Box::new(|b| acc.ap(F::of(Box::new(|v| v.extend(&[b]))))))) + } + return acc; + } +} + +impl Traversable for Result +where + F: Applicative + Applicative> + Pure>>, +{ + fn sequence(tbf: HktInHkt) -> HktInHkt { + match tbf { + Result::Err(e) => F::of(Result::Err(e)), + Result::Ok(fb) => fb.ap(F::of(Box::new(|v| Result::Ok(v)))), + } + } +} diff --git a/fp-examples/src/applicative_example.rs b/fp-examples/src/applicative_example.rs index 1b18c0f..596b54d 100644 --- a/fp-examples/src/applicative_example.rs +++ b/fp-examples/src/applicative_example.rs @@ -5,13 +5,15 @@ mod example { #[test] fn applicative_example() { - let x = Option::of(1).ap(Some(Box::new(|x| x + 1))); + let applicator: Applicator> = Some(Box::new(move |x: &i32| x + 1)); + let x = Option::::of(1).ap(&applicator); assert_eq!(x, Some(2)); } #[test] fn applicative_example_on_result() { - let x = Result::<_, ()>::of(1).ap(Ok(Box::new(|x| x + 1))); + let applicator: Applicator> = Ok(Box::new(move |x: &i32| x + 1)); + let x = Result::::of(1).ap(&applicator); assert_eq!(x, Ok(2)); } } diff --git a/fp-examples/src/main.rs b/fp-examples/src/main.rs index 89d18e5..6acf95c 100644 --- a/fp-examples/src/main.rs +++ b/fp-examples/src/main.rs @@ -34,6 +34,7 @@ mod referential_transparency_example; mod semigroup_example; mod setoid_example; mod side_effects_example; +mod traverse_example; mod type_signature_example; fn main() { diff --git a/fp-examples/src/monad_example.rs b/fp-examples/src/monad_example.rs index 1035143..6bf9772 100644 --- a/fp-examples/src/monad_example.rs +++ b/fp-examples/src/monad_example.rs @@ -5,13 +5,13 @@ mod example { #[test] fn monad_example() { - let x = Option::of(1).chain(|x| Some(x + 1)); + let x = Option::::of(1).chain(|x| Some(x + 1)); assert_eq!(x, Some(2)); } #[test] fn monad_example_on_result() { - let x = Result::<_, ()>::of(1).chain(|x| Ok(x + 1)); + let x = Result::::of(1).chain(|x| Ok(x + 1)); assert_eq!(x, Ok(2)); } } diff --git a/fp-examples/src/traverse_example.rs b/fp-examples/src/traverse_example.rs new file mode 100644 index 0000000..bbfa574 --- /dev/null +++ b/fp-examples/src/traverse_example.rs @@ -0,0 +1,20 @@ +use fp_core::traverse::*; + +fn div_tuple(tpl: (f32, f32)) -> Option { + match tpl { + (_, 0) => Option::None, + (x, y) => Option::Some(x / y), + } +} + +#[test] +fn traverse_example() { + let will_be_some_quotients = vec![(2, 1), (4, 2), (6, 3)]; + assert_eq!( + will_be_some_quotients.traverse(div_tuple), + Option::Some(vec![2, 2, 2]) + ); + + let will_not_be_some_quotients = vec![(2, 1), (4, 2), (6, 3), (45, 0)]; + assert_eq!(will_not_be_some_quotients.traverse(div_tuple), Option::None); +}