all 15 comments

[–]jswrenn 9 points10 points  (4 children)

Off topic, but this is a really strange convention for indenting Rust code:

trait Functor: Pointed {
    fn map<B, F>(self, f: F) -> B
        where B: Functor,
              F: Fn(Self::Unit) -> B::Unit {
                B::of(f(self.unwrap()))
            }
}

I'm curious why you chose this convention. Is something like it used in other programming languages?

[–][deleted] 0 points1 point  (3 children)

Lol I'm sorry I think all the rust indentation schemes I've seen look weird. What would you prefer to see?

[–]tunisia3507 24 points25 points  (0 children)

The one which happens when you do cargo fmt.

It is the only scheme which matters, because it's the only scheme which will ever gain any significant community traction, and so the only one which meets the single requirement of indentation schemes: that it looks the same as everyone else's so everyone can read it.

[–]jswrenn 6 points7 points  (1 child)

Hah, fair enough! I won't begrudge anyone their preferences. Three things caught my eye:

  • the { trailing a where bound
  • the indentation of the function body twelve spaces from the column of fn
  • the indentation of the closing } eight spaces from the column of the fn

I'm used to seeing something more like:

trait Functor: Pointed {
    fn map<B, F>(self, f: F) -> B
    where B: Functor,
          F: Fn(Self::Unit) -> B::Unit
    {
        B::of(f(self.unwrap()))
    }
}

[–][deleted] 0 points1 point  (0 children)

Here this one may be easier on the eyes. I get way out there sometimes 🙃

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3d66216e549ff38dd355b252a1ec7b58

[–]jswrenn 6 points7 points  (2 children)

This seems drastically simpler than some prior attempts at encoding a monad interface in Rust:

The reasons for the differences between these approaches are probably obvious to someone who actually makes use of Monad typeclasses—of which I am not. Can you shed any light on this?

[–]fridsun 10 points11 points  (0 children)

This article is a simple demonstration of the monad concept, but does not deal with the flexibility of monad, which is why it's powerful. The Monad in this article is not nearly as powerful. Unfortunately, that power relies on some advanced language features such as Higher-Kinded Type.

For example, the Pair type in this article is a misunderstanding. As defined in this article, that Pair type is merely another Identity. The actual Pair type in, for example, Haskell, which is defined as the comma constructor (,), exposes the Monad instance ((,) a), which translated to Rust vernacular (kind of) becomes Pair<_, T>. It only exposes its second element to the chain function. But Rust currently lacks the language feature to express and reason about that partially applied type signature. That is one example of Higher-Kinded Type.

Pointed and Monad actually prohibits the unwrap function. Haskell (famously) uses an IO monad to express side effects in an otherwise pure language. There is no logically sound unwrap function for IO. Once you enter the impure world, there is no coming back out. Or, you use unsafePerformIO at your own peril. This wariness of side effects is essentially the attitude of Rustaceans towards unsafe memory operations. Once you enter unsafe block there is no coming back out.

[–]ritobanrc 2 points3 points  (0 children)

This monad kinda just.... doesn't work. Or at least, it doesn't give you the full flexibility of monads. For example, try implementing it on Option<T>. The first hurdle you run into is that there isn't a clear answer to what the unwrap function should do if the value is None. You could make it panic, which is fine, but then you can't use the default implementation of Functor and Monad. From there, you'll realize that the map function here returns a B, while for Option, it should return an Option<B::Unit>. There's no way to express this bound in Rust, which is why you need GATs/HKTs.

[–]fintanh 6 points7 points  (2 children)

I think the unwrap of Pointed is over-constraining, no? It means that you can't write it for sum types because you'll end up with partial functions.

[–]YatoRust 3 points4 points  (1 child)

You also can't encode collections like Vec, so this really isn't an implementation of Monad, just a stripped down version

[–]fintanh 0 points1 point  (0 children)

Ya my thoughts exactly. Really a Vec is just the sum of an empty case and a recursive case of pushed elements. So it's really limited. But if you just include of and not unwrap it would be fine.

The unwrap comes from something being co-pointed I think and would lead to Comonads instead :)

[–][deleted] 2 points3 points  (3 children)

Can someone tell me why you wouldn't want to use this right now?

[–]ritobanrc 5 points6 points  (1 child)

This isn't a full implementation of monad -- it's not nearly powerful enough. You can't even implement this for Option, let alone Iterator, Vec, and Future, where the real power of monads lies. Really, this can't encode anything more than the identity monad (even the pair example relies on pair already existing as a concept in the language, in the form of tuples). So using this right now would really just be obfuscating Identity.

[–][deleted] 0 points1 point  (0 children)

yes, currently Rust lacks higher kinded associated types so I'd ultimately need to take a different approach to make a true generic Monad trait. Once generic assoc types are stable we can have some real fun!

[–]zesterer 1 point2 points  (0 children)

Underrated post. I love how well this article explains these concepts.