Currently, I try to implement a functor/monad in Rust.
Basically, in my own project, I'm very comfortable to share the same concept and similar code base design among Haskell/Typescript and now Rust.
For instance, I prefer to start with a very basic functor/monad which is just a binary operation of function application that is f(x)
.
In Haskell,
pipe :: (a -> b) -> a -> b
pipe = \f -> \a -> f a
then,
--(|>) = flip ($) / (&)
(|>) :: a -> (a -> b) -> b
(|>) = flip pipe
infixl 1 |>
I love the simplicity of the primitive code, and actually want to declare it by my own. |>
is often called as "pipe-operator" in F# or other FP languages.
In fact |>
is a monad operator that can satisfy so-called Monad-laws, and the good point of this code is by just replacing from pipe
to another foo
, we can define another functor/monad operation.
So, accordingly similarity goes to Typescript code.
type pipe = <A, B>
(f: (a: A) => B) => (a: A) => B;
const pipe: pipe = f => a => f(a);
then,
//extend Object.prototype with pipe operator as 'map'
const map = Symbol("for pipe");
Object.assign(
Object.prototype,
{
[map]: function <A, B>(
this: A,
f: (a: A) => B) { return pipe(f)(this); }
}
);
Please note, I did this by extending Object.prototype
(anti-pattern in JS) just for illustration to emulate to implement the binary operation of pipe
.
The point is also in JS, it's possible to re-use of the concise pipe
definition and replacing it to other functions, it would be easy to create other functors/monads. Therefore, I'm very comfortable to use this design-pattern with identical concept switching between Haskell and Typescript.
So, here's the main part. Ideally, I want to repeat the same design-pattern in Rust code. It's a comfortable, integrated and productive approach for me and easier to explain the concept of my code to others.
I've been doing this for more than a month, but since I'm not experienced in Rust, it's not really successful. I repeatedly read many claims Rust adopted partially FP but has many restriction by design, and I understand that well. What I want to do is the best-shot pattern, at least to be able to use defined pipe
function to functor/monad trait.
So far, I'm studying 2 codes below;
1: https://docs.rs/apply/0.3.0/apply/trait.Apply.html
Represents a type which can have functions applied to it (implemented by default for all types).
/// Represents a type which can have functions applied to it (implemented
/// by default for all types).
pub trait Pipe<B> {
/// Apply a function which takes the parameter by value.
fn pipe<F>(self, f: F) -> B
where
Self: Sized,
F: FnOnce(Self) -> B,
{
f(self)
}
/// Apply a function which takes the parameter by reference.
fn pipe_ref<F>(&self, f: F) -> B
where
F: FnOnce(&Self) -> B,
{
f(self)
}
/// Apply a function which takes the parameter by mutable reference.
fn pipe_mut<F>(&mut self, f: F) -> B
where
F: FnOnce(&mut Self) -> B,
{
f(self)
}
}
impl<T: ?Sized, B> Pipe<B> for T {} // T is just value, not struct
I've changed the names of values from https://docs.rs/apply/0.3.0/src/apply/lib.rs.html#16-31
This code works like foo.pipe(f)
.
Here I want to have a function which type is something like:
type Pipe<A, B> = dyn FnOnce(fn(A) -> B) -> dyn FnOnce(fn(A)-> B);
to be replaced to other monadic function,
type FooMonad<A, M> = dyn FnOnce(fn(A) -> M) -> dyn FnOnce(fn(A)-> M);
The both is hard to implement due to Generic closure and HKT thing, but once it becomes possible I could rewrite it to
pub trait Pipe<B> {
/// Apply a function which takes the parameter by value.
fn pipe<F>(self, f: F) -> B
where
Self: Sized,
F: FnOnce(Self) -> B,
{
pipe(f)(self)
}
//.....
}
2: GATs
The working code with HKT is probably, with nightly-feature GATs: https://blog.rust-lang.org/2021/08/03/GATs-stabilization-push.html
and I found it promising implementation is:
https://github.com/RustyYato/type-families/blob/main/src/gat.rs
#![feature(generic_associated_types)]
pub trait Family: Copy {
type This<A>; //<-- GATs
}
pub trait Functor: Family {
fn map<A, B, F: Fn(A) -> B>(self, this: Self::This<A>, f: F) -> Self::This<B>;
}
And sorry it took so long, but my question here is,
Is it possible to take advantage of the above Functor trait with GATs into just T
(no struct
)for pipe
?
Also, I want to write the pipe
function independently in Rust, as in Typescript/Haskell.
type pipe = <A, B>
(f: (a: A) => B) => (a: A) => B;
const pipe: pipe = f => a => f(a);
Please advise.