Skip to content

Commit

Permalink
fix #2
Browse files Browse the repository at this point in the history
add Pure
refactor apXX and mapXX methods
  • Loading branch information
kalaninja committed Sep 20, 2023
1 parent 64100b8 commit ec59065
Show file tree
Hide file tree
Showing 20 changed files with 610 additions and 491 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ use rust2fun::prelude::*;
- [Invariant](https://docs.rs/rust2fun/0.2.0/rust2fun/invariant/trait.Invariant.html)
- [Functor](https://docs.rs/rust2fun/0.2.0/rust2fun/functor/trait.Functor.html)
- [Bifunctor](https://docs.rs/rust2fun/0.2.0/rust2fun/bifunctor/trait.Bifunctor.html)
- Pure
- AndThen
- [Apply](https://docs.rs/rust2fun/0.2.0/rust2fun/apply/trait.Apply.html)
- [Applicative](https://docs.rs/rust2fun/0.2.0/rust2fun/applicative/trait.Applicative.html)
- [FlatMap](https://docs.rs/rust2fun/0.2.0/rust2fun/flatmap/trait.FlatMap.html)
Expand Down Expand Up @@ -164,7 +166,7 @@ fn validate_credit_card(
let expiration = validate_expiration(expiration).into();
let cvv = validate_cvv(cvv).into();

Apply::map3(number, expiration, cvv, CreditCard::new)
MapN::map3(number, expiration, cvv, CreditCard::new)
}
```

Expand Down
27 changes: 14 additions & 13 deletions laws/src/applicative_laws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,33 @@ use crate::is_eq::IsEq;
pub fn applicative_identity<FA>(fa: FA) -> IsEq<FA>
where
FA: Higher + Clone,
FA::Target<fn(FA::Param) -> FA::Param>: Applicative + Apply<FA::Param, Target<FA::Param> = FA>,
FA::Target<fn(FA::Param) -> FA::Param>:
Pure + Apply<FA::Param, FA::Param, Target<FA::Param> = FA>,
{
let lhs: FA = <FA::Target<fn(FA::Param) -> FA::Param>>::pure(id).ap(fa.clone());
IsEq::equal_under_law(lhs, fa)
}

pub fn applicative_homomorphism<FA, FB, F>(a: FA::Param, mut f: F) -> IsEq<FB>
where
FA: Applicative,
F: FnMut(FA::Param) -> FB::Param,
FA: Pure,
FA::Param: Clone,
FA::Target<F>:
Applicative + Apply<FB::Param, Target<FB::Param> = FB> + Higher<Target<FA::Param> = FA>,
FB: Applicative,
F: FnMut(FA::Param) -> FB::Param,
Pure + Apply<FA::Param, FB::Param, Target<FB::Param> = FB> + Higher<Target<FA::Param> = FA>,
FB: Pure,
{
let lhs = Applicative::pure(f(a.clone()));
let rhs = <FA::Target<F>>::pure(f).ap(Applicative::pure(a));
let lhs = Pure::pure(f(a.clone()));
let rhs = <FA::Target<F>>::pure(f).ap(Pure::pure(a));
IsEq::equal_under_law(lhs, rhs)
}

pub fn applicative_map<FA, B, F>(fa: FA, mut f: F) -> IsEq<FA::Target<B>>
where
FA: Apply<B> + Clone,
F: FnMut(FA::Param) -> B,
FA: Functor<B> + Clone,
FA::Target<F>:
Applicative + Apply<B, Target<B> = FA::Target<B>> + Higher<Target<FA::Param> = FA>,
Pure + Apply<FA::Param, B, Target<B> = FA::Target<B>> + Higher<Target<FA::Param> = FA>,
{
let lhs = fa.clone().map(&mut f);
let rhs = <FA::Target<F>>::pure(f).ap(fa);
Expand All @@ -39,9 +40,9 @@ where

pub fn ap_product_consistent<FA, B, F>(fa: FA, ff: FA::Target<F>) -> IsEq<FA::Target<B>>
where
FA: Higher + Clone,
F: Fn(FA::Param) -> B,
FA::Target<F>: Apply<B, Target<B> = FA::Target<B>>
FA: Higher + Clone,
FA::Target<F>: Apply<FA::Param, B, Target<B> = FA::Target<B>>
+ Higher<Target<FA::Param> = FA>
+ Semigroupal<FA::Param, Target<(F, FA::Param)> = FA::Target<(F, FA::Param)>>
+ Clone,
Expand All @@ -55,9 +56,9 @@ where

pub fn applicative_unit<FA>(a: FA::Param) -> IsEq<FA>
where
FA: Applicative,
FA: Pure,
FA::Param: Clone,
FA::Target<()>: Applicative + Functor<FA::Param, Target<FA::Param> = FA>,
FA::Target<()>: Pure + Functor<FA::Param, Target<FA::Param> = FA>,
{
let lhs = <FA::Target<()>>::unit().map(|_| a.clone());
let rhs = FA::pure(a);
Expand Down
6 changes: 3 additions & 3 deletions laws/src/apply_laws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::is_eq::IsEq;

pub fn map2_product_consistency<FA, FB, FC, F>(fa: FA, fb: FB, mut f: F) -> IsEq<FC>
where
FA: Apply<FB::Param> + Higher<Target<FB::Param> = FB> + Higher<Target<FC::Param> = FC> + Clone,
FA: MapN<FB::Param> + Higher<Target<FB::Param> = FB> + Higher<Target<FC::Param> = FC> + Clone,
FB: Higher + Clone,
FC: Higher,
F: FnMut(FA::Param, FB::Param) -> FC::Param,
Expand All @@ -18,7 +18,7 @@ where

pub fn product_r_consistency<FA, FB>(fa: FA, fb: FB) -> IsEq<FB>
where
FA: Apply<FB::Param> + Higher<Target<FB::Param> = FB> + Clone,
FA: MapN<FB::Param> + Higher<Target<FB::Param> = FB> + Clone,
FB: Higher + Clone,
FA::Target<(FA::Param, FB::Param)>: Functor<FB::Param, Target<FB::Param> = FB>,
{
Expand All @@ -30,7 +30,7 @@ where

pub fn product_l_consistency<FA, FB>(fa: FA, fb: FB) -> IsEq<FA>
where
FA: Apply<FB::Param>
FA: MapN<FB::Param>
+ Higher<Target<FB::Param> = FB>
+ Higher<Target<<FA as Higher>::Param> = FA>
+ Clone,
Expand Down
2 changes: 1 addition & 1 deletion laws/src/flatmap_laws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ where
FA: Functor<B> + Clone,
F: Fn(FA::Param) -> B,
FA::Target<F>: FlatMap<B, Target<B> = FA::Target<B>>
+ Apply<B, Target<B> = FA::Target<B>>
+ Apply<FA::Param, B, Target<B> = FA::Target<B>>
+ Higher<Target<FA::Param> = FA>
+ Clone,
{
Expand Down
6 changes: 3 additions & 3 deletions laws/src/monad_laws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use crate::is_eq::IsEq;

pub fn monad_left_identity<FA, B, F>(a: FA::Param, mut f: F) -> IsEq<FA::Target<B>>
where
F: FnMut(FA::Param) -> FA::Target<B>,
FA: Monad<B>,
FA::Param: Clone,
F: FnMut(FA::Param) -> FA::Target<B>,
{
let lhs = f(a.clone());
let rhs = FA::pure(a).flat_map(f);
Expand All @@ -26,9 +26,9 @@ where

pub fn map_flat_map_coherence<FA, B, F>(fa: FA, mut f: F) -> IsEq<FA::Target<B>>
where
FA: Monad<B> + Clone,
F: FnMut(FA::Param) -> B,
FA::Target<B>: Applicative,
FA: Monad<B> + Functor<B> + Clone,
FA::Target<B>: Pure,
{
let lhs = fa.clone().flat_map(|a| <FA::Target<B>>::pure(f(a)));
let rhs = fa.map(f);
Expand Down
91 changes: 45 additions & 46 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use proc_macro::{TokenStream, TokenTree};

use proc_macro2::Ident;
use quote::{format_ident, quote};

#[proc_macro]
Expand Down Expand Up @@ -92,98 +93,76 @@ pub fn noop_arity(input: TokenStream) -> TokenStream {
}

#[proc_macro]
pub fn apply_ap(input: TokenStream) -> TokenStream {
pub fn ap_n(input: TokenStream) -> TokenStream {
let arity = parse_arity(input);
let fn_name = format_ident!("ap{}", arity);
let msg = format!("Is a version of [Apply::ap] for a function of {arity} arguments.");

let types = ('A'..='Y').take(arity as usize);
let generic_type_args = types
.clone()
.filter(|&x| x != 'B')
.map(|t| format_ident!("{}", t));
let fn_args = types.clone().map(|t| {
let a = format_ident!("f{}", t.to_lowercase().next().unwrap());
let t = format_ident!("{}", t);
quote!(#a: Self::Target<#t>)
});
let generic_type_args = types.clone().map(|t| format_ident!("{}", t));
let fn_args = types.clone().map(to_fn_arg);
let constraints = {
let (f, mut c) = generic_type_args.clone().fold(
(quote!((Self::Param, B)), Vec::new()),
|(curr, mut gen), t| {
let next = quote!((#curr, #t));
let next_gen = quote! {
Self::Target< #curr >: Apply< #t , Target< #t > = Self::Target< #t >>
+ Higher<Target< #next > = Self::Target< #next >>
};
gen.push(next_gen);
(next, gen)
},
);
let (f, mut c) = generic_type_args
.clone()
.skip(1)
.fold((quote!(A), Vec::new()), generate_semigroupal_constraint);

c.push(quote!(Self::Target< #f >: Functor<Z, Target<Z> = Self::Target<Z>>));
c.push(quote!(Self::Target< #f >: Functor<Z, Target<Z> = Self::Target<Z>> + Clone));
c
};
let fn_types = types.clone().map(|t| format_ident!("{}", t));
let f_args = types.map(|t| format_ident!("{}", t.to_lowercase().next().unwrap()));
let f_args = types
.clone()
.map(|t| format_ident!("{}", t.to_lowercase().next().unwrap()));

let types = ['B', 'A'].into_iter().chain('C'..'Z').take(arity as usize);
let products = types.clone().map(|t| {
let products = types.clone().skip(1).map(|t| {
let a = format_ident!("f{}", t.to_lowercase().next().unwrap());
quote!(product(#a))
});
let map_pattern = types
.skip(1)
.map(|t| format_ident!("{}", t.to_lowercase().next().unwrap()))
.fold(quote!(func), |acc, a| quote!((#acc, #a)));
.fold(quote!(a), |acc, a| quote!((#acc, #a)));

let expanded = quote! {
#[doc = #msg]
#[inline]
fn #fn_name< #( #generic_type_args ),* , Z >( self, #( #fn_args ),*) -> Self::Target<Z>
fn #fn_name< #( #generic_type_args ),* >( self, #( #fn_args ),*) -> Self::Target<Z>
where
Self::Param: FnOnce( #( #fn_types ),* ) -> Z,
Self::Param: FnMut( #( #fn_types ),* ) -> Z,
Self: Sized,
#( #constraints ),*
{
self. #( #products ).* .map(| #map_pattern | func( #( #f_args ),* ))
let product = fa. #( #products ).*;
self.and_then(|mut func| product.clone().map(| #map_pattern | func( #( #f_args ),* )))
}
};

TokenStream::from(expanded)
}

#[proc_macro]
pub fn apply_map(input: TokenStream) -> TokenStream {
pub fn map_n(input: TokenStream) -> TokenStream {
let arity = parse_arity(input);
let fn_name = format_ident!("map{}", arity);
let msg = format!("Is a version of [Apply::map2] for a function of {arity} arguments.");

let types = ('B'..'Z').take((arity - 1) as usize);
let generic_type_args = types.clone().skip(1).map(|t| format_ident!("{}", t));
let fn_args = types.clone().map(|t| {
let a = format_ident!("f{}", t.to_lowercase().next().unwrap());
let t = format_ident!("{}", t);
quote!(#a: Self::Target<#t>)
});
let fn_args = types.clone().map(to_fn_arg);
let constraints = {
let (f, mut c) = generic_type_args.clone().fold(
(quote!((Self::Param, B)), Vec::new()),
|(curr, mut gen), t| {
let next = quote!((#curr, #t));
let next_gen = quote! {
Self::Target< #curr >: Apply< #t , Target< #t > = Self::Target< #t >>
+ Higher<Target< #next > = Self::Target< #next >>
};
gen.push(next_gen);
(next, gen)
},
generate_semigroupal_constraint,
);

c.push(quote!(Self::Target< #f >: Functor<Z, Target<Z> = Self::Target<Z>>));
c
};
let fn_types = types.clone().map(|t| format_ident!("{}", t));
let f_args = types.clone().map(|t| format_ident!("{}", t.to_lowercase().next().unwrap()));
let f_args = types
.clone()
.map(|t| format_ident!("{}", t.to_lowercase().next().unwrap()));

let products = types.clone().map(|t| {
let a = format_ident!("f{}", t.to_lowercase().next().unwrap());
Expand All @@ -209,6 +188,26 @@ pub fn apply_map(input: TokenStream) -> TokenStream {
TokenStream::from(expanded)
}

fn to_fn_arg(t: char) -> proc_macro2::TokenStream {
let a = format_ident!("f{}", t.to_lowercase().next().unwrap());
let t = format_ident!("{}", t);
quote!(#a: Self::Target<#t>)
}

fn generate_semigroupal_constraint(
acc: (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>),
t: Ident,
) -> (proc_macro2::TokenStream, Vec<proc_macro2::TokenStream>) {
let (curr, mut acc) = acc;
let next = quote!((#curr, #t));
let gen = quote! {
Self::Target< #curr >: Semigroupal< #t , Target< #t > = Self::Target< #t >>
+ Higher<Target< #next > = Self::Target< #next >>
};
acc.push(gen);
(next, acc)
}

fn parse_arity(input: TokenStream) -> u32 {
match input.into_iter().next().expect("arity is required") {
TokenTree::Literal(x) => x
Expand Down
85 changes: 85 additions & 0 deletions src/and_then.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! AndThen.

use std::marker::PhantomData;

use crate::higher::Higher;

/// Gives access to the [and_then] method. This trait is needed to implement [ApN]. The motivation
/// for not using [FlatMap] is that there are situations where `and_then` can be implemented but
/// not [FlatMap::flat_map], e.g. [Validated].
///
/// [and_then]: AndThen::and_then
/// [ApN]: crate::ap_n::ApN
/// [Validated]: crate::data::validated::Validated
pub trait AndThen<B>: Higher {
/// Maps a function over a value in the context and flattens the resulting nested context.
fn and_then<F>(self, f: F) -> Self::Target<B>
where
F: FnMut(Self::Param) -> Self::Target<B>;
}

// TODO. Refactor this when specialization is stable.
/// Macro to implement [AndThen] for types implementing a [FlatMap::flat_map].
#[macro_export]
macro_rules! and_then_flat_map {
($name:ident<$( $t:tt ),+>) => {
impl<B, $( $t ),+> $crate::and_then::AndThen<B> for $name<$( $t ),+> {
#[inline]
fn and_then<F>(self, f: F) -> Self::Target<B>
where
F: FnMut(Self::Param) -> Self::Target<B>,
{
$crate::flatmap::FlatMap::flat_map(self, f)
}
}
};
($name:ident<$( $t:tt ),+>, $ct:tt $(+ $dt:tt )*) => {
impl<B:$ct $(+ $dt )*, $( $t ),+> $crate::and_then::AndThen<B> for $name<$( $t ),+> {
#[inline]
fn and_then<F>(self, f: F) -> Self::Target<B>
where
F: FnMut(Self::Param) -> Self::Target<B>,
{
$crate::flatmap::FlatMap::flat_map(self, f)
}
}
};
}

impl<A, B> AndThen<B> for PhantomData<A> {
fn and_then<F>(self, _f: F) -> PhantomData<B>
where
F: FnMut(A) -> PhantomData<B>,
{
PhantomData
}
}

and_then_flat_map!(Option<T>);
and_then_flat_map!(Result<T, E>);

if_std! {
use std::boxed::Box;
use std::collections::*;
use std::hash::Hash;
use std::vec::Vec;
use crate::flatmap::FlatMap;

and_then_flat_map!(Vec<T>);
and_then_flat_map!(LinkedList<T>);
and_then_flat_map!(VecDeque<T>);
and_then_flat_map!(Box<T>);
and_then_flat_map!(BinaryHeap<T>, Ord);
and_then_flat_map!(BTreeSet<T>, Ord);
and_then_flat_map!(HashSet<T>, Hash + Eq);

impl<A, B, K: Hash + Eq> AndThen<B> for HashMap<K, A> {
#[inline]
fn and_then<F>(self, f: F) -> HashMap<K, B>
where
F: FnMut(A) -> HashMap<K, B>,
{
self.flat_map(f)
}
}
}
Loading

0 comments on commit ec59065

Please sign in to comment.