11 ms·
Functors, Applicatives, and Monads
- rthnbgrredf 1y agoThis reminds me of https://www.adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html https://www.adit.io/posts/2013-04-17-functors,_applicatives,... I think over the recent years, there's been a rise in typed languages that support functional programming like TypeScript and Rust. It will be interesting to see if this trend continues in the context of AI assistant programming. My guess is that it will become easier for beginners, and the type systems will help to build more robust programs in cooperation with AI.
- yodsanklai 1y agoYes, type checking works very well with AI since typing provides a step of verification. The more precise the type system is, and the more guarantees you can have. At some extreme, you can entirely specify the program with types. So if the program type checks, it is guaranteed to implement its spec and you can trust the AI. I assume the AI will have a hard time to write the code though. But I had very good results in Rust, less in Haskell. I think it's also because the Rust compiler gives more meaningful error messages, which helps the AI to iterate.
- snickell 1y agoEven in early 2025, LLMs are already the most powerful type inference algorithm. Why would they need a static type system in 2030? My guess is it'll be the opposite: I suspect compared to humans, LLMs will make fewer type errors, and more errors that are uncaught by types. Thus I expect type systems will be of lower value to them (compared to humans), leading to a shift toward dynamic languages and the possible extinction of typed languages. The alternative I could imagine is moving toward Haskell-like languages with MUCH stronger type systems, where higher-level errors ARE type errors. My one concrete observation in this direction: "press . to see valid options" behavior is a traditional strong point of typed languages. And interestingly, proved to be one the first thing that early/dumb LLMs were actually pretty good at. I believe that indicates LLMs are relatively good at type inference (compared to other things you can ask them to do), and we should expect that to continue being a strong point for them. In working with Cline in both TypeScript and JavaScript, I find the LLM making tons of errors it has to go and fix in a future iteration, but virtually none of them are type errors. I suspect LLMs are relatively good at duck-typed languages because they have a much bigger working memory than humans. As a result, the LLM can hold in working memory not just e.g., the function argument in front of them, but also all the callers of the function, and how they used the variable, and callers of the callers, and thus what "duck-type" it will be. A system that can do this level of automatic type inference doesn't necessarily benefit from a formal, static, compile time type system.
- nsonha 1y ago> Why would they need a static type system in 2030? Why do many people talk about type systems as if they're only a safety guard? To me that's never the main role of type systems. I don't know what's the word for it, but types allow me to read the code, on a high level. Sure AI will write them but as long as software engineers exist, we still have to read the code. How do you even read code without types? Comments? Unit tests? Actual implementation? > LLMs will make fewer type errors, and more errors that are uncaught by types > extinction of typed languages Don't you find these contradictory? If LLM increases the rate of error uncaught by types, then type systems or the usage of them should catch up, otherwise there is no magical way for software to get better with LLM. In the current state of LLM, the type system (or lsp and/or automated tests) is what allows the "agentic" AI coder to have a feedback loop and iterate a few times before it hands off to the programmer, perhaps that gives the delusion that the LLM is doing it completely without type system.
- snickell 1y ago> How do you even read code without types? We're not going to settle the preference for dynamic vs static types here. Its probably older than both of us, with many fine programmers on both sides of the fence. I'll leave it at this: well-informed programmers choosing to write in dynamically typed languages DO read code without types, and have happily done so since the late 1950s (lisp). The funny thing is, I experience the same "how do you even??" feeling reading statically typed code. There's so much... noise on the screen, how can you even follow what's going on with the code? I guess people are just different? > LLMs will make fewer type errors, and more errors that are uncaught by types The errors I'm talking about are like "this CSS causes the element to draw part of its content off-screen, when it probably shouldn't". In theory, some sufficiently advanced type system could catch that (and not catch elements off screen that you want off-screen)? But realistically: pretty challenging for a static type system to catch. The errors I see are NOT errors that throw exceptions at runtime either, in other words, they are beyond the scope of current type systems, either dynamic (runtime) or static (compile time). Remember that dynamic languages ARE usually typed, they are just type checked at runtime not compile time. > perhaps that gives the delusion that the LLM is doing it completely without type system. I mentioned coding in JS with cline, so no delusion. It does fine w/o a type system, and it rarely generates runtime errors. I fix those like I do with runtime errors generated when /I/ program with a dynamic language: I see them, I fix them. I find they're a lot rarer in both LLM generated code and in human generated code that proponents of static typing seem to think?
- personperson69 1y agothe bit at the end is quite rude of the haskeller responding but I also think they're largely right; another monads explained through boxes tutorial is not gonna help anyone. In fact it's really a step in the wrong direction. Using a few different monads is where to start.
- Vosporos 1y agoWas it rudeness or honesty without malice? The "monad tutorial" instinct is a well-documented fallacy. In my culture we don't whitewash our opinions to make them palatable to someone who's obviously doing something wrong in a known way.
- redlohr 1y agoOn first read, I was prone to agree with the author -- why put down someone seeking your input? Then I read your comment and went through it again. After a re-read I think you have it right. The response was direct, and perhaps quite cutting to the author who had devoted significant time to the article only to be told they're one of hundreds who have made the same mistake. But the only denigrating in the linked blog article seemed to be the grouping with others who had fallen into the same trap.
- jerf 1y agoUnfortunately, while you may not have appreciated the tone of the Haskell interaction, they are correct in their assessment from a factual perspective. This explanation propagates a number of misunderstandings of the topics well known to be endemic to beginners. In particular, I observed the common belief that functors apply to "containers", when in fact they apply to things that are not containers as well, most notably functions themselves, and it also contains the common belief that a monad has "a" value, rather than any number of values. For instance, the "list monad" will confuse someone operating on this description because when the monad "takes the value out of the list", it actually does it once per value in the list. This is the common "monad as burrito" metaphor, basically, which isn't just bad, but is actually wrong. I'm not limiting it to these errors either, these are just the ones that leap out at me.
- _jackdk_ 1y agoI agree. The "container" intuition for Monads leaves you stuck when you try to contemplate IO (or even Promises, these days), because the "bind" operator looks like it does something impossible: extract "the" `a` from the `IO a`, when you have no idea what it is. (Trust me, I spent a long time stuck at this point.) Better to think of Monad as "Applicative + join" (you need Applicative to get `pure`). If you think of Monads in terms of `fmap` + `join :: Monad m => m (m a) -> m a`, then you don't need to imagine an "extraction" step and your intuition is correct across more instances. Understanding `join` gives you an intuition that works for all the monads I can think of, whereas the container intuition only works for `Maybe` or `Either e` (not even `[]`, even though it _is_ a container). You can define each of `>>=`/`join`/`>=>` in terms of `pure` + any of the other two, and it is an illuminating exercise to do so. (That `class Monad` defines `>>=` as its method is mostly due to technical GHC reasons rather than anything mechanical.)
- jerf 1y agoI prefer the "join" approach for beginners too, but >>= has become so pervasive that I feel bad trying to explain it that way. Turning people loose on monad-heavy code with that approach still leaves them needing to convert their understanding into >>= anyhow. One does wonder about the alternate world where that was the primary way people interacted with it.
- randomstate 1y agoComing from non-Haskell background, it took me a good while to undestand that `Just` is a constructor specific to the `Maybe` type. Found this for a quite nice answer: https://stackoverflow.com/a/18809252 https://stackoverflow.com/a/18809252
- lihaoyi 1y agoFor some reason everyone likes to talk about Monads, but really the other types here are just as interesting. For example, Applicatives are less dynamic than Monads in that you can't `flatMap`/`bind` to decide on the "next" thing to evaluate based on the previous value, but in exchange you get a "static" tree (or graph) of Applicatives that lends itself much better to static analysis, optimization, parallelism, and so on. IIRC Haxl (https://github.com/facebook/Haxl https://github.com/facebook/Haxl) uses Applicatives to optimize and parallelise remote data fetching, which is hard to do with Monads since those are inherently sequential due to the nature of `flatMap`/`bind`. My own Mill build tool (https://mill-build.org/ https://mill-build.org/) uses an applicative structure for your build so we can materialize the entire build graph up front and choose how to parallelize it, query it, or otherwise manipulate it, which is again impossible with Monads since the structure of a Monad computation is only assembled "on the fly" as the individual steps are being evaluated. "Parallel Validation" where you want to aggregate all failures, rather than stopping at the first one, is another common use case (e.g. https://hackage.haskell.org/package/validation https://hackage.haskell.org/package/validation or https://typelevel.org/cats/datatypes/validated.html https://typelevel.org/cats/datatypes/validated.html) Monads seem to have this strange aura around them that attracts certain kinds of personalities, but really they're just one abstraction in a whole toolkit of useful abstractions, and there are many cases where Applicative or some other construct are much more suited
- kqr 1y ago> Monads seem to have this strange aura around them that attracts certain kinds of personalities Historical accident. There was a time, not very long ago, when we didn't know applicative functors were a useful separate subset of monads. We thought full monads were needed for all the neat things that applicatives are sufficient for. During this time, lots of ink was spilled over monads. Had we invented applicative functors a little earlier, they would probably have gotten more of the spotlight they deserve. ----- I also think people underappreciate the humble semigroup/monoid. But this is not historical accident, it is just that it seems to simple to be useful. But it is useful to be able to write functions generic over concatenation!
- 1y ago
- behnamoh 1y agomonads are the MCP of functional programming—no one really knows what they are but everyone writes an article about them using analogies that break when you actually use them in practice.
- aklein 1y agoWhat is MCP?
- marcus0x62 1y agoModel Context Protocol. It is a way to give an LLM access to an API. There's a lot of hype about it right now, and, thus, a great many half-baked articles floating around. https://www.anthropic.com/news/model-context-protocol https://www.anthropic.com/news/model-context-protocol
- chowells 1y agoNah. Lots of people know what monads are. And critically, they don't write monad explainers. This is because if you understand the fundamentals well enough to understand an explanation, monads are so trivially straightforward that the definition is 100% of the explanation you need. Learn about how Haskell denotes types. Learn about higher-order functions, higher-kinded types, parametric polymorphism, and bounded polymorphism. Once you are comfortable with what all of those do in Haskell, Monad is a way to bound polymorphism with a couple extra expectations about how things behave. It takes about 5 minutes to explain and show a bunch of examples. But before you're comfortable with those parts, it's like trying to explain exponentiation to someone who doesn't understand addition. People who understand exponentiation don't do that. They don't try to use analogies. They say "you need to learn about addition first, then multiplication. You can learn about exponentiation after that."
- BoiledCabbage 1y ago> Learn about higher-order functions, higher-kinded types, parametric polymorphism, and bounded polymorphism. Except that's not the case because most people know all of those concepts from their main language, and don't know what a monad is. Higher order functions yup. A function can take a funtion as an argument and correctly assign the argument type (unless C where you can finagle it but it's not first class). Higher-kinded types? Yup. Prettt much half of "generics". Taking C# that essentially the idea that List<T> is a "type constructor" that allows you to construct a type. If you specify Integer as the T you can say something like List<Integer> and you get a type which is a list of integers. Parametric polymorphism? Yup. When defining function - for example using List<T>. You can define the implementation of the function using the generic parameter "T" and not have to specify if you are defining the implementation on a list of Integers or a List of String, and the single implementation will work for all of them. Bounded polymorphism? Yup. Again using C#, you can specify a restring on the "T" type parameter. Instead of saying "T" can be any type at all, you can add a "where" clause that says "T" must implement the ISerializable interface, or it must be a subclass of Foo class. So most people will read this list. And say "huh, I guess I do already know all of those concepts but by different names." But that doesn't mean they understand Monads conceptually, when to use them nor why. Even if those things are required to read the Monad definition, there is more there. A rough analogy, but it's like saying people know the visitor or facade design pattern just by reading their type signature. Oh and if instead of having intuitive names their names useless names like "foblax" and "grobalum" design patterns.
- burlesona 1y agoI feel like Haskell is easier to use than it is to explain, and in my experience a lot of these kind of tutorial / explanations actually make things seem harder and more complicated than just working with the concepts and observing what they do. (This one included.)
- fellowniusmonk 1y agoWhy are there so few practical, example and code driven tutorials? I've never run across a succinct "build Twitter with Haskell" in the wild.
- rrgok 1y agoYes, I really need a real word Haskell project simple enough to understand all the math concept. Like, I don't know when to implement the Monad type-class to my domain data types. For example, taking the twitter example, if I have Tweet data type: - should I implement the Monad, Applicative or Functor type class? - How would that help in the big picture? - What if I don't do it? All these funny example of boxes, burritos or context doesn't not help me solve problems. Take for example Monoid, I understand (partially maybe) that it useful for fold (or reduce) a list to a single value.
- wavemode 1y ago> Yes, I really need a real word Haskell project simple enough to understand all the math concept There actually is a book with precisely that title, which provides what you're asking for: https://book.realworldhaskell.org/ https://book.realworldhaskell.org/ > Like, I don't know when to implement the Monad type-class to my domain data types A concrete type (such as your Tweet type) can't be a Monad. Monad is implemented on generic types (think: `MyType a`, where `a` can be filled in with a concrete type to produce e.g. `MyType Int` or `MyType String`). Most monads are data structures like list `[a]` or structures which provide context to computations like `State s a` or `Reader r a`
- T-R 1y ago> should I implement the Monad, Applicative or Functor type class? I struggled with this when I first learned Haskell. The answer is "yes, if you can". If you have a type, and you can think of a sane way to implement `pure`, `fmap`, and `bind` that doesn't break the algebraic laws, then there's really no drawback. Same for any typeclass. It gives users access to utility functions that you might not really have to document (because they follow a standard interface) and you might not even have to maintain (when you can just use `deriving`). Doing so will let you/users write cleaner code by allowing use of familiar tools like `do` notation, or functions from libraries that say they'll work for any Monad. It saves you from coming up with new names for those functions, and saves users from having to learn them; if I see something's a Monad, I know I can just use `do` notation; if I see something's a Monoid, I know I can get an empty one with `mempty` and use `fold` with it. As long as it's not a really strange Monad, and it doesn't break any laws, it probably just works the way it looks like it does. If you can define `bind` et. al., but it breaks the laws, it means the abstraction is leaky - things might not work as expected, or they might work subtly differently when someone refactors the code. Probably don't do that. If you don't implement a typeclass that you could have, it just means you might have written some code where you could've used something out of the box. Same as going through old code and realizing "this giant for-loop could've just been a few function calls if I used underscore/functools or generators". That said, it's not too common to stumble on a whole new Monad. The Tweet type probably isn't a Monad - what does it mean for a Tweet to be parameterized on another type like `Int`, as in `Tweet<Int>`? What would it mean to `flatMap`(`bind`) a function like `Int -> Tweet<String>` on it? A Tweet is probably just a Tweet. On the other hand, it's a little easier to imagine what a `JSON<Int>` might be, and what applying a function like `Int -> JSON<String>` to it might reasonably do. Or what applying an `Int -> Graph<String>` to a `Graph<Int>` might do. Most Monads in practice are combinations of well known ones. Usually you'll be writing some procedural code in IO, or working with a parser, and realize "I'm writing a lot of code checking for errors", "I'm tired of explicitly passing this same argument", or "I need some temporary mutable storage", or some other Effect - so you wrap up the Monad you're using with a Monad Transformer like `ExceptT`, `ReaderT`, or `StateT` in a `newtype`, derive a bunch of typeclasses, and then just delete a bunch of messy code.
- rebeccaskinner 1y agoI think it's great that people are excited about Haskell and want to write about it, and it's unfortunate that the author had to deal with a less thank tactful response to their work. I hope the author keeps spending time with Haskell and continues to make time to try to write more and help other people! That said, here is a bit of a long comment on my thoughts about writing about and teaching these things: It's true that teaching Monads, Applicatives, and Functors can be tricky and there are a lot of articles that end up doing more harm than good- either by teaching things that are outright incorrect, or more often, teaching people a particular way to use them but setting people up for a lot of trouble when they run across uses that diverge significantly from the mental model they've built up. Functions are a classic example of this. There useful definitions of Functor, Applicative, and Monad for functions, and depending on the mental model you've built up they can be either fairly easy to understand or very difficult to understand. This ends up being a big problem because Applicative and Monadic functions are so pervasive, but they are incomprehensible if your stuck in the traditional data structure mental model. IO is a great example of this- it's really just a specialized State, but it can be really hard to understand how it works if you're thinking about data structures. Parsers are another good example. I generally prefer to start people off with the "monad-as-computation" mental model, roughly "An `m a` is an m-computation that can have side effects and when evaluated returns a value of type a", where Maybe are computations that could fail, Lists are computations that can return multiple times, and IO are computations with all of the normal IO side effects. Starting with IO has the nice benefit that you can also help people come to terms with monadic IO as a means of dealing with lazy evaluation. It's a good gateway both into helping people come to terms with the challenges of lazy IO, and it also helps to provide a concrete motivation for IO in haskell that doesn't result in people going off thinking that Monads are a hammer and every problem in the world is a nail. From there, I think it's helpful to talk not just about bind but also join. Showing someone how to implement join in terms of bind and vice versa is a nice thing to do early because it helps to differentiate Monad from Applicative and it demystifies the "a monad is a monoid in the category of endofunctors" thing a bit (not that I'd proactively bring that up when teaching someone how to use them). I like to characterize the high level difference as something like "Monads are computations that can _call out to_ other computations and integrate their results", "Applicatives can run computations in parallel and combine the resulting structures / side effects", and "Functors allow you to lift pure functions into a computation". At each step, highlighting both how you are getting less powerful (because you can implement functors in terms of applicatives, and applicatives in terms of monads, but not the other way around), and how having less power can help you reason better about your programs (pros/cons of applicative vs. monadic parsers are a good example here). Finally, I think it's important early on to make sure your reader understands higher kinded types. A lot of people are used to languages with generics, but many of those languages aren't expressive enough to let you express something like Functor, and people often lack practice in thinking about something like `Maybe` separately from `Maybe Int` or `Maybe a`. In the end, I think these things really aren't that complicated, but they are built on a different view of programming that a lot of readers have the first time they encounter them, and the best approach isn't to translate the concepts into something people are already familiar with. Instead, I think you need to help the reader adapt their mental model. It's a harder path, but one that I think pays off more in the long run.
- IshKebab 1y agoThe problem with Monads etc. is that they're simple concepts with extremely confusing names. Monad should be FlatMappable. Once it has the correct name it barely even needs an explanation at all.
- agumonkey 1y agoEven though I see why it could help as introduction, I think flatmap is too narrow to express monadism
- bontaq 1y agoI've seen this opinion before but disagree with it. There are maybe five names to learn. They relate to the actual concepts, allowing you to expand your knowledge.
- contravariant 1y agoOne issue with that is that you can write Flatmap in a way that doesn't obey the Monad axioms. And once you write out what it means to be 'correctly' flatmappable you've recreated the Monad axioms. Though it would help if more people were aware that a 'nice' way to 'unnest' a functor (F F x -> F x) is really all that it takes to have a Monad.
- yodsanklai 1y agoFlatMappable doesn't capture what a monad is. For instance, you can do async programming using monads. Doesn't relate to FlatMappable. I think you don't see the need for a new name if you don't grasp the concept. It's like in mathematics, you have tons of algebraic structures, like monoid, groups, fields, rings. They all represent categories of things which share some properties. You don't want to name the category by a one of its representatives, that would defeat the purpose of introducing an abstraction.
- IshKebab 1y agoI don't know, I think the fact that you can use FlatMappables to do async programming and pure IO etc. doesn't mean you have to capture all of the potential uses in the name. I mean... you can use timer interrupts to do preemptive multi-threading but we don't feel the need to give them a confusing name.
- e-dant 1y agoPart of why monads are not interesting to talk about is that they’re generic enough that most explanations are incomplete, and sufficient explanations are boring and unhelpful. But the biggest reason is that they’re sort of intuitive, plenty examples exist. And then at some point someone tells you that those things are monads, but it’s in the kind of way that social psychologists make up some fancy word for crap we all know about in our gut. Nobody gives a shit that a list is a monad, people give a shit that it’s a list. Anyone who’s written lisp or node or any nontrivial C program or anything with coroutines or anything with concurrency can and will tell you that, yeah, duh, control flow can be represented by a data structure. A couple more fancy “monad laws” and you have something that looks like other monads, and lists and if expressions and IO meld together. Ok, how unhelpful.
- chowells 1y agoThe helpful part is the ability to abstract over arbitrary monads. That's the thing that makes it worth identifying that it's a known and well-studied pattern.
- hibikir 1y agoThe fact that they are so generic is what makes people misunderstand them: They focus on 1 or 2 examples, without seeing that the same concept works in all kinds of other use cases. People realize a list can be a monad, and they they imagine option and set are also monads. But then you have to tell them that the same applies to Future, and Either. That you can have a resource monad that closes resources. This is when the fact that something is a monad starts to matter, because of generic concepts for transformers. Every language that has promises and lists will give you a way to turn a List[Promise[T]] into Promise[List[T]], written ad-hoc, but it doesn't have to be quite so ad-hoc. It's when you are stacking 3 or 4 different properties together that the abstract concepts matter. The lack of the abstraction is what makes some language have trouble doing more than just a little bit of functional programming, as going deeper becomes unmanageable without some help.
- VirusNewbie 1y agoBut if people understood monads they wouldn't be bending over backwards to shoehorn specific syntactic sugar just for error handling.
- mjfl 1y ago[dead]
- hu3 1y agoThis is how Chat GPT o1 would explain Functors, Applicatives and Monads to a PHP developer. Looks more digestible to me, supposing it is correct. https://chatgpt.com/share/67e9b3b0-52a8-8001-87d1-d6d222a27ec4 https://chatgpt.com/share/67e9b3b0-52a8-8001-87d1-d6d222a27e... The prompt to save you a click: "I'm an experienced PHP developer, explain Monads to me using PHP exmaples." (yes I made a typo in exmaples but it worked fine anyway).
- layer8 1y ago> A functor is an abstraction that allows for mapping a function over values inside a context without altering the context of the functor. I’m not sure this is intelligible to laypeople. ;)
- mncharity 1y agoHmm. I've not yet seen a topical presentation which embeds a tweaked chatbot. Graphics, video, interactive graphics, each provide additional leverage beyond text. So too might "something to talk over the topic with". Something with a punch-list of insights to be conveyed, and misconceptions to be probed for. Monad education is rich in flawed models and incomplete appreciation, and also in meta discussion of these. Might this lend itself to a interactive socratic-y tutor? Could the world use a... new and improved monad tutorial?
- deterministic 1y agoMonads in Haskell is just a way to combine two functions into one when the output of the first function doesn't match the input of the 2nd function. That's all there is to it. It is not a way to "box values". Yes you can use it for that if the functions you combine happens to operate on "boxed values" (like Maybe) but that has nothing to do with the fundamental idea of what a monad is. And yes there are "monad rules" that ensures that combining the functions "makes sense". But people sometimes use monads in Haskell that doesn't actually follow those rules. But it works fine anyways for whatever they are doing.