logoalt Hacker News

jerflast Monday at 2:57 PM1 replyview on HN

    Result<User, Error> result =
        ParseId(inputId)
            .Bind(FindUser)
            .Bind(DeactivateDecision);
This does not implement monads as Haskell has them. In particular, Haskell can do:

    do
       id <- ParseID inputId
       user <- FindUser id
       posts <- FindPostsByUserId id
       deactivateDecision user posts
Note id getting used multiple times. "Monad" is not a pipeline where each value can be used only once. In fact if anything quite the opposite, their power comes from being able to use things more than once. If you desugar the do syntax, you end up with a deeply nested function call, which is necessary to make the monad interface work. It can not be achieved with method chaining because it fails to have the nested function calls. Any putative "monad" implementation based on method chaining is wrong, barring some future language that I've not seen that is powerful enough to somehow turn those into nested closures rather than the obvious function calls.

I wrote what you might call an acid test for monad implementations a while back: https://jerf.org/iri/post/2928/ It's phrased in terms of tutorials but it works for implementations as well; you should be able to transliterate the example into your monad implementation, and it ought to look at least halfway decent if it's going to be usable. I won't say that necessarily has every last nuance (looking back at it, maybe I need to add something for short-circuiting the rest of a computation), but it seems to catch most things. (Observe the date; this is not targeted at the original poster or anything.)

(The idea of something that can be used "exactly once" is of interest in its own right; google up "linear types" if you are interested in that. But that's unrelated to the monad interface.)


Replies

louthylast Monday at 3:07 PM

In C# you can implement SelectMany for a type and that gives this:

    from id    in ParseId(inputId)
    from user  in FindUser(id)
    from posts in FindPostsByUserId(id)
    from res   in DeactivateDecision(user, posts)
    select res;
It is the equivalent to do-notation (was directly inspired by it). Here's an example from the language-ext Samples [1], it's a game of 21/pontoon.

> I wrote what you might call an acid test for monad implementations a while back: https://jerf.org/iri/post/2928/ It's phrased in terms of tutorials but it works for implementations as well; you should be able to transliterate the example into your monad implementation, and it ought to look at least halfway decent if it's going to be usable.

If I try to implement the test from your blog with Seq type in language-ext (using C#), then I get:

    Seq<(int, string)> minimal(bool b) =>
        from x in b ? Seq(1, 2) : Seq(3, 4)
        from r in x % 2 == 0
                      ? from y in Seq("a", "b") 
                        select (x, y)
                      : from y in Seq("y", "z")
                        select (x, y)
        select r;
It yields:

    [(1, y), (1, z), (2, a), (2, b)]
    [(3, y), (3, z), (4, a), (4, b)]

Which I think passes your test.

[1] https://github.com/louthy/language-ext/blob/main/Samples/Car...

show 1 reply