aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib.rs57
-rw-r--r--tests/multiple_generics.rs65
2 files changed, 102 insertions, 20 deletions
diff --git a/src/lib.rs b/src/lib.rs
index c0783f6..f12fe95 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,31 @@
+//! A macro that uses the traits from the [higher] crate and generates a Free [`Monad`][higher::Monad] type for a given [`Functor`][higher::Functor].
+//!
+//! # Free Monad? What is that?
+//! A Free Monad is the left-adjoint to the Forget-Functor from the category of Monads into the category of Endofunctors.
+//!
+//! This of course doesn't explain what one can do with a Free Monad data type. There are plenty of blog posts and websites that
+//! explore different use cases. A very good explanation is given by Nikolay Yakimov's
+//! [Introduction to Free Monads](https://serokell.io/blog/introduction-to-free-monads).
+//!
+//! # Why a Macro?
+//! Until [non-lifetime binders](https://github.com/rust-lang/rust/issues/108185) become stable, this seems to be the easiest way.
+//! In generic code, the type signature would be `enum Free<A,F> where F : Functor<Free<A,F>>`. If one now wants to implement the [`Functor`][higher::Functor]
+//! trait for this, it is not really possible to express the `Target<T> = Free<A,F::Target<Free<A,F::Target<...>>>>` generic associated type.
+//!
+//! See the [blog post about this crate](https://www.grois.info/posts/2023-03/2023-03-11-adventures-with-free-monads-and-higher.xhtml)
+//! for a more detailed explanation.
+
pub extern crate higher;
#[macro_export]
macro_rules! free {
- ($v:vis $name:ident<$($other_lifetimes:lifetime,)* $generic:ident>, $f:ty) => {
+ ($v:vis $name:ident<$($other_lifetimes:lifetime,)* $generic:ident $(,$other_generics:ident)*>, $f:ty) => {
#[derive(Clone)]
- $v enum $name<$($other_lifetimes,)* $generic> {
+ $v enum $name<$($other_lifetimes,)* $generic $(,$other_generics)*> {
Pure($generic),
Free(Box<$f>)
}
- impl<$($other_lifetimes,)* $generic> $name<$($other_lifetimes,)* $generic>{
+ impl<$($other_lifetimes,)* $generic $(,$other_generics)*> $name<$($other_lifetimes,)* $generic $(,$other_generics)*>{
$v fn lift_f(functor : <$f as $crate::higher::Functor<Self>>::Target<$generic>) -> Self{
use $crate::higher::Functor;
Self::Free(Box::new(functor.fmap(|a| Self::Pure(a))))
@@ -23,10 +40,10 @@ macro_rules! free {
}
}
- impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* A> $crate::higher::Functor<'free_macro_reserved_lifetime,A> for $name<$($other_lifetimes,)* A> {
- type Target<T> = $name<$($other_lifetimes,)* T>;
- fn fmap<B,F>(self, f: F) -> Self::Target<B> where F: Fn(A) -> B + 'free_macro_reserved_lifetime{
- fn __fmap_impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* A, B, F>(s : $name<$($other_lifetimes,)* A>, f: &F) -> $name<$($other_lifetimes,)* B> where F: Fn(A) -> B + 'free_macro_reserved_lifetime{
+ impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* $generic $(,$other_generics)*> $crate::higher::Functor<'free_macro_reserved_lifetime,$generic> for $name<$($other_lifetimes,)* $generic $(,$other_generics)*> {
+ type Target<FreeMacroReservedType> = $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*>;
+ fn fmap<FreeMacroReservedType,FreeMacroReservedType2>(self, f: FreeMacroReservedType2) -> Self::Target<FreeMacroReservedType> where FreeMacroReservedType2: Fn($generic) -> FreeMacroReservedType + 'free_macro_reserved_lifetime{
+ fn __fmap_impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* $generic $(,$other_generics)*, FreeMacroReservedType, FreeMacroReservedType2>(s : $name<$($other_lifetimes,)* $generic $(,$other_generics)*>, f: &FreeMacroReservedType2) -> $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*> where FreeMacroReservedType2: Fn($generic) -> FreeMacroReservedType + 'free_macro_reserved_lifetime{
use $crate::higher::Functor;
match s {
$name::Pure(a) => {$name::Pure(f(a))},
@@ -37,32 +54,32 @@ macro_rules! free {
}
}
- impl<$($other_lifetimes,)* A> $crate::higher::Pure<A> for $name<$($other_lifetimes,)* A> {
- fn pure(value : A) -> Self {
+ impl<$($other_lifetimes,)* $generic $(,$other_generics)*> $crate::higher::Pure<$generic> for $name<$($other_lifetimes,)* $generic $(,$other_generics)*> {
+ fn pure(value : $generic) -> Self {
Self::Pure(value)
}
}
- impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* A> $crate::higher::Apply<'free_macro_reserved_lifetime, A> for $name<$($other_lifetimes,)* A> where A: 'free_macro_reserved_lifetime + Clone, Self : Clone {
- type Target<T> = $name<$($other_lifetimes,)* T> where T:'free_macro_reserved_lifetime;
- fn apply<B>(
+ impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* $generic $(,$other_generics)*> $crate::higher::Apply<'free_macro_reserved_lifetime, $generic> for $name<$($other_lifetimes,)* $generic $(,$other_generics)*> where $generic: 'free_macro_reserved_lifetime + Clone, Self : Clone {
+ type Target<FreeMacroReservedType> = $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*> where FreeMacroReservedType:'free_macro_reserved_lifetime;
+ fn apply<FreeMacroReservedType>(
self,
- f: <Self as $crate::higher::Apply<'free_macro_reserved_lifetime, A>>::Target<$crate::higher::apply::ApplyFn<'free_macro_reserved_lifetime, A, B>>,
- ) -> <Self as $crate::higher::Apply<'free_macro_reserved_lifetime, A>>::Target<B>
+ f: <Self as $crate::higher::Apply<'free_macro_reserved_lifetime, $generic>>::Target<$crate::higher::apply::ApplyFn<'free_macro_reserved_lifetime, $generic, FreeMacroReservedType>>,
+ ) -> <Self as $crate::higher::Apply<'free_macro_reserved_lifetime, $generic>>::Target<FreeMacroReservedType>
where
- B: 'free_macro_reserved_lifetime,
+ FreeMacroReservedType: 'free_macro_reserved_lifetime,
{
$crate::higher::apply::ap(f,self)
}
}
- impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* A> $crate::higher::Bind<'free_macro_reserved_lifetime,A> for $name<$($other_lifetimes,)* A>{
- type Target<T> = $name<$($other_lifetimes,)* T>;
- fn bind<B, F>(self, f: F) -> Self::Target<B>
+ impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* $generic $(,$other_generics)*> $crate::higher::Bind<'free_macro_reserved_lifetime,$generic> for $name<$($other_lifetimes,)* $generic $(,$other_generics)*>{
+ type Target<FreeMacroReservedType> = $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*>;
+ fn bind<FreeMacroReservedType, FreeMacroReservedType2>(self, f: FreeMacroReservedType2) -> Self::Target<FreeMacroReservedType>
where
- F: Fn(A) -> Self::Target<B>,
+ FreeMacroReservedType2: Fn($generic) -> Self::Target<FreeMacroReservedType>,
{
- fn __bind_impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* A, B, F>(s : $name<$($other_lifetimes,)* A>, f: &F) -> $name<$($other_lifetimes,)* B> where F: Fn(A) -> $name<$($other_lifetimes,)* B> + 'free_macro_reserved_lifetime{
+ fn __bind_impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* $generic $(,$other_generics)*, FreeMacroReservedType, FreeMacroReservedType2>(s : $name<$($other_lifetimes,)* $generic $(,$other_generics)*>, f: &FreeMacroReservedType2) -> $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*> where FreeMacroReservedType2: Fn($generic) -> $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*> + 'free_macro_reserved_lifetime{
use $crate::higher::Functor;
match s {
$name::Pure(a) => {f(a)},
diff --git a/tests/multiple_generics.rs b/tests/multiple_generics.rs
new file mode 100644
index 0000000..ce13414
--- /dev/null
+++ b/tests/multiple_generics.rs
@@ -0,0 +1,65 @@
+//! Tests if multiple generic parameters work, for the case that lifetimes are independent of mapping functions.
+//! For simplicity, it just creates a FreeResult
+
+use higher_free_macro::free;
+use higher::{Functor, Bind, Apply};
+
+free!(FreeResult<O,E>, Result<FreeResult<O,E>,E>);
+
+#[test]
+fn test_multiple_generics(){
+ let m : FreeResult<_, String> = FreeResult::lift_f(Ok(37u32));
+ let m = m.fmap(|x| x*2);
+ let m = m.bind(|x| FreeResult::Free(Box::new(Ok(FreeResult::Pure(x)))));
+ let f = FreeResult::Pure((|x| x*3).into());
+ let m = m.apply(f);
+ match m {
+ FreeResult::Free(b) => {
+ match *b {
+ Ok(f) => {
+ match f {
+ FreeResult::Free(b) => {
+ match *b {
+ Ok(f) => {
+ match f{
+ FreeResult::Pure(x) => assert_eq!(x, 37*6),
+ _ => unreachable!()
+ }
+ }
+ _ => unreachable!()
+ }
+ },
+ _ => unreachable!()
+ }
+ }
+ _ => unreachable!()
+ }
+ }
+ _ => unreachable!()
+ }
+}
+
+#[test]
+fn test_multiple_generics2(){
+ let m : FreeResult<_, String> = FreeResult::lift_f(Ok(37u32));
+ let m = m.bind(|_| FreeResult::<u32, _>::lift_f(Err("An early out.".to_owned())));
+ match m{
+ FreeResult::Free(m) => {
+ match &*m {
+ Ok(m) => {
+ match m{
+ FreeResult::Free(m) => {
+ match &**m {
+ Err(e) => assert_eq!(e, "An early out."),
+ _ => unreachable!()
+ }
+ }
+ _ => unreachable!()
+ }
+ },
+ _ => unreachable!()
+ }
+ },
+ _ => unreachable!()
+ }
+} \ No newline at end of file