Click to change color scheme

Notes on codes, projects and everything

Latest Post

Reinventing wheels with cheats using Rustlang

I finally put in some time and effort learning myself a bit of Rust. Though I am still struggling with ownership and lifetimes (which is essentially everything about the language, to be honest), I find it more interesting compared to Golang, which is relatively boring, though being functional (no pun intended). While learning the language, the one thing I came across often is the Option enum, then I remembered that I read something about Monad.

It was a great Aha moment!

So while I am getting used to seeing compiling errors due to lifetimes and ownership Option enum, namely Some(value) and None, I felt like I found something that somehow answers my frustration not answered by typed languages. There’s one thing I like from dynamic-typed language that is not possible in typed language like Golang, which is what to return when the user send in rubbish to the function. Of course, sending a zero value is usually what I assume most people do, but I prefer sending something back in different type to differentiate between “Hey you are sending me nonsense” and “Hey the result is zero”.

Rust answers this through the use of enum, and one of the most used is provided in standard library which is Option. Then I realized in a lot of places there are being used, so I started reading more and experiment with them. So after getting used to the enum, revisiting the article gave me a new insight into understanding Monad. I didn’t really understand what the post was about before this. Category theory in total was so abstract I didn’t get a thing, even reading / watching numerous talk and tutorial posts.

But then I now know slightly more.

And I thought if it is possible to somehow do it in Rust, by cheating a little bit, as the original article was written for Haskell programmers. Then I came out with something like this (the code is of proof-of-concept quality, and probably violates all the ownership rules, obviously).

First I started by implementing Functor. Functor is a wrapped value that can be fmapped by a function. In the land of Rustlang, we can probably represent this as a trait, as follows,

pub trait Functor<T, U, V> {
    fn fmap(self, func: fn(T) -> U) -> V;
}

So fmaps takes in a functor, and a function that takes an unwrapped value, and return another unwrapped value. After some computation, fmap returns a wrapped value. The analogy of wrapped value is usually used when describing Monads, Applicatives and Functors, which is practically putting values into something that wraps around it, perhaps a box. The type of wrapped value that made this post is Option, but there are other types too, for instance a list of values in the form of Vectors, or even another function/anonymous function/closure. Therefore, we have the following implementations for the trait above, in the mentioned three forms

impl Functor<i32, i32, Option<i32>> for Option<i32> {
    fn fmap(self, func: fn(i32) -> i32) -> Option<i32> {
        self.map(func)
    }
}

impl Functor<i32, i32, Vec<i32>> for Vec<i32> {
    fn fmap(self, func: fn(i32) -> i32) -> Vec<i32> {
        self.into_iter().map(|x| func(x)).collect()
    }
}

impl<F: 'static> Functor<i32, i32, Box<Fn(i32) -> i32>> for F where F: Fn(i32) -> i32 {
    fn fmap(self, func: fn(i32) -> i32) -> Box<Fn(i32) -> i32> {
        Box::new(move |x| {
            func(self(x))
        })
    }
}

This is exactly what I was referring to as cheating. The function body makes it really clear that fmap is already implemented in a different way in Rust, and I am just rewiring them in different way. But I am doing this to further understand what those things are, so I guess it is … fine? One thing I am not sure about the code is that I probably should borrow stuff instead of moving them into the function, but I gave up wrestling the compiler, and most importantly, they work, somehow.

// Returns Some(2)
println!("Functor (wrapped value) {:?}", Some(1).fmap(|x| x * 2));
// Returns vec![2, 4, 6]
println!("Functor (vec) {:?}", vec![1, 2, 3].fmap(|x| x * 2));
// Returns 6
println!("Functor (function composition) {:?}", (|x| x + 1).fmap(|x| x * 2)(2));

Then I went on with Monad. Monad on the other hand is something that implements a function called bind.

pub trait Monad<T, U> {
    fn bind(self, func: fn(T) -> U) -> U;
}

Unlike fmap, the function required by bind takes an unwrapped value, and return a wrapped value. Bind itself returns a wrapped value. So cheating away

impl Monad<i32, Option<i32>> for Option<i32> {
    fn bind(self, func: fn(i32) -> Option<i32>) -> Option<i32> {
        self.and_then(func)
    }
}

I only do for Option this time as I got a bit tired wrestling with the compiler errors, also I already kinda get what it is by the time this was done. Laziness is also why I skipped Applicatives, partly also because I am not that comfortable enough with Rust to reimplement lift, which is the function that defines an Applicative. Perhaps when I get more comfortable with Rust I would attempt again (and possibly re-implement Maybe).

So I shall stop here and continue with my work.

Leave a comment

Other new Posts