
Idiomatic Monads in Rust - fanf2
https://varkor.github.io/blog/2019/03/28/idiomatic-monads-in-rust.html
======
tinco
When this was released I posted this comment on the Haskell subreddit,
thinking I was on the rust subreddit. It's got some musings about what the
dangers of becoming like Haskell are:

I wonder, were the Rust community be given the same power as the Haskell
community, would they use it to create a complex more advanced idiomatic Rust
that you could only read and understand after months of training?

There is this weird phenomenon in Haskell, I attribute it to Haskell having a
syntax that feels limiting, but a type system that seems almost limitless.

There are two branches of idiomatic Haskell. One is the style anyone would
recognize as generic "functional programming" and would be able to work with
and be productive after a week or two of practice.

The other is where a dozen language features are enabled, and the latest and
greatest utility libraries are imported and Haskell shows itself to be as
flexible and alienating as Lisp. An expert can wield extreme expression under
purity and safety. A beginner stands no chance.

It would be interesting to see more expressive power come to Rust. But it
would be a bad thing if it would come at the cost of being beginner friendly.
Rust might be challenging to write as a beginner due to the borrow checker,
but at least it's easy enough to read.

~~~
Animats
Um, yes. C++ went down that rathole, and it's not pretty.

The functional people have been able to hammer Rust into a functional
language, sort of. Now they want a Haskell-like "do" construct so they can get
imperative sequentiality back. Is that right?

I'm not a huge fan of extensible languages. I've had to debug LISP code
written by others.

~~~
comex
> The functional people have been able to hammer Rust into a functional
> language, sort of. Now they want a Haskell-like "do" construct so they can
> get imperative sequentiality back. Is that right?

Not really, in that even before the blog gets to do notation, it depends on
features that don't exist yet: generic associated types, which are RFC-
accepted but not yet implemented, and associated traits, which haven't even
been proposed. For now the whole thing is just a sketch. However, since
associated traits aren't critical to the design (and would be a good feature
to add anyway), at least some version of it will presumably be implementable
in the future.

Overall, Rust has always been inspired by functional languages to some extent,
e.g. Rust traits are based on Haskell typeclasses, and Rust has pattern
matching and full type inference. But I don't see `Monad` ever becoming
idiomatic. I also don't see `do` notation being accepted, because its use
cases largely overlap with other features, namely async/await and generators.

On the other hand, it would require only minor changes to generators to allow
implementing `do` notation on top of them, something I'm highly interested in
mainly just because I want to prove it's possible. ;p

------
steveklabnik
Please note that these are musings about possible language extensions that
would let us express monads; this is not even at the stage of a proposal yet,
and we haven’t all agreed that enabling this kind of code is actually useful.

~~~
chenglou
Where would you say that Rust core folks stand, regarding usage/overusage of
such patterns? Sugar for common unwrappings?

Appreciate the pragmatism =)

