11 ms·
Functor, Applicative, and Monad
- nacc 7y agoBeing someone who is still struggling with these concepts, I like this tutorial because at least it doesn't use the common list/maybe/state to illustrate the concepts. Somehow I feel these concepts are so abstract - unless one is well versed in category theory, maybe only a data approach can prevent people from overfitting these concepts to specific examples. I would really hope to see a tutorial that have a diverse set of examples and just fmap each example with a light explanation of say, what is a monad in this code and what is not, and because it's a monad we can do this. Essentially the tutorial can just train a classifier in one's head, and with a nice set of examples maybe the brain can learn a general representation of concepts for the classifier ...
- weavie 7y agoI started writing this the other day - https://codersteve.dev/post/refactoring-to-monads/ https://codersteve.dev/post/refactoring-to-monads/ It's just a start, but maybe it could help.
- mjlangiii 7y agoIn this series functional concepts are very gently introduced. I feel like it really appreciates the beginners starting point and assumes very little. Is this close to what you want? https://egghead.io/lessons/javascript-linear-data-flow-with-container-style-types-box https://egghead.io/lessons/javascript-linear-data-flow-with-...
- mesarvagya 7y agoEven better http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html http://adit.io/posts/2013-04-17-functors,_applicatives,_and_...
- TheAsprngHacker 7y agoI am the author of this submission. I have strong opinions about functor and monad tutorials, and here are my thoughts: Back when I didn't understand what a monad was, I would read a bunch of tutorials and get confused by the analogies and examples. For example, I would get confused by comparisons to "boxes," or I would think that Maybe was the definition of a monad, or that IO was the definition of a monad. The information in the tutorial that you link seems to be all correct. However, it's too "jumpy" for my tastes. The tutorial talks about "contexts," but doesn't really explain what a "context" is, except for making a comparison to "boxes." I get that a functor is an abstract idea, so explaining it in an understandable way is difficult. However, I wish that the article would discuss type constructors, because the idea of mapping types to types is an important part of the definition of functor. Without this explanation, I imagine that the comparison to "boxes" would have given the past me the wrong impression of what a functor is. In my tutorial, I sought to teach the actual definition of a functor, applicative, and monad. I explain that a functor maps types to types and functions to functions in a way that preserves composition and identity, that an applicative preserves the product, and that a monad is characterized by a "join" operation that "flattens" the data and a "return" operation that "wraps up" the data. I actually would have preferred to explain the actual category theory, but I felt that it would be too intimidating, and so I attempted to convey the ideas in a non-category-theory way. With my current understanding of functor, applicative, and monad, I believe that if one doesn't learn their actual definitions, one doesn't truly understand them. I guess that I wanted my tutorial to be more rigorous. However, I am not an expert on teaching, so maybe I'm taking the wrong approach. See my Reddit comment: https://www.reddit.com/r/programming/comments/cy35zz/functor_applicative_and_monad/eyshqyc/ https://www.reddit.com/r/programming/comments/cy35zz/functor...
- mesarvagya 7y agoAs Doug Crockford once said "In addition to it begin useful, it is also cursed and the curse of the monad is that once you get the epiphany, once you understand - "oh that's what it is" - you lose the ability to explain it to anybody." [1] I think someone who is a beginner or just want to use Functor / Applicative / Monad without mastering underlying Category Theory, boxed model seems good enough. However, if you are creating own monads, then of-course we need to understand monadic laws. Every programming language has monad of some sort e.g. Optional in Java. To use optional chaining, I may not need to know all details but only how `flatMap` works. Maybe I am wrong too :). [1] https://www.i-programmer.info/news/167-javascript/5207-crockford-on-monads-and-gonads.html https://www.i-programmer.info/news/167-javascript/5207-crock...
- BucketSort 7y agoIt's just mind boggling how universal these concepts are and how they show up in surprising ways. As a recent example, I've been learning the basics of composing music in Haskell with Euterpea[1] and wanted to make a function which played several notes over a list of octaves to make chords. It turns out the applicative operator was exactly the function I need to do this! It would be hard to go into the details in just a little blurb here... but here is a piece I made with with it[2]. And here's the line of code with the applicative operator: notePlayer notes octs dur = musicSeq $ (uncurry <$> notes) <*> ((, dur) <$> octs) Won't make much sense without context, but it's there! [1]: http://www.euterpea.com/ http://www.euterpea.com/ [2]: https://soundcloud.com/a-mathematical-way/not-enough-time-to-read-the-manual https://soundcloud.com/a-mathematical-way/not-enough-time-to...
- deleted 7y ago[deleted]
- kccqzy 7y agoI have never heard of Euterpea, nor have I seen the rest of your code, but I suggest you refactor your slightly convoluted code like this: notePlayer notes octs dur = musicSeq $ notes <*> octs <*> pure dur Coincidentally, this might be a testament of the power of parametric polymorphism and equational reasoning.
- whateveracct 7y agoMy favorite part about Haskell is how you can know literally nothing about the domain and make meaningful changes to programs regardless thanks to local reasoning. That's really one power of functor/applicative/monad - if you understand their interfaces, you can work with new unfamiliar types that have these instances without much effort at all.
- BucketSort 7y agoIt's amazing. No one could ever do something like this with an imperative language!
- larusso 7y agoI always go back to “Learn you a Haskell for Great good”. http://learnyouahaskell.com/functors-applicative-functors-and-monoids http://learnyouahaskell.com/functors-applicative-functors-an...
- harry8 7y agoPretty weird that a comment linking a chapter from an oft referred to haskell text is a dead comment here. Surely if the alternate explantion in "Learn You a haskell for great good" is somehow sub-optimal it would be better to explain how rather than kill the comment inside 10 minutes? You see this sort of thing from language warriors fighting silly wars but, yeah, what's wrong with Learn You a Haskell? Why must it be fought and suppressed immediately? Crazy...
- harry8 7y agoDo please feel free to, you know, engage and, respond to the issue of "Learn you a Haskell..." like an adult capable of intellectual discussion.
- mkl 7y agoIt was probably automated. larusso has only made two comments ever, both containing links, so that probably triggered the spam detectors. You can manually resurrect for accidentally dead comments like this by clicking on the comment's time and then clicking "vouch".
- larusso 7y agoI’m personally more the consumer type here at hacker news.
- _hardwaregeek 7y agoAn important realization that I had was that monads/functors/applicatives aren't patterns in the sense of design patterns. You don't solve a singular problem with a monad. With something like a strategy pattern you have a concrete problem: how do I select different potential algorithms? Monads don't have a specific problem that they solve. Any attempt to motivate monads in such a manner falls flat because the problem is either too general to be motivating or too specific to apply to monads as a whole. Instead, functors/monads/applicatives are more like a technique that can be used to solve a wide variety of problems that all coincidentally use the same function signature. And therefore, it's perfectly acceptable to say "I know how monads work with Maybe and List, but not Reader" Because fundamentally, how a Reader implements bind is in no way related to how Maybe or List implement bind.
- mavelikara 7y ago> And therefore, it's perfectly acceptable to say "I know how monads work with Maybe and List, but not Reader" Because fundamentally, how a Reader implements bind is in no way related to how Maybe or List implement bind. If this is indeed true, what is the point of learning these "patterns"? From a mechanical understanding of the signature of bind and unit, you'd arrive at more sophisticated signatures, say filterM, but an understanding of what it does will still need an understanding of the specific implementation of the Monad instance it is being applied on. That sounds like a leaky abstraction. If so, why bother?
- TheAsprngHacker 7y agoTo my understanding, although the behaviors of filterM, etc. differ depending on the monad instance, as long as the monad instances follow the laws, those functions like filterM have predictable behavior. It's a consequence of "theorems for free" / parametricity.
- bad_user 7y agoThe point is that Monad, Applicative and Functor are well defined interfaces with laws (properties) you can count on. They are in fact much better, more precisely defined than classic design patterns. And in expressive programming languages (that support higher kinded types or that at least let you encode such types) you can also describe generic code that works over any applicative or monadic type. Having reusable functions that work just as well on lists, maybe/option, reader, io / promise or what have you means these type classes do a very good job at abstracting over data types. These are the purest forms of abstraction. And you can get syntactic sugar from the language as well. For example "for comprehensions" in Python work via the iterator/enumerable protocol, but that's super limiting. Haskell's "do notation" or Scala's "for comprehensions" work on any monad instead, being much more powerful and reusable. Unfortunately it takes an expressive language to understand and work with these abstractions comfortably. You won't grok monads in Go or Java.
- hoseja 7y agoI should learn to read Haskell one of these days. Not today though.
- TheAsprngHacker 7y agoThe majority of my tutorial actually uses OCaml, though. :P I like OCaml because it strikes a balance between imperative and functional programming. Maybe learning OCaml would be easier than learning Haskell? (Plus, OCaml has neat features like polymorphic variants and a powerful module system!)
- BucketSort 7y ago> one of these days More like one of these months/years. It's not just a language, but a philosophy of computation.
- ipnon 7y ago"Learn You a Haskell" is sparse on theoretical foundations. I made it halfway through the book feeling like I only had a superficial understanding of the language. If you are interested in a rigorous or systematic approach I recommend "Haskell Programming from First Principles".
- weavie 7y agoI think both are great books, but the book you choose is secondary in importance to the amount of time you spend just bashing your head against it until it starts to make sense.
- wh0knows 7y agoI don't think this article is very useful. It doesn't adequately provide an introduction to OCaml code (or adequately explain what a given code snipped is doing) and yet frequently defers to just code to explain a concept. It's an unrealistic expectation to expect an unfamiliar reader to simultaneously infer what a particular code snippet is doing then also go a level deeper and understand the concept that is trying to be presented. This article is most readable to those who already understand OCaml code, and if you can already read OCaml code you already understand these concepts.
- TurboHaskal 7y agoYou claim that OCaml programmers are already versed in category theory but that’s not correct. The first exposure usually comes with concurrency libraries such as Lwt which isn’t very old nor in the standard library, and it provides syntax sugar so you can start being productive right away. You can write perfectly fine production OCaml programs without knowing what a monad is. Some may, but a lot of OCaml devs don’t care about them.
- hope-striker 7y agoThis is correct, and in contrast to Haskell, where monads are a core part of the language (they are used in the definition of do-notation and list comprehensions) and currently the mainstream way to do IO.
- notfashion 7y agoThis is misleading. There's no place in the specification of Haskell that specifically defines monads as part of the language. They aren't a language feature. They are a pattern which happens to be expressible in Haskell, and which is supported by libraries: "Haskell's built in support for monads is split among the standard prelude, which exports the most common monad functions, and the Monad module, which contains less-commonly used monad functions. The individual monad types are each in their own libraries and are the subject of Part II of this tutorial." https://wiki.haskell.org/All_About_Monads#Monad_support_in_Haskell https://wiki.haskell.org/All_About_Monads#Monad_support_in_H... The fact that monads are used to implement parts of the language doesn't make them a core part of it. Techniques used in implementation aren't the same as language features.
- hope-striker 7y agoNice explanation of monads! My two cents: personally, I would've started with the fact that a monad is exactly the stringing together of functions (a -> m b), and the similarity between monoids, monads, strings / lists and functions under composition. You mentioned that • (a -> [b]) is the type of non-deterministic computations • (a -> Maybe b) is the type of fallible computations • (a -> IO b) is the type of effectful computations • (a -> (s, b)) is the type of computations with state s • that the Monad instance merely specifies how to compose them • all such composable constructs can be expressed as a monad • do notation and list comprehensions automatically work across all of them and, in my opinion, these are all much more powerful motivation than beginning with a comparison to functors.
- ollysb 7y agoAfter learning Elm for a few months I picked up a Haskell book. When I got to the Functor, Applicative and Monad chapters I discovered that I’d actually been using them for months without knowing it. Having had practical experience with them the theory came as a revelation. I found that I’d developed an intuition for them from all the concrete scenarios where I’d used them. The Elm community goes out of it’s way to avoid talking about them because they make learning functional programming seem far more intimidating. It’s a very simple language though - Evan had always had beginners in mind when desiging the language and tools. If you’re looking to improve your functional programming skills it’s got a great learning curve.
- uryga 7y agothere's an equivalent definition of Applicative that may be a bit less intimidating: class Functor f => Applicative f where unit :: f () (**) :: f a -> f b -> f (a,b) [source - a bit CT heavy](https://stackoverflow.com/a/35013667/5534735 https://stackoverflow.com/a/35013667/5534735) (i'll be writing the second operation as `××` in some places because of HN asterisk weirdness) this gives us: - `unit`, a "template" container with a hole we can fill (by doing `fmap (const myValue) unit`, equivalent to `pure myValue`) - `fa ×× fb`, to "compose" two Applicative values. this shows how, unlike with Monads, the two computations must be "independent". for example, if IO were only an Applicative, you could do print "hello" ** print "world" but not getLine >>= \s -> print ("hello, " ++ s) (where the second computation depends on the string we got in the first one) it also nicely shows the two ways lists can be an Applicative - `fa ×× fb` can be either the Cartesian product `[(a,b) | a <- fa, b <- fb]` or `zip a b` (ZipList). (also, it's kind of like a Monoid, which is neat!)
- TheAsprngHacker 7y agoHuh? I cover this definition in the tutorial, when I discuss OCaml's (and+) operator! Did I gloss over things too quickly?
- uryga 7y agooh man, i must've missed it! tbh though, i just quickly scanned through the article to see if this needed plugging, because I remember being completely stumped by `(<×>)`. too late to edit it now though :/ sorry!
- foobar_ 7y agoMaths is what you get when you limit all your variable names to single letters.
- kybernetikos 7y agoAnd make symbols mean different things in different situations, and sometimes reuse symbols for the same thing, and sometimes make sin(x+3) mean the variables s * i * n * (x+3) and sometimes mean taking the sine of x+3. Mathematical language is worse specified than markdown and has a huge amount of ambiguity and requirement that you understand the context that you're working in. It works fairly well for mathematicians, in terms of being concise to express complex ideas on a blackboard, but overall it's a mess.
- foobar_ 7y agoAnd the naming convention is absolutely horrid. It seems the main criteria is .. it should sound clever.
- louthy 7y agoI used to think the way you do (or the way I'm inferring you think), especially coming from the OO world to functional. But, functional programming is really about programming with types, not named things, and so a lot of the time we work with much smaller functions that are expressions. It should be trivial to follow the intention without explicit names. Having terse and concise names can make it much easier to read if you're following the types. Occasionally, if a piece of code is more challenging to read, I may use single word variable names, but mostly it's trying to get the names out of the way of the types and operators. For example, if I have a type (where `a` is generic): a -> bool Then this can only be a predicate function. If it's provided as an argument to a function, I'll call it `f`, not `predicate`, because the type itself is the documentation. It's definitely a mindset change for a different approach, functional programming is much more about function composition, whereas imperative is about a list of steps to perform, and so that composition is much more about whether the types fuse or not, the names become slightly less relevant, especially for general purpose library functions. Personally, I feel it helps. But this is probably one of those subjective things like tabs vs spaces. (spaces are correct)
- moomin 7y agoIt turns out that every generic type t has a corresponding map function map : ('a -> 'b) -> 'a t -> 'b t. So how about x : 'w -> Bool?
- TheAsprngHacker 7y agoIf there is a function `map : ('a -> 'b) -> 'a t -> 'b t`, and there exists a function `x : 'w -> bool`, then `map x : `'w t -> bool t`. Is this what you were asking?
- antisemiotic 7y agoThink `data Foo a = Foo (a -> Bool)`, pardon my Haskell. A function `map :: (a -> b) -> Foo a -> Foo b` is impossible, however `contramap :: (a -> b) -> Foo b -> Foo a` is fine (just pre-compose the given function with the stored function). Even worse with `data Bar a = Bar (a -> a)`.
- TheAsprngHacker 7y agoThanks. I know what contravariant functors are, but I haven't used them, so I didn't realize that was what the parent commenter was asking about. You're right, my claim that every type 'a t has a corresponding (covariant) functor is incorrect, and I should either take that out or mention contravariant functors.
- moomin 7y agoIt’s even worse than that. Consider data Foo a = Foo (a -> a) This admits neither Functor nor Contravariant. Sadly all you can say is “If you can map, it’s a functor.” I always end up finding out more about any subject I actually publish a post about when people read it...
- TheAsprngHacker 7y agoThe other person also mentioned this. It's called invariance. There's also a fourth variance: https://www.benjamin.pizza/posts/2019-01-11-the-fourth-type-of-variance.html https://www.benjamin.pizza/posts/2019-01-11-the-fourth-type-...
- leshow 7y ago> According to currying, 'a -> 'b -> 'c is interchangeable with 'a * 'b -> 'c. (The fancy math word for this interchangeability is "isomorphism.") I thought isomorphism was when a function was reversible. I didn't think it had anything to do with currying.
- Iceland_jack 7y agoIt means there are two functions going between them that witness the isomorphism. The witnesses in Haskell are the higher-order functions (they transform functions) `{,un}curry`: curry :: ((a, b) -> c) -> (a -> b -> c) uncurry :: (a -> b -> c) -> ((a, b) -> c)
- TheAsprngHacker 7y agoIsomorphism is not about currying. When I mentioned isomorphism, I was referring to the interchangeability in general, and the currying relationship is just an example of an isomorphism. In category theory notation: Hom(A * B, C) ~ Hom(A, C ^ B) This is one of the laws of a Cartesian closed category. The simply typed lambda calculus with products is the internal language of a Cartesian-closed category.
- voidhorse 7y agoI still find Philip Wadler’s original paper on Monads to be the clearest explanation of the pattern and concept. It remains scoped to the usefulness of the concept in programming, lays out very clear motivating examples, and proceeds to implement the pattern to solve each case in a lucid and explocit manner. I’d say the only downside is that it assumes at least some familiarity with FP and writing more “theoretical” or academic tools like evaluators, but all in all it’s still much clearer than the majority of garbled explanations of monads, even those that attempt to explain the concept through its dependencies/priors (functor/applicative). here’s the paper: https://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf https://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/b...
- deleted 7y ago[deleted]
- _bxg1 7y agoI've always understood intuitively the existence of .map(), .reduce(), and .flat() in JavaScript, but .flatMap() felt like a weirdly arbitrary combination of two of them. Now it makes sense!