4 ms·
Some thoughts based on reading the README: * I'm not sure if the "challenge" approach will work. I think it's good to demonstrate things to students before let
by noelwelsh 3y ago
Some thoughts based on reading the README:
* I'm not sure if the "challenge" approach will work. I think it's good to demonstrate things to students before letting them loose. I strongly agree that process (e.g. knowing how to find information) is really important and under-taught. I'm not sure that the approach here will be sufficient scaffolding for students to make progress.
* I'm not convinced that monads are really all that hard. I'm mostly a Scala developer. We used to have a lot of monad tutorials at Scala conferences but not so much any more. I feel the baseline level of knowledge has moved on, just like you don't need to explain first-class functions to most developers today but it was very necessary 20 years ago. I think once you start using `flatMap` / `bind` it makes sense fairly quickly. `IO` is a little bit harder because it wraps up the interpreter pattern (distinction between description and action) in a monad, so there are two distinct concepts at work. However, my feeling about monads is that they are either deeply profound or trivially simple (it's just a useful API!) and too much time is spent on deep profoundity when what beginners need is just the trivially simple.
* It's always good to have different ways to approach a problem. What works for one person might not work for another. While some books & tutorials are better on average than others, I've never found one learning resource that works for everyone.
- rbonvall 3y agoPeople seem to do fine dealing with iterators—which are defined by their operations (hasNext, next) and are convenient because they have a dedicated syntax (foreach)—without the need for enlightenment from esoteric "iterator tutorials". Monads are defined by their operations (flatMap/bind, unit) and have a dedicated syntax (do). I agree with you: just get used to flatmapping stuff and the concept will sink in.
- taeric 3y agoAssuming you mean iterators in other languages, I have seen folks bit by iterators supporting the "member" functions. That said, agreed that standard use is mostly fine. The problem with monads, in this regard, is folks try and bend everything to them. I've seen similar with "everything can be iterated" before, and it ended as poorly as you might expect. The basic trap comes down to the fact that many of us (yes, I'm projecting), really only understand basic algebra as defined with addition and multiplication. But a basic understanding doesn't help you see how that will abstract over working with other real life things. And as soon as you have to start dealing with non-transitive and non-associative things, the basic understanding can hurt.
- sixo 3y agoThe problem with monads is just the words. "bind" in particular is a bad term!
- sdeframond 3y agoIt make sense in some contexts: it binds the value produced by the monad to a new name. For example, it binds (or "assigns") the value read from a file to the variable x. I agree that `flatMap` is clearer when manipulating lists, which is probably a better first approach than the IO monad. However `flatMap` would sound weird for IO monad, I think. What I find confusing is using the do notation for monads with very different meaning. Code looks the same but it does something totally different! I wonder if putting the Monad interface ("typeclass") front and center in Haskell is such a good idea. Kinda feels like premature abstraction. We should use descriptive names instead of opaque abstractions. ...or maybe I haven't seen the light yet.
- kagakuninja 3y agoI'm using Scala syntax here... If I have: val a = IO(42) def f(n: Int) = IO(n) And I use map, then I get: val r: IO[IO[Int]] = a.map(f) Which we don't want... the solution is to flatten it. This can be done with flatten, or flatMap: a.map(f).flatten a.flatMap(f) Both produce IO[Int], and this makes sense to me using any monad. By contrast, bind means nothing to me.
- corethree 3y ago>Monads are defined by their operations (flatMap/bind, unit) This is why everyones intuition screws up. It's better to explain monads in terms of the Kliesli operator (>=>). Monads like functions in functional programming are about composition and the explanation should center around that. The right intuition about monads is that they are higher level functions that like regular functions can compose. Bind by itself feels weird and sort of pointless for a newcomer as he usually thinks why does this sort of strangely specific operation need to be abstracted behind a name "monad"?
- foldr 3y ago>`IO` is a little bit harder because it wraps up the interpreter pattern (distinction between description and action) in a monad I've always thought that the trick to explaining the IO monad in Haskell is just to realize that IO in Haskell has very little to do with monads. Fundamentally, Haskell values of type (IO a) may have side effects when evaluated, the evaluation semantics of Haskell are such that expressions are (observably) evaluated at most once†, and the >>= operator forces sequential evaluation by threading through a hidden world parameter and doing some special compiler magic. (It's a common myth that sequential evaluation is forced merely by the type of >>=, but this is just false.) Once you've grasped this underlying mechanism, you can then observe that 'return' and >>(=) form a monad. † This is subtle and somewhat beyond my pay grade. Strictly speaking the semantics of Haskell only require non-strictness, which doesn't directly entail any limit on the number of times an expression is evaluated. But in practice, it is safe to assume that a Haskell implementation will evaluate any expression with side effects at most once. I'm sure someone else can do a better job of explaining exactly to what extent, if any, this property is entailed by a non-strict semantics.
- agentultra 3y agoThe trick with IO was basically putting a monotonically increasing counter in it. Binds increment the counter creating the implicit ordering dependency needed for the compiler to "sequence" the actions as expected. It's supposed to be some elegant design handed down from an all-knowing creator I guess, but it's a useful hack in practice.
- foldr 3y agoYes. You also need something to force strictness and disable certain potential optimizations, though. The counter isn't actually used, so the compiler ought to be free just never to evaluate it, or delete it altogether.
- corethree 3y agoflatMap is the worst way to explain monads. People associate mapping with iteration and a monad is a much higher level concept that is hard to associate. Also Bind or the >>= operator is the worst way to explain monads. Flatmap is a synonym for Bind which makes everything even more confusing. If your intuition around monads centers around the >>= operator then your intuition is incomplete. This infatuation with BIND as the central concept around monads is what screws everything up, it leaves people with broken intuition about monads and it also makes learning 1000x harder. Better to explain monads in terms of functions. A function (F) is something that takes an input A and returns an output B. F(A) -> B You can compose functions. Combine 2 functions into one function to produce a bigger function so long as the types match. We often overload an operator for function composition: F(A) -> B F2(B) -> C F3 = F2 * F = compose(F2, F) compose(F, F2) = func(A) -> C {return F2(F(A))} You can create bigger functions by composing lots of tiny functions F5 = F4 * F3 * F2 * F A monad is simply a special type of function that returns something in "addition" to the return type. One way to think of it is it's like Golang where the function returns an additional error value, another way to think of it is it returns the function as a list with additional values appended. normal function: F(A) -> B monad: M(A) -> (B, error) Nothing crazy here, M is a monad we can give it a name call it the "golang error monad." Why does this matter? Well For one thing. Monads should be able to compose like functions. You should be able to: M5 = M4 * M3 * M2 * M1 How would that work? Imagine two monads: M(A) -> (B, error) = func(A){return (A, nil) if A.correct() else (nil, error.New()) } M2(B) -> (C, error) = func(B){return B, nil) if B.correct() else (nil, error.New()) } M2 * M - compose(M2, M) compose(M2, M) = func(A){ B, err = M(A) if err { return nil, err } else { return M2(B) } } It's monads can be composed like functions it's just a little different. For the golang error monad as soon as one monad in the composition chain returns an error just make the whole thing an error. In haskell the compose operator for monads is F1 >=> F2 for functions its F1 . F2 So that's all it is. Functions should have the property to compose. Monads should be like functions with the results wrapped in a burrito, but they also should be able to "compose" as well. It's like a higher level version of a function. That's all. Typically in languages you create functions by defining functions. Normal. In languages with first class support for monads, you define the monads like functions, BUT after that you HAVE to define how the monads compose. That completes the definition of a monad and function. Functions need to be defined in terms of how they compose as well, but you typically don't do this in other languages as those languages just have you do it explicitly F(A) = F1(F2(A)) or they have the compose operator predefined.