~~~
steveklabnik
The lang team would be in charge of these kinds of features, and I'm not on
it, so I can't totally say. There are certainly members who do want this
stuff. However, community sentiment is all over the map.
[https://www.reddit.com/r/rust/comments/b6bu2c/idiomatic_mona...](https://www.reddit.com/r/rust/comments/b6bu2c/idiomatic_monads_in_rust_a_pragmatic_new_design/)
is the reddit thread on this post, you can see a number of different opinions
expressed there.

I personally want to see examples of real Rust code that has a problem that
this solves. I get monads, I like monads in Haskell, but I'm unconvinced that
the additional complexity is worth it in Rust.

~~~
ldng
Well, I hope they don't. Functional extremism killed Scala for the wider
public (IMHO)

~~~
nine_k
I'd say that _not enough_ functional extremism killed Scala. It tried to be
all things for all purposes, compatible with every approach. So it grew into a
incomprehensible monster of a language, full of subtle traps, like C++.

Making things compact and focused helps. Of mixed-approach languages, I see
Kotlin's approach to integrating FP as more successful.

~~~
ldng
I'm sorry, I respectfully disagree on the "not enough" :-)

I think it is a matter of choice of audience. If you want pure functional
languages, there are plenty that will rock your boat (Haskell, OCaml ...).

But, to my mind, I see Rust as a somewhat "modernised" C/C++ with, like
Python, the most interesting _and_ accessible parts of FP backed in. It is a
balance and it is fragile. So, as I said, I hope that Rust will keep that fine
line.

All that said, it only is my opinion, I might be wrong and the wider audience
might be ready for "harder" FP. But I doubt it.

~~~
nine_k
Rust not only has some good parts from FP; they are added thoughtfully enough
to keep them logically sound. So the fact that there are math-backed theories
behind them still matters.

With C++, I can't say the same is true. Some libraries try to alleviate that,
of course.

~~~
rujuladanh
What math-backed theories? Why do they matter? Do you mean for the compiler
team or for the users?

~~~
nine_k
Hindley-Milner type inference and linear types (used indirectly for lifetimes)
spring to mind.

~~~
rujuladanh
Those are useful for compiler devs and language designers, not so much for the
average user of the language.

------
mruts
I'm not that familiar with Rust, but what is the rational against higher-
kinded types? Is it a technical limitation that everyone agrees is a
limitation, or is there some rationale why higher-kinded types in Rust are not
useful?

This is probably not relevant, but as a professional Scala programmer, I find
higher-kinded types absolutely indispensable. Without them, writing truly
generic (and imo, beautiful) code is almost impossible. Programming with
higher-kinded types does really feel like some "next level shit." higher-
kinded types are to types as first-order functions are functions: the natural
and logical extension that unlocks simplicity, genericity, and beauty.

~~~
steveklabnik
> what is the rational against higher-kinded types?

There isn't so much a rationale against them, but rather, nobody has put
forward a coherent proposal _for_ them. Changes only happen when proposals are
made and accepted, and there's never really been one for HKT.

That said, there are arguments that apply fairly generally that would apply
there too, namely "is the additional complexity worth it"?

------
tempodox
If taking monads to Rust takes such convoluted code, my conclusion would be
that it probably isn't a good fit. Compared to this, Haskell is a breeze.

~~~
empath75
Implementing monads will be convoluted, using them is relatively easy. It
wouldn’t change any of the existing code, but it would allow you to write a
function like this:

fn double_inner<M: Monad<u64>>(m: M) -> M { m.bind(|x| M::unit(x * 2)) }

Which takes anything wrapping an integer and doubles it, whether it’s an
option or result or iterator.

~~~
uryga
not sure if this is the best example – `Functor` would be enough here

~~~
empath75
well, as neither exist currently it doesn't matter much does it?

------
leshow
Does any of this actually compile? Even with generalised_associated_types
enabled I couldn't get the Functor definition to compile with a `trait
MapFn...`

edit: If the main point of the article is to show that 'monads are feasible in
rust' shouldn't you not be assuming a bunch of language features exist that
don't?

~~~
coldtea
>*edit: If the main point of the article is to show that 'monads are feasible
in rust' shouldn't you not be assuming a bunch of language features exist that
don't?8

Except if the main point is "monads are feasible in rust if we add these
features".

~~~
blattimwind
Rust really needs to watch out to not further increase the complexity of the
basic language.

------
chmln
Call me closed-minded, but I seriously hope something like this never makes it
into Rust. Not only is the complexity overbearing, but the motivation for this
solution is rather thin.

------
Ericson2314
Rust should get Higher kinded types so these experiments can come up with
something practical.

------
mesarvagya
I am not sure about whether the functor definition in article is correct or
not.

Isn't it supposed to lift function as below:

class Functor f where map :: (a -> b) -> f a -> f b

instead of: class Functor f where map :: f a -> (a -> b) -> f b

~~~
ivanbakel
The definitions are identical. You could define one in terms of the other by
just reversing the arguments.

~~~
mesarvagya
The first definition is says it takes a normal function and returns a function
in category f. But second definition says it takes a value fa and a function
and return fb. Does the order of defining arguments matters or its just a
taste of matter? Thanks.

~~~
ivanbakel
Don't make the mistake of thinking that translating a type signature into
written English gives a canonical description of the function. The first
definition says "(a -> b) -> f a -> f b" \- that is it.

You can translate one into the other easily

    
    
        map' :: (a -> b) -> f a -> f b
        map' i j = map'' j i
    
        map'' :: f a -> (a -> b) -> f b
        map'' m n = map' n m

~~~
majewsky
Or, more succinctly,

    
    
      map' = flip map''
      map'' = flip map'

------
ainar-g
One question I almost never see addressed with monads is the question of the
stack. The stack is not endless, and may actually be quite small on some
devices. How is that fact reconciled with the fact that do-notation is
essentially

    
    
      doStuff >>= (\a ->
        doMoreStuff >>= (\b ->
          doEvenMoreStuff >>= (\c ->
            -- Ad nauseam.
          )
        )
      )
    

Depending on the data being transferred, that looks like a lot of stack to me.

~~~
_hardwaregeek
Couldn't you apply standard tail call optimization and turn it into a
trampoline?

~~~
ainar-g
That is assuming that your compiler knows about such optimisations and can
apply them in non-recursive situations. As far as I know, Go doesn't apply
tail-call optimisation. Haskell probably does. Not sure about Rust.

Either way, depending on the compiler making one particular optimisation just
so that your code works seems rather fragile to me.

~~~
eslaught
There are languages where TCO is defined as part of the language spec,
specifically to give programmers permission to write this sort of code. Some
Lisps (Scheme, I think) and Lua work this way. TCO is a relatively well-
understood optimization that isn't prone to being brittle the way e.g. auto-
parallelizers or auto-vectorizers can be. So it's less fragile than you'd
think.

~~~
eridius
TCO is a lot easier to do in a garbage-collected language. In particular, with
Rust, having any live stack value that conforms to Drop will break the ability
to do tail-calls.

~~~
0815test
You could have a trait similar to Copy that forces Drop to be a no-op, and
require live values that don't conform to it to be moved into any `become`.

~~~
eridius
Or just skip the trait and require there to be nothing live on the stack that
conforms to Drop after a `become`.

In fact, the main reason to have a keyword like `become` is to signal to the
compiler to do this kind of analysis, because it's so easy to accidentally
break the ability to do TCO when you can't lean on a GC to clean things up
later.

------
eatbitseveryday
This article might do better by actually explaining what a monad is in
layman’s terms, rather than deferring the reader to Wikipedia via a footnote.
I read 1/3 of the article hoping to gain an understanding but gave up and
didn’t read further :(

~~~
ivanbakel
That's because it's a technical article about monad implementation, building
on previous technical discussion. The author could spend time explaining what
monads are, but that's a notoriously tricky thing to teach, and if you don't
understand them fully, the article as a whole won't be worth anything to you
anyways.

~~~
eatbitseveryday
Perhaps the author could have put such a disclaimer? "Note: it is beyond the
scope of this blog post to explain what a monad is. Please refer to x,y,z for
background material."

Anyway, I understand.

~~~
Certhas
The article says in the introduction:

> I am going to be assuming some familiarity with monads (and to a certain
> extent, do notation). There are enough introductions to monads out there as
> it is.

------
b0rsuk
What would performance be like compared to imperative style i/o ?

~~~
Buttons840
Monads are not about I/O.

~~~
b0rsuk
Parent comment is not about answer.

------
truth_seeker
Wow. Does that monad abstraction also cost free ?

~~~
lucozade
He only suggesting adding associated traits to trait definitions from what I
can see.

So there shouldn't be any extra abstraction cost. And any actual cost
shouldn't be any larger than it is for traits generally.

I believe most of the controversy in this space is about language complexity
cost.

~~~
devit
Compilation time is the other potential issue, although it's hard to predict
it.

------
misnome
Mods: Suspecting the fragment part of the URL wasn't meant to be submitted
here? It jumps to some nonsensical part of the page...

~~~
sctb
Removed. Thanks!

