Please disable your adblock and script blockers to view this page

Functors and Monads for People Who Have Read Too Many ?Tutorials?


HaskellTitle
Functable
Fmapable
Serializable
OO
Ints
String Int
Iterator
Next
Functor
Stringable
massaging):I've
something?Monad
DATATYPE
SalaryRecords
AuditableData
Manipulate
the Software Transactional Memory
STM
TVar
IO
IO Int
Control
RealWorld
you.(In
the Real World
Not-Haskell
API
& ApplicativeDo
MonadError


Monad
Hoogle
fmap
List Int
Int Int
Int String
List Int ->
List String
StringAgain
Blorp Int ->
DATATYPE
Contains x.


Programming


Blob
Java

No matching tags


IO
unsafePerformIO

No matching tags

Positivity     41.00%   
   Negativity   59.00%
The New York Times
SOURCE: http://www.jerf.org/iri/post/2958
Write a review: Hacker News
Summary

Even if you completely understand everything I write in this post, the real "ah ha!" where you realize just how useful the libraries built up around the monad interface are, the first time you search for a type on Hoogle where you're like this should exist and it turns out it does in fact exist already, that's still in your future. Just as a variety of functions are now possible and useful that only know they are taking "an iterator", by having a named, coherent "functor" concept in the language functions can be written that take advantage of the common functionality.It is important not to credit complexity of particular implementations to the complexity of the interface. but the latter's complexity should be accounted solely to that particular implementation, not the function () string interface it can conform to.Similarly, a functor may be as simple as mapping the incoming function over a list, or it may require an arbitrarily-complicated traversal over a data structure like a skip list and construction of a new one. Then we can visually match: f = [] and a = Int. I don't write a lot of Haskell code nowadays but I've been adopting that as how I represent lists everywhere in my types, because I don't think the special case is very useful.The other confusing type is the function type a -> b. Since this article is about understanding "monad" without going deeply into Haskell-specific details, I'm going to ignore it because it's just syntax sugar.Bind is simply a function that takes some wrapped type, a function that takes the inner value and returns a wrapped value, and a final wrapped value of that type.Maybe Int -> (Int -> Maybe String) -> Maybe StringAgain, no mystery to this implementation. To "understand iterators" in the sense I think most people mean, you just need to know the tiny amount of details around how to use instances, but the bulk of the value to the interface lies in the hundreds of individual implementations of them. But the complicated ones aren't complicated "because they're monads", they're complicated things that happen to be able to implement the monad interface, but the complication is fundamental to the implementing data type, not the monad interface.But there is something sensible to say about why the set of implementations of bind can have so much more complication than functors. A functor implementation would be able to return the contents of the URL, but it would have no way to pass an audit log back with it, because the type will have no room for that.Looking at the definition for monad again:What this interface happens to give us is a way we can program with whatever our "values" are (ints, Salaries, etc.) without worrying about the audit record. You get to program in STM oblivious to all that machinery under the hood, and the bind function works together to maintain the correct data structures the commit function will eventually need to do its work.In my opinion, this is the key element of understanding them, this ability to return "more stuff" than just what you asked for, and for this machinery under the hood to combine those "more stuffs" for you in a way that is meaningful for the particular data type, thus yielding an STM type where you are not responsible for managing what variables have been used, performing operations on values without worrying about the audit log, etc.But most monad implementations in Haskell, and pretty much all of the ones used in "tutorials", are degenerate, and thus, in my opinion, bad examples. This is not a criticism of those implementations or those data structures, which are the way they are for good reasons, but I think it's better to start here and then understand the standard things like List as degenerate implementations (and perhaps ponder the mathematical reasons for that degeneracy) than to try to build up to them from the degenerate case.Let me show you what I mean:Maybe is an increasingly popular data type that is now fairly frequently discussed. If there is a value, it is unwrapped, passed to the function, and the result of the function is simply returned as the value.This is, frankly, a terrible place to start discussing the monad interface, because it uses none of the monad typeclass' powers.This is a great example of the power of simplifying things by keeping them in a common interface, even when it involves a degenerate implementation. The easiest way is to concatenate the lists in order, resulting in ["X", "X", "X", "X", "X"].This is often called "flatmap" because it looks a lot like mapping the transformation function over the original values:It is a common misconception that "monad is the same as flatmap", which has even made its way into some "monad libraries" in non-Haskell languages. flatmap is the particular implementation of the monad interface on lists. But it is still an overcomplicated implementation of iteration over an array, even if it yields the correct values in the correct order.List's monad implementation does at least demonstrate how the bind implementation has a lot of freedom in how it calls the passed-in a -> m b function. A common mistake in implementing "monad libraries" is to set things up so the function will be called exactly once; the monad interface can not be implemented that way, only certain ones that happen to do that work. That would take more machinery.There's another common misunderstanding of the monad interface which has led to much head-scratching confusion: The idea that it isn't possible to extract values from "monads", and that this is somehow an important element of what they are.This is completely false, and not just false, irrelevant to the monad interface.It is true that the monad interface itself does not specify a way to extract the values from its originating data structure. That means that using >>= alone, it is not possible to "extract" values from the data type implementing the monad interface because no matter what you do, the result is still wrapped in the outer data type.You can see it right in the definition of the monadic interface. The return value is specified as m b; that is, there is no way to escape the data type wrapper using just bind.But monad is just like any other interface... It is true you can't use the Monad interface itself to do that, but that doesn't mean you can't some other way.Whether you can extract values from a data structure has nothing to do with whether it implements the monad interface... what controls that is the data structure itself.It happens to be convenient that the monad interface allows you to manipulate values contained in them without every having them "directly", because it gives certain important data types a way of manipulating values without letting them escape out where the data type may not be able to provide guarantees about what's going on, which brings us finally to...Not "the IO Monad"... the "IO data type that implements a monad interface".The problem with "understanding IO" actually has nothing to do with monad. The bad news is that because it is so weird, it's a very bad data type to try to understand the monad interface with, because as a confused newbie, it is very easy to attribute confusion to "monad" when in fact you're confused about this super-magical IO type.The good news is that it's very easy to just mechanically follow directions online, use the do notation with the IO data type, and things will just work. just like in languages like Go that lack an iteration interface!What the monad typeclass brings to Haskell is not IO. they would just no longer have a shared way to speak about all of them.What the monad interface brings is not capability; interfaces can't do that, because interfaces don't do anything on their own.If you understand this all properly, I think "needs monads to do IO" is not even a particularly sensible thing to say. Even if you understand the interface, you still have to understand the specific data type implementing it to know what's going on.It is much like the much more common Iterator interface this way. That does not come from the hash table's "iteratorness"; it comes from the nature of the internal implementation of the hash table and how it can efficiently yield keys or values give that implementation.Similarly, for the various exotic monads, it is not an understanding of "monad" that will reveal what they are doing, but an understanding of the specific data structure and implementation.To the extent that there is a sense in which you can "understand monad", it comes from understanding things like the Control.Monad package, and the tooling around monad, and how they can be exploited with the properties of individual monads to achieve various powerful effects concisely, without sacrificing the essential properties of Haskell. Glossed over with malice aforethought: lawfulness of implementations, automatic derivation of functor instances, how laziness turns data structures into control flow and especially how that interacts with the IO monadic implementation and how a Haskell program is really one big generally-infinite pure data structure lazily traversed such that every Haskell program really is just a pure value run through a particularly complicated interpreter, the trivial upgrade of any Functor to Monad, applicative & ApplicativeDo, a deep discussion of "do" (which I think isn't too hard once you get the monad interface itself) (and yes, technically do does do a little bit of stuff with MonadError but it's minor and mostly deprecated last I knew anyhow), join (which I kinda think might be a better way to introduce the monadic interface on its own terms, but it's so offbeat relative to the rest of the web I decided to go with the usual >>=, some details on the Haskell type system (I had some more in here but they got in the way).

As said here by