0
votes

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.