Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traverse trial #48

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions fp-core/src/applicative.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use crate::apply::Apply;
use crate::pure::Pure;

pub trait Applicative<A, B>: Apply<B> + Pure<A> {}
pub trait Applicative<B>: Apply<B> + Pure<B> {}

impl<A, B> Applicative<A, B> for Option<A> {}
impl<A, B> Applicative<B> for Option<A> {}
impl<A, B> Applicative<B> for Vec<A> {}

impl<A, B, E> Applicative<A, B> for Result<A, E> {}
// Note on trait bound: look in apply.rs with the
// impl Apply for Result
impl<A, B, E: Copy> Applicative<B> for Result<A, E> {}
30 changes: 22 additions & 8 deletions fp-core/src/apply.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
use crate::functor::Functor;
use crate::hkt::HKT;

type Applicator<B, S: HKT<B>> = <S as HKT<Box<dyn Fn(<S as HKT<B>>::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`: `<F<A> as HKT<B>>::Current` is `A`.
// Saying this in every place we'd have to was looking unwieldy, so this type does it for us.
// `Applicator<T, Option<U>>` is `Option<Box<dyn Fn(U) -> T>>`.
pub type Applicator<B, S: HKT<B>> = <S as HKT<Box<dyn Fn(&<S as HKT<B>>::Current) -> B>>>::Target;

pub trait Apply<B>: Functor<B> + HKT<Box<dyn Fn(<Self as HKT<B>>::Current) -> B>> {
fn ap(self, f: Applicator<B, Self>) -> <Self as HKT<B>>::Target;
pub trait Apply<B>: Functor<B> + HKT<Box<dyn Fn(&<Self as HKT<B>>::Current) -> B>> {
fn ap(self, f: &Applicator<B, Self>) -> <Self as HKT<B>>::Target;
}

impl<A, B> Apply<B> for Option<A> {
fn ap(self, f: Applicator<B, Self>) -> <Self as HKT<B>>::Target {
self.and_then(|v| f.map(|z| z(v)))
fn ap(self, f: &Applicator<B, Self>) -> <Self as HKT<B>>::Target {
self.and_then(|v| f.as_ref().map(|z| z(&v)))
}
}

impl<A, B, E> Apply<B> for Result<A, E> {
fn ap(self, f: Applicator<B, Self>) -> <Self as HKT<B>>::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<A, B, E: Copy> Apply<B> for Result<A, E> {
fn ap(self, f: &Applicator<B, Self>) -> <Self as HKT<B>>::Target {
self.and_then(|v| f.as_ref().map(|z| z(&v)).or_else(move |e| Result::Err(*e)))
}
}

impl<A, B> Apply<B> for Vec<A> {
fn ap(self, f: &Applicator<B, Self>) -> <Self as HKT<B>>::Target {
self.into_iter().flat_map(move |v| f.into_iter().map(move |z| z(&v))).collect()
}
}
12 changes: 12 additions & 0 deletions fp-core/src/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ impl Empty for String {
"".to_string()
}
}

impl<A> Empty for Option<A> {
fn empty() -> Option<A> {
Option::None
}
}

impl<A, E: Empty> Empty for Result<A, E> {
fn empty() -> Result<A, E> {
Result::Err(E::empty())
}
}
34 changes: 34 additions & 0 deletions fp-core/src/foldable.rs
Original file line number Diff line number Diff line change
@@ -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<B> here isn't about Self<B> having any meaning,
// it's about folding on some Self<A> -- the HKT lets us
Expand Down Expand Up @@ -47,3 +49,35 @@ impl<A, B> Foldable<B> for Vec<A> {
self.iter().rev().fold(b, |x, y| fa(y, x))
}
}

impl<A, B> Foldable<B> for Option<A> {
fn reduce<F>(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<F>(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<A, B, E: Debug> Foldable<B> for Result<A, E> {
fn reduce<F>(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<F>(self, b: B, fa: F) -> B
where
F: Fn(&A, B) -> B,
{
self.as_ref().and_then(|v| Result::Ok(fa(v, b))).unwrap()
}
}
15 changes: 12 additions & 3 deletions fp-core/src/functor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,33 @@ use crate::hkt::HKT;
pub trait Functor<B>: HKT<B> {
fn fmap<F>(self, f: F) -> Self::Target
where
F: FnOnce(Self::Current) -> B;
F: Fn(Self::Current) -> B;
}

impl<A, B> Functor<B> for Option<A> {
fn fmap<F>(self, f: F) -> Self::Target
where
// A is Self::Current
F: FnOnce(A) -> B,
F: Fn(A) -> B,
{
self.map(f)
}
}

impl<A, B> Functor<B> for Vec<A> {
fn fmap<F>(self, f: F) -> Self::Target
where
F: Fn(<Self as HKT<B>>::Current) -> B,
{
self.into_iter().map(f).collect::<Vec<B>>()
}
}

impl<A, B, E> Functor<B> for Result<A, E> {
fn fmap<F>(self, f: F) -> Self::Target
where
// A is Self::Current
F: FnOnce(A) -> B,
F: Fn(A) -> B,
{
self.map(f)
}
Expand Down
4 changes: 4 additions & 0 deletions fp-core/src/hkt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ macro_rules! derive_hkt {
};
}

// A nice way of saying G<F<B>> purely through HKT.
// TODO(after declarative macros stabilize): make a macro to define arbitrary lists of these.
pub type HktInHkt<G, F, B> = <G as HKT<<F as HKT<B>>::Target>>::Target;

derive_hkt!(Option);
derive_hkt!(Vec);

Expand Down
1 change: 1 addition & 0 deletions fp-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ pub mod monoid;
pub mod pure;
pub mod semigroup;
pub mod setoid;
pub mod traverse;
6 changes: 3 additions & 3 deletions fp-core/src/monad.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::applicative::Applicative;
use crate::chain::Chain;

pub trait Monad<A, B>: Chain<B> + Applicative<A, B> {}
pub trait Monad<B>: Chain<B> + Applicative<B> {}

impl<A, B> Monad<A, B> for Option<A> {}
impl<A> Monad<A> for Option<A> {}

impl<A, B, E> Monad<A, B> for Result<A, E> {}
impl<A, E: Copy> Monad<A> for Result<A, E> {}
4 changes: 4 additions & 0 deletions fp-core/src/monoid.rs
Original file line number Diff line number Diff line change
@@ -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<T: Clone> Monoid for Vec<T> {}
impl Monoid for String {}
impl<A: Semigroup> Monoid for Option<A> {}
impl<A: Semigroup, E: Empty + Debug> Monoid for Result<A, E> {}
14 changes: 10 additions & 4 deletions fp-core/src/pure.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
use crate::hkt::HKT;

pub trait Pure<A>: HKT<A> {
fn of(c: Self::Current) -> Self::Target;
fn of(c: A) -> Self::Target;
}

impl<A> Pure<A> for Option<A> {
fn of(a: A) -> Self::Target {
impl<A, B> Pure<A> for Option<B> {
fn of(a: A) -> <Self as HKT<A>>::Target {
Some(a)
}
}

impl<A, E> Pure<A> for Result<A, E> {
impl<A, B> Pure<A> for Vec<B> {
fn of(a: A) -> Self::Target {
vec![a]
}
}

impl<A, B, E> Pure<A> for Result<B, E> {
fn of(a: A) -> <Self as HKT<A>>::Target {
Ok(a)
}
}
29 changes: 29 additions & 0 deletions fp-core/src/semigroup.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use crate::empty::Empty;

use std::fmt::Debug;

pub trait Semigroup {
fn combine(self, other: Self) -> Self;
}
Expand Down Expand Up @@ -27,3 +31,28 @@ impl Semigroup for String {
format!("{}{}", self, other)
}
}

impl<A> Semigroup for Option<A>
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<A, E> Semigroup for Result<A, E>
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())
}
}
76 changes: 76 additions & 0 deletions fp-core/src/traverse.rs
Original file line number Diff line number Diff line change
@@ -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<B> and just B and F is Applicative
// for B and T<B>.
pub trait Traversable<B, F>:
Functor<B> + Foldable<B> + Functor<<F as HKT<B>>::Target> + Foldable<<F as HKT<B>>::Target>
where
F: Applicative<B> + Applicative<<Self as HKT<B>>::Target>,
Self:
Functor<B> + Foldable<B> + Functor<<F as HKT<B>>::Target> + Foldable<<F as HKT<B>>::Target>,
{
fn sequence(tfb: HktInHkt<Self, F, B>) -> HktInHkt<F, Self, B>;
}

impl<A, B, F> Traversable<B, F> for Option<A>
where
F: Applicative<B> + Applicative<Option<B>> + Pure<Applicator<B, Option<A>>>,
{
fn sequence(tbf: HktInHkt<Self, F, B>) -> HktInHkt<F, Self, B> {
match tbf {
Option::None => F::of(Option::None),
Option::Some(fb) => fb.ap(F::of(Box::new(|v| Option::Some(v)))),
}
}
}

impl<A, B, F> Traversable<B, F> for Vec<A>
where
F: Applicative<B> + Applicative<Vec<B>> + Pure<Applicator<B, Vec<A>>>,
{
fn traverse<AFB>(&self, traverser: Box<AFB>) -> HktInHkt<F, Self, B>
where
AFB: Fn(A) -> <F as HKT<B>>::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<Self, F, B>) -> HktInHkt<F, Self, B> {
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<A, B, E: Debug, F> Traversable<B, F> for Result<A, E>
where
F: Applicative<B> + Applicative<Result<B, E>> + Pure<Applicator<B, Result<A, E>>>,
{
fn sequence(tbf: HktInHkt<Self, F, B>) -> HktInHkt<F, Self, B> {
match tbf {
Result::Err(e) => F::of(Result::Err(e)),
Result::Ok(fb) => fb.ap(F::of(Box::new(|v| Result::Ok(v)))),
}
}
}
6 changes: 4 additions & 2 deletions fp-examples/src/applicative_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<i32, Option<i32>> = Some(Box::new(move |x: &i32| x + 1));
let x = Option::<i32>::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<i32, Result<i32, ()>> = Ok(Box::new(move |x: &i32| x + 1));
let x = Result::<i32, ()>::of(1).ap(&applicator);
assert_eq!(x, Ok(2));
}
}
1 change: 1 addition & 0 deletions fp-examples/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
4 changes: 2 additions & 2 deletions fp-examples/src/monad_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ mod example {

#[test]
fn monad_example() {
let x = Option::of(1).chain(|x| Some(x + 1));
let x = Option::<i32>::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::<i32, ()>::of(1).chain(|x| Ok(x + 1));
assert_eq!(x, Ok(2));
}
}
Loading