12 ms·
Functors, applicatives, and monads in pictures
- jerf 13y agoThis has one of the usual dangers of trying to do a visualization of a monad, which is that it strongly implies that there is a one-to-one relationship between what you put in and what comes out, which is basically saying that the bind function of the monadic interface implementation will only call the function it receives once. This is not true; it may be called zero times (Nothing, empty list), once (Just something, a list of one element), or many times (once for each element in the list for the list monad, for instance), which is a critical element to understanding the monad interface. So, you know, be aware of that I guess.
- tellarin 13y agoFrom my point of view the guy does a good job of showing the three cases. Would you have a specific suggestion to improve his presentation?
- egonschiele 13y ago/r/haskell has already discussed this at length: http://www.reddit.com/r/haskell/comments/1co0s5/functors_applicatives_and_monads_in_pictures/ http://www.reddit.com/r/haskell/comments/1co0s5/functors_app... And I've given Maybe's Monad instance definition as well as explained it in pictures. If there's something missing please let me know specifically.
- gioele 13y agoHave we got a new _why?
- obviouslygreen 13y agoIf so, let's hope it's more content and less drama... and please, let's concentrate on the content and not some odd analogy that will only take away from said content.
- gioele 13y agoMine was a compliment, not only an analogy. The tech field needs people that are both confident with words, fun and have a deep understanding of the inner details of things. _why was such a writer, let's hope Adit (correct?) is another. Regarding form vs. content, I think that the HN community enjoy discussing form as much as content. Most of the people here are interested in design or typography, things that more close to form than to content.
- obviouslygreen 13y agoI agree that the tech community could benefit from more well-versed people with insight into our tools and paradigms... however, I don't agree that _why is such a person. He openly claims to be a poor programmer, and as an artist... well, my contention is that he appeals to a very limited audience. That's not a criticism, simply a suggestion that while he may be valuable in an artistic sense to some people, he's not contributing to the "tech community" in a meaningful way. By which I simply mean that he should not be a yardstick by which we measure anyone. Let him be himself. Let the rest of us be ourselves. Let that be good enough.
- steveklabnik 13y ago> Let him be himself. Let the rest of us be ourselves. Let that be good enough. _why even said this himself: > caller asks, “should i use hpricot or nokogiri?” if you're NOT me: use nokogiri. and if you're me: well cut it out, stop being me.
- obviouslygreen 13y agoHard time arguing with that. :)
- rat87 13y agoLearn You a Haskell for Great Good! (http://learnyouahaskell.com/chapters http://learnyouahaskell.com/chapters) is one of the better gentle into to haskell guides. I think it started attempting to be sort of similar to why's guide but ended up being a lot more straightforward although there are still some whimsical examples and pictures.
- seldo 13y agoI reluctantly confess that, knowing nothing about Haskell, I was lost by the 4th diagram. Presumably if you know Haskell, the line "data Maybe a = Nothing | Just a" makes sense, but I can't parse it.
- ominous_prime 13y agoI don't know Haskell, but it seems pretty clear: "something called a Maybe named 'a'; is equal to Nothing, or just 'a' itself" (hmm, I'm not sure that's a whole lot clearer :-/ )
- shoo 13y agoThe variable 'a' there is a placeholder for a type. e.g. loosely translating the idea of Maybe to C++ syntax (perhaps more familiar?) might give something resembling: Maybe<int> x = Just<int>(30); Maybe<int> y = Nothing<int>(); x and y are values of type Maybe<int>. I.e. we've plugged in the type int for the type variable a. Given an arbitrary value of type Maybe<int> you have two cases to consider - either it is a "Nothing" which contains no further data, or it is a "Just<int>" which contains an int value. You can think of the type Maybe<int> as being a "nullable int" type or perhaps a special collection of ints that can only contain 0 or 1 int value. (beware: my knowledge of both haskell & c++ is not fantastic)
- chongli 13y agoThis just defines a new type (called Maybe). The Maybe type can either contain a value or Nothing. This is useful in Haskell because regular values cannot be null. When a function has the potential to fail, it can use one of these Maybe types as its return type to let the caller know of (and handle) the possibility of failure. As for the syntax itself? It's called a tagged union. Just and Nothing are defined to be constructors for building values of type Maybe a. Just takes one argument (of any type) and Nothing takes no arguments. Just "hello" This has type Maybe String. Nothing This might also have type Maybe String (or it might not). A function which accepts a Maybe String as one of its arguments will accept Nothing as well.
- grn 13y ago
- babesh 13y agoStill have not encountered a good explanation of monads although there seems to be a new try on Hacker News every couple of weeks. Wonder if/when it will reach mainstream and become part of the common vocabulary of programming. Wonder if a limited syntax would help. Maybe a better example than the common 'or nothing' would help in this case.
- dustingetz 13y agoI just coded this today, do you think this is a decent example? Uses monads to do logging around I/O that can fail, in such a way that your logs across multiple threads are guaranteed not to interleave. The code can be improved in many ways, this is a first pass "let's make this compile to prove it works". https://gist.github.com/dustingetz/5606201 https://gist.github.com/dustingetz/5606201
- lbrandy 13y agoI just worked my way through learning-myself-a-haskell yesterday, so this is timely. And I agree with your criticism. The `Maybe` example is simple, and helps with drawings, but it really lacks motivation. I understand all the words, and all the definitions, but it doesn't really explain what problem we've solved. The payoff of the article is, I think, supposed to be: getLine >>= readFile >>= putStrLn Which looks great. But what I'd like to see is a longer explanation of why this is better than the alternatives (like simple function composition).
- egonschiele 13y agoPost author here. You're asking the right questions! There's a tutorial already that does this very well: http://blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html http://blog.sigfpe.com/2006/08/you-could-have-invented-monad... My goal was just to provide a gentle introduction.
- Peaker 13y agoSimple function composition composes functions. getLine, for instance, isn't a function. There is a big problem that "Monads" can refer to the generalization and combinators that work with any Monad, or to specific Monad types and the style of composition that they choose to compose values together. Monadic IO is a solution to combining referential transparency and effectful programs. Moands in general is a solution to defining the same combinators over and over for each Monad.
- balloot 13y agoYikes. That might as well be titled "Why functional languages will never catch on." It took something dead simple (a function that adds 3), and made it insanely complicated. Why would anyone choose to deal with that?
- seldo 13y agoThey chose a deliberately over-simplified example in order to demonstrate the core concepts; I don't begrudge them that (nobody needs a program that prints out "Hello World!"). The audience level of this piece is confusing -- it looks like a friendly, cartoony introduction for a general audience, but it actually assumes you already know Haskell. Basically, you and I are not the intended audience.
- egonschiele 13y agoRight. I'm hoping to make monads easier to understand, but you need some knowledge of functional programming to build on. If I had tried catering to the complete beginner, this post would be much longer and not as focused :)
- seldo 13y agoThat's fair enough! Perhaps a sentence at the beginning to establish your goal would prevent comments from confused clueless people like myself.
- egonschiele 13y agoDone!
- chii 13y ago> They chose a deliberately over-simplified example in order to demonstrate the core concepts; i wish people who are writing guides on these topics pick a non-simplified situation where using this method makes for an easier program (vs using the "traditional method"). The comparison and contrasting of a functional/monadic approach vs a procedural approach in a complex scenario might actually shed more light - at least, for some one who is familiar with the procedural approach.
- GhotiFish 13y ago> (+) <$> (Just 5) Just (+5) > Just (+5) <$> (Just 4) ERROR ??? WHAT DOES THIS EVEN MEAN WHY IS THE FUNCTION WRAPPED IN A JUST A functor takes a function, and applies it to the inside of a context, but all of a sudden now a functor can't take a function and apply it to the inside of a context. No, that will never make sense. Sorry. Haskell is hard. edit: oh, it's in a just value, I didn't notice that the first time I read it, for... some reason.
- deleted 13y ago[deleted]
- usea 13y agoI stumbled on the exact same thing. Eventually I just moved on without knowing what I was missing. Your post pointed it out to me, thanks.
- antninja 13y ago"Just (+5)" is not a function. This would have worked with: (+5) <$> (Just 4)
- tel 13y agoOr Just (+5) <*> Just 4 as it turns out.
- chongli 13y ago>A functor takes a function, and applies it to the inside of a context, but all of a sudden now a functor can't take a function and apply it to the inside of a context. Where you're getting mixed up is in the distinction between Functors and Applicative Functors. You can think of an Applicative Functor as one which allows you to apply its contents to other things. One function which must be defined for all Applicative Functors is: (<*>) :: f (a -> b) -> f a -> f b Looking at the type signature, this is a function of two arguments. The first argument is an Applicative Functor containing a function. The second argument is the same functor containing a value to which that function may be applied. What does this function do? It simply applies the function and returns the result, wrapped in the same Functor. Thus: Just (+5) <*> Just 4 >>> Just 9 This is very much analogous to typical function application: (+5) 4 >>> 9 Which is why the function (<*>) is usually pronounced "ap" or "applied over".
- benched 13y agoThe pictures look cute and friendly, but to me it read like a series of non-sequiturs or deus ex machinas. I don't know Haskell.
- alok-g 13y agoPosted earlier here: https://news.ycombinator.com/item?id=5575334 https://news.ycombinator.com/item?id=5575334
- DanWaterworth 13y agoI think that many monad tutorials, including this one, fall into the same trap. They explain monads without giving any motivation, or when they do, it seems like monads only exist to get around problems caused by being pure. When you program in normal languages, you have the ability to perform effects all of the time. You can always read/write to/from files, you can always raise exceptions or otherwise change the control flow. There's nothing you can't do. Haskell is different. In Haskell, by default, you don't have any effects at your disposal at all. You can create values from other values, that's it. When you want to program with effects, you use a monad. The type of monad that you use decides the effects that you are allowed. For example, the maybe monad instance gives you the "fail without an error effect". The either monad gives you the "fail with an error effect". The list monad instance gives you the non-determinism effect. etc. You might expect me to say that IO gives you the "interact with the outside world effect", but that's not really true, forget what you know about IO for the moment. The continuation monad instance is a bit special. In a sense, it is the most general of the monad types, because every monad instance can be seen as a specialization of it. The continuation monad allows every kind of effect. At this point, you might be thinking, "but how can this be, it doesn't allow IO". To reconcile these facts. I present the coroutine monad. It has an effect, yield, that gives a value to the caller and waits for the caller to respond with a value so that it can continue. Here are some types: runCoroutine :: Coroutine i o x -> Either (o, i -> Coroutine i o x) x yield :: o -> Coroutine i o i So running a coroutine either produces a value of o and function to resume the coroutine or it completes with a value of x. IO is very similar to the coroutine monad. When a Haskell program gets to a function that talks to the outside world it yields control to the runtime environment, the runtime environment does the actual IO and passes the result back so that the program can continue. This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done. So why are monads useful? They allow you to restrict the effects that are possible in different parts of your programs. This aids in reasoning about your programs, because you don't have to think about whole classes of behaviour, but they're less useful in other languages where you already have effects everywhere.
- zohebv 13y agoI don't know how else to put it, but at least half of what you have written above is completely wrong. > but they're less useful in other languages where you already have effects everywhere. Scala has side effects everywhere, yet the Monad is extensively used in Scala programs. Monads have nothing to do with "effects". Monads are not used to decide the kind of "effects" you are allowed. By calling every Monad a kind of "effect", you are just begging the question. Monads are merely objects that follow 3 monad laws, no more no less. > This is why Haskell is pure despite having effects. Haskell programs don't do IO, they yield control to the runtime environment whenever IO needs to be done. How is this different from a C program? Haskell is pure in the presence of IO, because the type system tracks IO calls and forces any function that even indirectly refers to a quantity obtained via IO to modify its type signature to reflect the IO action. This can be done with/without Monads. Haskell IO is a Monad, because it pretty much analogous to the State Monad and Haskell has special Monad syntax that lets you pass the World State implicitly and write imperative code in an imperative style.
- virtualwhys 13y ago"Bill O’Reilly being totally ignorant about the Maybe functor" and the corresponding pic was (for me) LOL funny ;-) My intro to functional programming has been via Scala; not quite Haskell, but makes use of some of the same functional idioms, so a good place to get your feet wet in FP if you're tied to the JVM (yes, yes, there's also Clojure, prefer Scala's type system and easily grok-able syntax).
- klrr 13y agoIf you learn Haskell without getting into these stuff directly it's soo much easier. It will obvious and not cryptic like if you tackle this without any basic understanding of Haskell. This guide is for understanding the theory behind monads, the use should not be something difficult to get if you have used the IO monad before.
- Ygg2 13y agoOne thing that bothers me, but I found no explanation why - is there a way to turn 'Just a' to 'a'? >>= seems like it does that but I only see it working with function that returns a Monad. EDIT: I may have skimmed a bit.
- DanWaterworth 13y agoJust think about it. Let's call such a function fromJust: fromJust :: Maybe a -> a fromJust (Just x) = x fromJust Nothing = ??? -- What can we put here? Haskell doesn't have a null value that we could use here. There is such a function defined in the standard library, but it is partial. It uses error in the Nothing case. edit: In many cases there is a clear value that should be used in the Nothing case, suppose you have a Maybe Int which is the number of times a specific event happened in the last second. Clearly 0 is a good default and you can write a function (Maybe Int -> Int). There's a function I tend to use for such cases: (maybe :: b -> (a -> b) -> Maybe a -> b). In this case: maybe 0 id :: Maybe Int -> Int
- Peaker 13y agoMaybe a is a value that may or may not contain an a within it. So how would you convert that to an a, which definitely has an a in it? What you can do, is pass a callback to a function that handles the case where you do have an "a" in the Maybe. And that's basically what fmap, <$>, and >>= do.
- yummyfajitas 13y agoimport Data.Maybe let x = fromMaybe 1 (Just 2) -- x == 2 let y = fromMaybe 1 Nothing -- y = 1
- deleted 13y ago[deleted]
- sesqu 13y agoThat last picture was problematic in that it showcased three different conventions for application: fx f <*> x x >>= f The first of these (see also the Monads section) was the author's fault (should have used f <$> x), but the last conjures memories of PHP. Something to expand on? I would have also preferred more reminders that the box is the Functor/Applicative/Monad, not the function or the infix operator. [1] http://adit.io/imgs/functors/recap.png http://adit.io/imgs/functors/recap.png
- egonschiele 13y agoWhoops! I'll get this fixed shortly. > the last conjures memories of PHP As a non-PHP programmer I'm not sure what you mean.
- sesqu 13y agoA common complaint of PHP is that ordering of the arguments is inconsistent. So too here.
- nickknw 13y agoI for one thought this was a fantastic article, and quite well-written.
- egonschiele 13y agoThank you!
- osmano 13y agogood tutorial.