This is an introduction to monads. There are many of these. My goal today is to show how a simple class written in Java could be translated into equivalent functionality in Haskell using some monads, without getting into any of the theory stuff.
Hopefully some people coming from a non-Haskell background will get something out of this, though the Haskell syntax is likely to be very WTF-inducing for those who haven’t seen it before.
I should begin with a few things that this guide is not about:
- Categories. The etymology of the word “monad” is a red herring. Trust me. Knowing a lot about category theory will make you a better programmer in the same way that playing a lot of checkers will make you better at chess: there’s probably some benefit, but it’s not a good way to get up and running with the basics.
We will be treating monads as a design pattern instead of monoids in the category of endofunctors. The latter perspective is interesting to the people who like to design languages; the former perspective is interesting to people who like to write code.
- The full generality of monads in programming. Lots of things are monads, and I will be ignoring most of them. I’m going to focus on how monads can be used to translate a particular Java class into Haskell, and what it looks like as we add functionality to both versions of the code.
- Haskell evangelism. I’ve already written about the things in Haskell that I love. I love Haskell, and I use it to make my dreams come true. On the other hand, I’m not at all invested in whether or not you like Haskell.
Though if you want to learn more about this language, I’d like to help you along your journey.
What I am going to talk about is how to use monads to do something in Haskell that is easy to do in Java.
Some idiomatic Java code and some non-idiomatic Haskell code
Let’s start by taking a look at a class in Java:
public class IntWrapper { private int m_i; public IntWrapper(int i) { m_i = i; } public void print() { System.out.println(m_i); } public void inc() { m_i++; } public void nextPrime() { while (!isPrime()) inc(); } public boolean isPrime() { for (int t = 2; t < m_i; t++) if (m_i % t == 0) return false; return true; } }
We’re going to re-implement this in Haskell using monads. To do this, we’re going to need to look at some facets of this code that are so obvious we usually wouldn’t even think to mention them:
- (Valid by construction) This class has a constructor. The constructor is there to make sure that the class methods have valid data to work with.
- (Data hiding) Once we’ve created a new
IntWrapper
object there is no way to get its private memberm_i
out: the only code that can access this member variable are the member methods. - (Implicit scoping) Member methods can refer to
m_i
without specifying an object instance. This is because the variablem_i
is implicitlythis.m_i
. - (Encapsulation) Member methods can refer to
m_i
belonging to other instances, but methods in other classes (that is, methods not in theIntWrapper
class) cannot do this.
How might a translation of this class look in Haskell? Here’s a “day one” approach:
module IntWrapper (intWrapper, printiw, inc, isPrime, nextPrime) where data IntWrapper = IntWrapper Integer -- We don't export the IntWrapper constructor because we want to -- preserve encapsulation. Because of this, we need to export a -- function that wraps the constructor. That's what intWrapper does. intWrapper i = IntWrapper i printiw (IntWrapper i) = putStrLn (show i) inc (IntWrapper i) = IntWrapper (i+1) isPrime (IntWrapper i) = isPrime' 2 i where isPrime' t i | t >= i = True | i `mod` t == 0 = False | otherwise = isPrime' (t+1) i nextPrime iw = until isPrime inc iw
What makes this a “day one” approach? It’s all about the interface. Let’s do a compare and contrast between using the IntWrapper
class in Java versus the IntWrapper
data type in Haskell.
Java:
public class UseIntWrapper { public static void main(String[] argv) { IntWrapper myInt = new IntWrapper(20); myInt.print(); for (int i = 0; i < 4; i++) myInt.inc(); myInt.print(); myInt.nextPrime(); myInt.print(); } }
Here’s what this might look like in Haskell (again, a “day one” translation):
main = do let iw1 = intWrapper 20 printiw iw1 let iw2 = (iterate inc iw1) !! 4 printiw iw2 let iw3 = nextPrime iw2 printiw iw3
This is utterly terrible. Because we aren’t mutating any of our variables, we’ve been forced to define three variables (iw1, iw2, iw3
) to try and describe a succession of modified values. It’s obviously error-prone. In fact, when I did this translation, I typo’d my way into print iw2
for the last line instead of print iw3
(seriously). If you like this style, that’s cool, but if you want to tell me that I should like it as well, be prepared to get punched in the face.
The essential weakness of this style is that it requires us to manually thread the IntWrapper
data through each of the functions. The Java code also does this (we need to refer to myInt
throughout) but clearly Java’s support for mutable state means it isn’t as ugly as our Haskell code.
The difference between the two
Isn’t this an example of why Java has better syntax than Haskell? Well, no: our Java code looks better than our Haskell code because we have used good Java practices and bad Haskell practices.
It’s quite easy to write equally crappy code in Java. In fact, let’s do just that. When we’re done, we’ll compare the bad Java to the good Java, make a few observations, and apply those lessons to our Haskell code.
Here is the IntWrapper
class, written so that
- The
m_i
variable is final, thereby simulating the way in which Haskell prevents us from mutating variables, and - each of the methods will be static, thereby simulating the way in which our Haskell functions led us to have
iw1, iw2, iw3
in our code.
Here is the resulting mess:
// IntWrapper2.java public class IntWrapper2 { private final int m_i; public IntWrapper2(int i) { m_i = i; } public static void print(IntWrapper2 iw) { System.out.println(iw.m_i); } public static IntWrapper2 inc(IntWrapper2 iw) { return new IntWrapper2( iw.m_i + 1 ); } public static IntWrapper2 nextPrime(IntWrapper2 iw) { int i = iw.m_i; while (!isPrimeHelper(i)) i++; return new IntWrapper2(i); } public static boolean isPrime(IntWrapper2 iw) { return isPrimeHelper(iw.m_i); } private static boolean isPrimeHelper(int i) { for (int t = 2; t < i; t++) if (i % t == 0) return false; return true; } } // UseIntWrapper2.java public class UseIntWrapper2 { public static void main(String[] argv) { IntWrapper2 iw1 = new IntWrapper2(20); IntWrapper2.print( iw1 ); IntWrapper2 iw2 = iw1; for (int i = 0; i < 4; i++) iw2 = IntWrapper2.inc(iw2); IntWrapper2.print( iw2 ); IntWrapper2 iw3 = IntWrapper2.nextPrime(iw2); IntWrapper2.print(iw3); } }
Where we had originally made use of objects to pass state between function, in this crap code we are not doing any such thing. It looks strikingly similar to our Haskell code, but with extra verbosity (obvious fact: this isn’t an argument against Java, because in practice no one codes like this). Let’s analyze the differences between this crappy implementation and our original Java code.
An analogy and a lesson
In our original Java code, the IntWrapper
class has a mutable member variable m_i
. Our client code could then create a new IntWrapper
, call it myInt
, and repeatedly use the same variable as it did its calculations.
I want you to think of the line IntWrapper myInt = new IntWrapper(20)
as a declaration of a context. It is saying “hey, make me a new context for carrying around an integer. Call it myInt
.” Then I want you to read lines like myInt.print()
as saying “go into the myInt
context and print
.” This is precisely what this code means. In other words, to some approximation, classes in Java provide us a way to define a stateful context.
This mode of thinking is important enough in what follows that I want to expand on it a bit. Let’s leave the world of programming for a moment and enter into an analogy. You and Sue are both talking about a mutual friend named Bob. You’ve both known Bob for years. Your conversation might look something like this:
Sue: Did you see the postcard Bob sent me from Alaska?
You: Yeah. I couldn’t believe what he wrote on the back!
Sue: Oh, the thing about the reindeer? That’s an old inside joke we had from when we were dating.
You: I didn’t know you guys had dated.
Sue: It was before we met. We broke up because he wanted kids and I didn’t.
You: Really? I never figured him to be the fatherhood type.
In this exchange there are a few things to observe:
- After Sue mentions Bob in the first line, for the rest of the conversation he is only mentioned by pronoun. The conversation implicitly carries the information that you’re talking about Bob, and indeed, it appears to carry the information about which Bob you’re talking about.
- Sue and Bob have their own contextual information (the inside joke) that wasn’t known to you until Sue alluded to it.
- For that matter, the information that the two of them had dated wasn’t known to you until Sue mentioned it.
- Before the end of the conversation, your mental model of Bob has shifted: you now know that he wants to be a father, whereas before you did not.
Structurally, when the conversation started, it was established that you and Sue are working in a context where “Bob” is a known entity. This is analogous to the Java statement IntWrapper myInt = new IntWrapper(20);
which establishes myInt
as a known entity. As Sue reveals information about Bob, changing your mental model of who he is (his state), she is speaking statements similar to myInt.nextPrime()
.
In our crappy Java code, we’ve basically abandoned this whole “context” idea.
So to fix our Haskell code, we’re going to do what we already were trained to do in Java: introduce a notion of context. To some approximation, this is what monads are about. A subtle point is that there are different notions of context that arise in programming, and different monads for modeling each. Following our conversation example from above, this is already something you are familiar with: some conversations are friendly, some are professional, some are adversarial, etc, and each carries with it a unique set of rules for what’s appropriate and what is not.
This is what different monads do: each comes with its own set of operations that are legal within the context that the monad is modeling. In some cases — as with some conversations — it becomes necessary to nest contexts. This is what monad transformers do: they give us a way to define some nested contexts and to move information back and forth through the nesting. Again, an example conversation to illustrate the point:
You: Say, have you heard any news about Bob’s mother?
Sue: Yeah, I heard she’s doing alright.
You: Has Bob gotten over the fact that she’s dating so soon?
Sue: Last time I brought it up he changed the subject.
You: Did she mention anything about whether or not she can make it to Bob’s surprise party?
Sue: She said she could if we schedule it for Saturday, but not if we are shooting for Sunday.
Here the conversation enters a sub-context dealing with Bob’s mother. Information about Bob’s relationship with his mother is moved around, based on the fact that there are two related contexts now in play in the conversation. Monad transformers provide a tool for moving information between nested contexts as well.
In Java, the analogous situation is where you have an object which references members that are also objects. This is obviously an extremely common situation. Later, when we add logging to our examples, we’ll see this explicitly.
Enough analogies, time to be concrete
We will add some context to our Haskell code via the StateT
monad transformer. You can think of StateT
as a design pattern for when you think your code would be simplified if you had some mutable state. (Unsurprisingly, this is a common thing.) I frequently see people claim that Haskell doesn’t support mutable state; we’re about to see that this is false.
The StateT
monad gives us a single mutable variable. It gives us three functions for working with this: put
, get
, and modify
(the latter is a convenience function built as a combination of put
and get
). Refactoring our Haskell code to use StateT
will complicate it a bit, but ultimately boils down to two types of changes:
- Whereas our earlier Haskell code didn’t have any type annotations, once we start using
StateT
it will be better if we include some. The reason is that, while Haskell is clever enough to infer that we are using some monad, it isn’t always clever enough to infer which monad we’re using. (This is often related to the monomorphism restriction, but that’s a topic for another day.) The type annotations that we’ll be putting on will be pretty bland. - We’ll use some
do
notation, which will make our code look more imperative. We’ll also useget
andmodify
.
There’s one other change that we’ll make: we’re going to replace our intWrapper
constructor with a different style of constructor that will wind up exposing a better interface to our client code.
When you start using StateT
, you can think of the functions you write as being very similar to methods in Java: the function implicitly has access to some data (in Java it was m_i
, here it is the data accessed by get
and put
). The type signatures on these functions documents this fact. It’s important to know, however, that StateT
isn’t the same thing as a Java class, due to some differences I’ll mention at the end.
Here’s what the conversion looks like:
module IntWrapper2 (runIntWrapper, printiw, inc, isPrime, nextPrime) where import Control.Monad.State data IntWrapper = IntWrapper Integer runIntWrapper i f = do (a, i) <- runStateT f (IntWrapper i) return a printiw :: MonadIO m => StateT IntWrapper m () printiw = do (IntWrapper i) <- get liftIO $ putStrLn (show i) inc :: Monad m => StateT IntWrapper m () inc = modify (\(IntWrapper i) -> IntWrapper (i+1)) isPrime :: Monad m => StateT IntWrapper m Bool isPrime = do (IntWrapper i) <- get return (isPrime' 2 i) where isPrime' t i | t >= i = True | i `mod` t == 0 = False | otherwise = isPrime' (t+1) i nextPrime :: Monad m => StateT IntWrapper m () nextPrime = do isItPrime <- isPrime if isItPrime then return () else do inc nextPrime
This code has definitely gotten more complex in appearance, but if you pick any given function, the differences between this version and the original are small.
Before we analyze this code, let’s take a look at what effect these changes have had on our client code:
import IntWrapper2 main :: IO () main = runIntWrapper 20 $ do printiw sequence (replicate 4 inc) printiw nextPrime printiw
This is substantially better than our “day one” implementation, and (in my opinion) is even better than our original Java implementation as well. The runIntWrapper
function takes two arguments: the initial integer (20
) and a body of code to execute. The body of code (the stuff following the do
keyword) is just a sequence of instructions that we want to perform on our IntWrapper
. Behind the scenes, runIntWrapper
is implicitly threading our IntWrapper
to each of these instructions. This really obviates the whole “think of this as declaring a context” idea.
In our conversation analogy, this might be something like sending Bob an email:
To: Bob
From: You
Subject: Directions to my houseFrom I-90, take exit 18 up to the Highlands. From there, take the first right turn, passing by Cafe Ladro. Drive until the road ends. My house is at the very end; park anywhere on the street. The doorbell is broken, so you’ll need to call Sue when you arrive.
The directions don’t say Bob’s name anywhere except for the To
field of the email, which establishes that the instructions are implicitly for him.
Some analysis of the transformation
I already mentioned that StateT
is a monad transformer. Let’s dig into what this means.
All along I’ve been trying to push the idea that monads provide us with a way of describing “contexts” for our code. When you sit down and try to get some work done with this idea, you’ll quickly run into the following question: how can I nest contexts?
I’ve already mentioned that this is what monad transformers do. Let’s dig into that a bit.
Most monads come in two flavors: a standard flavor and a transformer flavor. The former provides a notion of context; the latter provides a notion of context and is compatible with nesting.
Our printiw
function actually demonstrates this idea rather nicely. As a refresher, here’s the code for printiw
, exactly as it appears above in our StateT
version:
printiw :: MonadIO m => StateT IntWrapper m () printiw = do (IntWrapper i) <- get liftIO $ putStrLn (show i)
Notice that this function does exactly two things:
- It uses the
get
accessor to grab a copy of our integer. - It uses
putStrLn
to print this integer.
What’s so significant about this? In Haskell, all things related to the environment (writing to files, opening sockets, printing to the console, etc) are modeled in the IO
monad (read: context). So printiw
must be able to work with two contexts: our StateT
and IO
. The type signature
printiw :: MonadIO m => StateT IntWrapper m ()
documents this: it says that printiw
foremost resides in a StateT
context, but it must also have access to an IO
-capable context m
(that’s what the MonadIO m
assumptions means). The line liftIO $ putStrLn (show i)
does two things:
- It indicates that we want to print to the console. Trouble is, printing to the console is an action in the
IO
context, and our code resides within theStateT
context. So to fix this… - it then takes this action and “lifts” it to the broader (
StateT
) context (vialiftIO
).
Note to the reader. If this seems like boilerplate, it’s because it is. Lifting is a facet of the monad design pattern that sometimes introduces boilerplate, but is sometimes done automatically (the distinction depends on which monad transformers you’re using, and sadly, it seems to take some experience and trial-and-error to get a sense of when you need to lift
explicitly and when it’ll be done for you). In a moment, when we add logging to our example, lifting will be done automatically for us, and our code won’t have any additional lift
s in it.
You’ll notice that most of our functions have a signature like Monad m => StateT IntWrapper m ()
instead of using MonadIO
. That’s because printiw
is the only function that needs to do an IO
action; the others have no such need, so we are able to give them a more general-purpose signature.
Isn’t this a giant hassle?
Experienced programmers know that there are many philosophies when it comes to writing code. Usually these philosophies come in opposing pairs: static versus dynamic typing, functional versus imperative, compiled versus interpreted, etc.
What we’ve just seen is an example of a currently not-so-mainstream philosophy: that types should be used to annotate the ways in which code interacts with contexts. For instance, the type signature for printiw
explicitly documents that this function requires access to the IO
monad, while the type signature for nextPrime
is sufficiently general that we can conclude that this function does not use the IO
monad.
There is no related idea in Java or .Net (though the latter is introducing the pure
keyword, at present time support is extremely limited). In Java, a function may or may not write to the console; the only way to know is to analyze the code, as the type system isn’t strong enough to make claims one way or the other.
Haskell has a popular reputation for making IO difficult. My experience — and I’d suspect that other experienced Haskell programmers would agree — is that working with IO in Haskell is no hassle at all; the difficulty is in shifting one’s thinking away from Java’s fast-and-loose approach to Haskell’s explicit-by-default approach.
Whether or not this is a good thing is decidedly a matter of one’s programming philosophy.
Lessons in this code that can be reused elsewhere
When I’m programming, I usually start without monads, and add them as it becomes clear that they will simplify my design. Over time, intuition develops that will allow you to decide from the beginning if you want to be using a monad (and which monad to use).
If you want to have some notion of mutated state, StateT
is a good way to do it. To take code that is not written for StateT
and refactor it so that it is, the basic steps are:
- Write a “constructor” function. In our example, that’s
runIntWrapper
. The constructor function takes the initial value of the internal value (we supplied20
in our example) and some code to execute within this new context. In terms of implementation, it’s just a wrapper forrunStateT
, which returns two things: the value of your internal state variable after the given code has executed (in our example we discarded this value) as well as any data returned by the given code. - Use
get
,put
, andmodify
in your functions. - Provide some type signatures to help Haskell along.
Obviously this is just a quick and dirty to-do list, and while the steps sometimes vary based on the application, the complexity involved is usually at about this level.
Nesting contexts
Let’s beef up this example a bit, both in Java and in Haskell. We’ll add a logging feature to our wrapper: every time a function is called to act on our wrapper, it’ll make an entry in the log.
In Java the easiest way to introduce logging is to just have a list of strings. Here’s what the code looks like:
public class IntWrapper3 { private int m_i; private List<String> m_log; public IntWrapper3(int i) { m_i = i; m_log = new ArrayList<String>(); } public void print() { m_log.add("print"); System.out.println(m_i); } public void inc() { m_log.add("inc"); m_i++; } public void nextPrime() { m_log.add("nextPrime start"); while (!isPrime()) inc(); m_log.add("nextPrime done"); } public boolean isPrime() { m_log.add("isPrime"); for (int t = 2; t < m_i; t++) if (m_i % t == 0) return false; return true; } public void printLog() { for (Iterator it = m_log.iterator(); it.hasNext(); ) System.out.println( it.next() ); } }
Pretty simple. Here’s how I’d do the same job in Haskell. While I could use my current StateT
to carry log data, it would be a bit of a mess; after all, “logging” and “working with IntWrapper
” are orthogonal concerns. Let’s reflect that fact in our code by using another monad transformer in addition to StateT
. Folks who have read other monad tutorials will not be surprised when I say that we’re going to use WriterT
.
Here’s what it looks like:
module IntWrapper3 (runIntWrapper, printiw, inc, isPrime, nextPrime) where import Control.Monad.State import Control.Monad.Writer data IntWrapper = IntWrapper Integer runIntWrapper i f = do ((a, i), l) <- runWriterT (runStateT f (IntWrapper i)) return (a, l) printiw :: MonadIO m => StateT IntWrapper (WriterT [String] m) () printiw = do tell ["printiw"] (IntWrapper i) <- get liftIO $ putStrLn (show i) inc :: Monad m => StateT IntWrapper (WriterT [String] m) () inc = do tell ["inc"] modify (\(IntWrapper i) -> IntWrapper (i+1)) isPrime :: Monad m => StateT IntWrapper (WriterT [String] m) Bool isPrime = do tell ["isPrime"] (IntWrapper i) <- get return (isPrime' 2 i) where isPrime' t i | t >= i = True | i `mod` t == 0 = False | otherwise = isPrime' (t+1) i nextPrime :: Monad m => StateT IntWrapper (WriterT [String] m) () nextPrime = do tell ["nextPrime start"] nextPrime' tell ["nextPrime done"] where nextPrime' = do isItPrime <- isPrime if isItPrime then return () else do inc nextPrime'
There’s a few basic things to notice. First, signatures like Monad m => StateT IntWrapper m ()
have become Monad m => StateT IntWrapper (WriterT ["String"] m) ()
. This indicates that we have a nested context, or in the Haskell terminology, a monad stack. The inner monad (read: context) (WriterT
) provides us with our logging interface (granted by the tell
function). The ["String"]
specifies the implementation of our log: we’re just using a list of strings.
Second, we’ve changed runIntWrapper
so that it is able to accommodate the logging. In addition to using runStateT
, it now also uses runWriterT
to get the contents of the log. It is now returning these contents to its caller.
Let’s look at our client code:
main :: IO () main = do (_, l) <- runIntWrapper 20 $ do printiw sequence (replicate 4 inc) printiw nextPrime printiw mapM_ putStrLn l
Not surprisingly, only a little has changed. We are now capturing the data returned by runIntWrapper
. The runIntWrapper
function yields both the output of our given code (which we ignore with a little _
, as the code we’re giving doesn’t have anything to return) as well as the contents of the log at the end of execution (which we capture as l
). We then print the contents of the log using mapM_ putStrLn l
.
A qualitative analysis
There are a lot of important differences between our Haskell and Java code, some we’ve already discussed, some we have not.
One difference is the way in which we separated concerns. In our Haskell code, we wound up using two separate monads (StateT
and WriterT
) to implement our wrapped integer with logging. In our Java code, we used a single class with a member variable (a List<String>
).
In Haskell, using a stack of monad transformers is an idiomatic way to separate concerns. The WriterT
transformer is an expert in providing a write-only record; the StateT
transformer is an expect in providing a mutable variable. If we were being more serious with our coding, we would have defined a new type like
type IntWrapperT m a = StateT IntWrapper (WriterT [String] m) a
to ease up our type signatures. (This practice also makes it easier to later come along and factor in additional monad transformers. Indeed, defining this type synonym is definitely the preferred way to do things: by leaving it out of the samples I’ve unfortunately demonstrated bad behavior, but I didn’t want to have a sidebar discussion of type synonyms in the middle of the example.)
If we had been more serious on the Java side of things, we could have created a class that was purpose-built for logging. We may have then simply had IntWrapper
inherit from this class.
In my view, adding logging in Java and Haskell had about the same amount of overhead (in terms of the work it took as a programmer). Then again, I’ve been working in both languages for years, so adding a member variable or another monad transformer is a pretty routine thing for me. I’d presume that someone who is new to Haskell (or Java for that matter) would have a different view on the issue.
There’s an important topic I’ve thus far ignored: should all Java classes be translated into monads? My experience has been that this is usually a fine way to implement the class’ functionality in Haskell, in situations where the class is used to carry and mutate state.
It’s important to realize, however, that these two approaches to design are not interchangeable. In Java it makes sense to have several instances of a class interacting with one another; in Haskell one usually would not have several blocks of monadic code interacting, unless they were all running within the same monad (read: context).
So while we could implement a method in Java for adding two IntWrapper
instances together, a similar function in Haskell would probably require us to refactor a bit (we’d need to look at the MonadPlus
typeclass). This would be simple to do, but would have taken us down a different path. This difference does illustrate that Java and Haskell are sufficiently different that a direct translation is not always a natural thing to do.
Of course, there are examples that work the other way as well (that is, monad idioms that are commonplace in Haskell but hard to translate naturally into Java). I wouldn’t say that this is a good way to go about deciding which language is “better,” but it is definitely an example of why Haskell monads and Java classes aren’t really solving the same problems (though clearly their problem domains overlap).
Is the approach I demonstrated in Haskell easy or hard? I believe this to be a matter of one’s experience. I can attest that it took me some time to really grok this approach, but then again, I can also attest that it took me some time before I was able to write good object-oriented code as well.
I think that part of the problem is that, while it’s pretty simple to motivate how one should think about object oriented programming, similar models for how to think about monads have taken some time to develop. I suspect this is a pedagogical problem more than an intrinsic difficulty with the monad design pattern, but ultimately only time will tell.
Using monads in your own code
We’ve just looked at an example using the StateT
and WriterT
monad transformers. Moving forward, here some important questions:
- Will I always use these two monad transformers? Yes and no. When you are writing your own applications and libraries, you’ll probably wind up using these transformers (perhaps as well as
ReaderT
, which provides a read-only variable) as the foundation for your work. As suggested above, you’ll probably define atype
synonym to hide this fact. You’ll frequently have theIO
monad at the base (so that you can have IO), but will sometimes use theIdentity
moand at the base (when you are writing functions that don’t need IO). (TheIdentity
monad literally does nothing. If you have a stack of monad transformers, and need a monad at the base that won’t change the semantics of your program, you can use theIdentity
monad.)When you use libraries written by others (such as parsec or Template Haskell) you will find that these libraries provide you with their own monads that they want you to use. It will be convenient and natural. You’ll use these monads in a manner similar the client code we were writing earlier.
(Parsec, a library for writing parsers, is so natural and intuitive that it’s completely changed my outlook on writing parsers.)
- Should I always use the transformer versions of the monads, or should I use the “regular” versions? Why use
StateT
instead ofState
? I tend to always use the transformer versions, simply because I find it makes refactoring easier, and it makes the code slightly more general purpose in some situations. If I think I want to use the regularState
monad, I often useStateT
transformer on top of theIdentity
monad, yielding the same behavior.Of course, that’s just how I do things. It’s certainly not the law. I’d presume that someone out there will have a very good list of reasons why I’m wrong to do what I just described, and in the interest of being a better programmer, I’d appreciate hearing from them.
- When should I write my own monad? Aside from creating a
type
synonym, I rarely find myself writing my own monads (and with a type synonym, you don’t need to define a monad instance anyway). Monads are most frequently used to carry state around, and theStateT
,WriterT
, andReaderT
monad transformers do that handsomely.Sometimes a need for extra high performance can drive the need to write your own monad. Sometimes you need to write your own monad to interact with some weird Haskell extension (like the rank-2 types example I mention here).
Usually, though, you can go pretty far just using the standard monads and transformers. I’ve talked with many multi-year Haskell programmers who have said that they’ve never written their own monad. Your mileage may vary.
- What are the most important monads for a beginner to know about? This would be
StateT
,WriterT
,ReaderT
, andErrorT
. Here’s what they are used for:StateT
is used to pass around mutable state, like ourIntWrapper
example.WriterT
provides an interface for writing to a collection. It is very often used for introducing simple logging to an application.ReaderT
provides a read-only variable. It is most often used to pass around application configuration data in such a way that consumers of this data can’t modify it (which is a useful design invariant in many situations).ErrorT
provides a way to perform operations which might fail, and to manage failure gracefully. You can think of it as a way to perform computations which might throw exceptions.
From here, if you want to know more about the practice of using monads to get work done in Haskell, I’d suggest checking out Real World Haskell, which has some good discussion about using monads to engineer solutions to real problems, and the Typeclassopedia.
Luca Bruno
/ 20 July, 2010Creating monads in an imperative language might help for understanding them: http://valajournal.blogspot.com/2010/06/vala-and-monads.html
z
/ 20 July, 2010editing note: you missed a closing PRE tag or something after the code snippet just before the “A qualitative analysis” section, to the effect it makes the rest of the page mono-spaced.
At any rate, Great article!
intoverflow
/ 20 July, 2010@z thanks for the correction! You’re right, I missed that. For some reason Firefox wasn’t showing the behavior, but Safari (correctly) did. It should be fixed now, modulo caching.
Chris Kuklewicz
/ 21 July, 2010You describe StateT as introducing mutable state, as if the StateT were like the first java example.
In the second Haskell example StateT does not allocate mutable storage. It just hides the extra parameter passing in the first Haskell and second Java example.
One could use a newtype around an IORef to create mutable state and then make a third Haskell example.
Mithrandir
/ 22 July, 2010My experience so far is that good quality code can be written without using monads.
It will be more reusable if using monads but one can always refactor code. I do this each day as an exercise (taking between 15 mins and 1 hour). If you do this, you may also use TDD (HUnit)
Ben Butler-Cole
/ 22 July, 2010This is very interesting, thank you.
You mention the possibility of refactoring the Java version to introduce a superclass which handles the logging. My approach would be to write the logging as a decorator around the original class, which gives a better separation between the two concerns.
In fact I think there is an interesting parallel between decorators and monad transformers, in that they both have similar composability properties.
Although monad transformers allow for composable contexts with clear separation concerns, the code that uses them doesn’t share these properties. The logging is nestled up against the original functionality in your functions. Is there some way to achieve a better separation?
-Ben
intoverflow
/ 22 July, 2010@Ben You raise excellent points.
Some code
One option would be to introduce a typeclass that models the requirements of our
IntWrapper
monad, and then supply an instance that describes how logging can be strapped on. Here’s some code I knocked out to illustrate this:First some boilerplate:
Now we get down to business. What I'm going to do is define two typeclasses: one for modeling
printiw
(it gets its own typeclass because it relies on theMonadIO
assumption, which is stronger than theMonad
assumption the other functions makes) and the other for the rest of the functions:This code is basically just the code from our pre-logging Haskell example.
To introduce logging, we'll add another instance to each typeclass:
Some analysis
There are three things of note in this version of the code:
The client code can choose between the logging and non-logging version based on which
run
function it uses. If the client code usesrunIntWrapper
it will get the non-logging behavior; if it usesrunIntWrapperWithLogging
it will get the logged behavior. Aside from the fact that these functions return slightly different data (the latter returns the log), the client code is otherwise unmodified.This is basically a gift given to us by type inference. Haskell knows which instance of the
MonadIntWrapper
typeclass to use based on whichrun
function we're using. This is my favorite type of polymorphism in action.This code has better separation than the version I originally posted, but it still isn't perfect: in particular, it seems that if I want to properly log the
nextPrime
function, my with-logging version basically needs to re-implementnextPrime
. This is kinda crappy, and I'm not sure of a way around this (and I'd be interested in discussing this further with anyone who has questions or ideas).While the code I originally posted uses the monad stack
StateT IntWrapper (WriterT [String] m)
, this version switches things up and usesWriterT [String] (StateT IntWrapper m)
. I made this change because, well, these two things aren't equivalent owing to the way that data flows, and while the former worked just fine when we were mixing concerns, it doesn't work when we're trying to separate concerns usinglift
. (I tried it the old way, concluded in .7 seconds it wasn't working, and refactored in about 30 seconds.)I think you bring up an interesting point about the parallel between decorators and monad transformers. This seems like a productive line of thinking.
Rudi G
/ 23 July, 2010Great post. Small typo in the first Haskell listing: you didn’t actually define addFrom.
intoverflow
/ 24 July, 2010@Rudi thanks for the heads up, should be fixed
Peter Ashford
/ 9 December, 2010Hmmm…. if that’s what you’re wanting to achieve, then I think a more compact Java approach would be to use method chaining:
IntWrapper myInt = new IntWrapper(20).print();
for (int i = 0; i < 4; i++) myInt.inc();
myInt.print().nextPrime().print();
Which I would contend is probably better in that it doesn't require the construction of the monad to make it work. It's also more compact.
Tony Morris
/ 5 September, 2011See http://functionaljava.org/ for “monads in java.” In particular, the observation that “monad” is a computational concept that is independent of any particular programming language.
djhworld
/ 20 July, 2012I’ve spent a lot of time trying to get my head around monad transformers but my attempts have often led to me to become frustrated to the point where I’ve abandoned Haskell for weeks and months (only to come crawling back eventually, like a long lost love)
This article has helped me immeasurably in understanding the “core” monad transformers, I’m genuinely pleased that I’ve managed to get this far, I even implemented the ReaderT transformer into the IntWrapper example and that wasn’t hard at all!
While I’m probably not there in terms of being able to implement my own Monads/Monad Transformers, I feel much more confident in using the provided ones.
Thanks for writing this.
stolik01
/ 13 July, 2013Каждому Доброе утро!Всем привет лучший женский сайт Женские стрижки,Женские причёски
OccubMoreZoob
/ 12 September, 2013автооценка обухово
независимый эксперт ярославский
автоэкспертиза дтп электросталь
независимый эксперт спортивная
экспертиза автомобиля партизанская
независимая экспертиза павловский посад
Alexander
/ 18 October, 2016Nice article!