7 ms·
Snake-fury – a challenge for Haskell beginners
- noelwelsh 3y agoSome 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.
- Joker_vD 3y agoFrom skimming the code, this honestly seems to be designed in the way that begs to be written down in Erlang, not Haskell.
- supermatt 3y ago> now you know what a monad is... therefore you will be unable to explain to anyone what a monad is Isn't a monad effectively just an abstract wrapper for some properties that you can apply sequential transforms on? Why do people keep making out they are some form of voodoo?
- lgas 3y agoNo, they are not wrappers. I know what they are though, so I won't be able to explain them to you.
- noelwelsh 3y agoThat's definitely a sufficient understanding to make progress with. It doesn't describe every monad but it describes the ones you will commonly meet when programming, and it provides enough understanding to scaffold further understanding of the more abstract concept. A lot of the "it's so hard" BS is from people who think you have to understand a concept in its full generality in one shot. That's definitely not how effective learning works in most cases.
- bowsamic 3y agoBecause, that's nowhere near general enough to describe what monads can do, that is, your explanation is wrong because of its over-specificity. That's the problem with monads. They are an extremely general mathematical object, but the specific uses of them are wildly different. It's the exact same "problem" with functor and applicative. It's like explaining a vector space using the analogy of arrows in space and then having to explain how sets of functions are also a vector space. In mathematics you just have to stick with the general definition and a few examples, keeping in mind that those examples are specific. Most programmers are simply not used to thinking in that way. That's why it's "some form of voodoo". Further, much like the definition of a vector space does not make it immediately obvious that functions can be vectors, it's not obvious from looking at the definition of a monad that it allows us to do sequential IO. That's why people think it's some sort of voodoo. You have to just accept that the example obeys the definition, the example doesn't fall out of the definition.
- 3y ago
- rebeccaskinner 3y agoI think this is a great idea. I love working with haskell and I’m always excited to see more opportunities for people to learn it in ways that work for them. I learned Haskell mostly through building things without a lot of structured reading, and it can be a rough path but I think it can work out in the end for sufficiently motivated people.
- marai2 3y agoI have read many a monad tutorials. I'm reading through your new book right now and the chapter on IO is one of the best onramps I've read on building up intution for Monads! Writing a book is hard work. Thank you for all the hardwork you put in writing this book, it's great! (I haven't gotten to the Monad Transformers yet in the book, I hope your book can finally help me get over the hump in understanding them :-)
- rebeccaskinner 3y agoThanks! I spent a lot of time trying to get the introduction just right in a way that was practical and accessible, I’m really happy to see it’s been landing very well with people.
- jdeaton 3y agoThe haskell learning-curve graph seems quite accurate. I'm over in the "happy pythonistas" camp.