These dialogues somewhat pedantically dissect what we mean when we say a type is an instance of a typeclass or a type has an instance of a typeclass and why. Through the course of conversation, we touch on the nature of types, type constructors, and typeclasses, which led to us talking about math, set theory and category theory, and what they have to do with types and typeclasses.
Examples of the phrases we’re discussing, using “semigroup” as an example:
Semigroup
The takeaway is that when we use the word “type” it can mean any of several things: “type declaration” on its own, “type plus instances”, names of “types” or “type constructors”, a property of an algebraic structure. These are not in conflict; they are all essential, in our view, to understanding what types and typeclasses are.
Oh, dear, there are still many unanswered tickets in the (haskellbook) Zendesk. This one I just opened is a complaint/question because we say a type “has an instance” of a typeclass.
What are people’s objections to that?
That it can’t be both “is an instance” and “has an instance” and the Haskell Report says “is” – or if they haven’t checked the report, then generally that they think “has” is confusing because they see “is” elsewhere.
Hmmm, indeed the Report does. Very pointedly, too.
Yes. The book used to kind of alternate between the two “is” and “has” and it bothered me. I don’t like the emphasis, though. I think there isn’t as much contrast between the two things as other people seem to think.
I think “isa” makes sense for the mathematical object and “hasa” makes sense for the Haskell type.
Maybe. I’m not sure enough about the distinction you’re making. For me, as a learner, the thing that bothered me is that if a type “is” an instance of a typeclass, why do I have to write an instance (or derive one)? If I write a type, it exists before its typeclass instance, and the type declaration is not the same piece of code as its instance declaration. It can exist as a type without any typeclass instances. You can’t do much with it, but it can. So, saying a type is an instance of a typeclass seemed pedagogically misleading to me.
Yes, that’s kinda my thinking.
I mean, I understand what they’re getting at with that, but as a learner I found that a difficult thing to grasp.
Like, there’s the type itself, and the typewithinstances, and they both have the same name. The former has an instance, the latter is an instance. They both work, but “hasa” corresponds more to what you do when you’re programming.
They do, yeah. But to keep things clear, I switched them all to “has”. We do say “this type is a Monoid
” as a kind of synecdoche of “this typewithinstances is a Monoid
.” I should write a form letter response I can send to the people who send in this complaint.
I told you the guy who wants to argue about “is” vs “has” an instance of a type replied, right? I just reread it and noticed that his argument consists of twice repeating verbatim what the Haskell Report says. I don’t think he can articulate what the difference is without using the exact same phrasing.
Does he actually have an argument beyond an appeal to authority?
No.
Just draw circles around some code and be like, “Look: This is a type. This is an instance. The type is clearly not the instance. QED.”
LOL. The usage of “is” started to bother me in particular once I found out that some types, like Integer
, are monoidal but don’t have a Monoid
instance. Because they form monoids under (at least) two different operations.
I still have objections to saying a type is inherently monoidal, or of any other class.
Martin, are we going to argue about monoids again? Addition and multiplication are canonical monoidal operations. I don’t know if ‘canonical’ is a mathematically approved term.
Lists also admit different monoids, but we’ve chosen one as the default. Is concatenation more monoidal than zipping?
I think the only open question to that guy’s point is whether we consider the instance definition to be “part of the type” in some sense.
Yeah, and that’s a reasonable point. I chose “no” for the book to make it more apparent that the instance is a separate piece of code – an implementation! – whether you write it or derive it.
But we kinda pretend it is, right? Which is why we get all antsy about orphans and handwavy about typeclass cohesion.
One day, Chris tweeted this:
Typeclass instances can be very boilerplatey but usually in a satisfyingly shoveitundertheruganddon’tworryaboutitagain sort of way
And the replies rekindled our discussion.
I’m gonna get real tired of answering the question of whether an instance and an instance declaration are the same thing, aren’t I?
Yes. It’s okay with me if you want to switch for Joy. Many people are going to prefer to stick to the Report wording. But to me it’s like shrugging away a chunk of code.
Why does this man feel the right to demand I justify my word choice? I can just not reply, right.
Yes. When people email our book support, I feel compelled to reply, but it’s Twitter, you’re not obligated to anything.
It’s like getting upset when someone refers to a type declaration by calling it a type.
Do you want to write a blog post about this?
A little. But a very short one, focusing not on the Haskell syntax, but on the math definitions. For example, “a semigroup is a set with a binary associative operation”, so it is weird to say that String
is an instance of semigroup. It is a set with a binary associative operation, so it is a semigroup, right?
There’s a semigroup over strings – actually, more than one – isn’t that the more mathematical way to put it? Not that they are or have? Or maybe that they admit a semigroup or form a semigroup under concatenation, I’m not sure. Like, the semigroup is the combination of the set and the operations, and String
can be the “set” part of that.
…If that makes sense.
Now one might rightfully distinguish “is a semigroup”, “is a Semigroup
instance”, and “is an instance of the Semigroup
typeclass.”
What’s the distinction between the last two?
I didn’t mean to say there are three different things, but there are three phrases and you could reasonably choose to draw the line at any point. There could be a difference between the last two if you choose to say “semigroup” for the math concept and “Semigroup
typeclass” for the Haskell thing. But nobody would ever do that consistently so that’s not a good prescription to fight for.
Another weird thing, to me, about saying the type is the instance: a String
has at least two operations under which it forms a semigroup, right? So, okay, it is, or admits, a semigroup. But saying it is an instance of Semigroup
seems strange, from a programming perspective. I suppose it’s the CT idea that we have a structure called Semigroup
and the set String
is an instance, or an object in that category or something?
With the typeclass, as with the category theoretic notion, we don’t really care what the operations are, just that there exists some operation under which it forms this structure. So, that’s all cool, it just seems weird to talk about Haskell that way. When we program, we do care about those operations and how they work, so we care about the instance declaration. But this gets weirder, in my humble opinion, when you’re talking about, say, Num
. What is the algebraic structure called Num
?
Num
is an algebraic structure; it’s like a fucked up ring with some other shit tacked on.
I find it unhelpful the way programmers talk about “an algebra,” but it makes more sense as an algebraic structure – at least to me, although I think this is a question of terminology, not meaning. An algebraic structure is a set with one or more operations defined on it that satisfies a list of axioms. I forget how we ended up defining “an algebra” in haskellbook, but we did try to define it because it was bothering me.
Things like semigroup, group, ring, field, etc., are those algebraic structures, or classifications of algebraic structures?
And so, as we said above, an algebra refers to some operations and the set they operate over. Here again, we care less about the particulars of the values or data we’re working with and more about the general rules of their use.
So, those are algebraic structures. A monoid is an algebraic structure. A monoid is not just a binary associative operation but a structure that includes a set (a type, in Haskell?) and that operation.
Wait, but … so it’s the instance that’s the algebraic structure? Not “monoid” itself but like “the monoid of summed integers”?
I think that’s right. A monoid in general is a structure with a set and a monoidal operation defined for it that follows some axioms or whatever, right? But that’s a monoid in general. A specific monoid, a specific structure, I think it’s right to say, e.g., “the monoid of summed integers” is an algebraic structure. Does that make sense? I do not have high confidence about this.
Yes, and that jibes with the Wikipedia summary.
Another day, another Twitter thread, in which someone said this:
But there are no * or + operators for IO.
But IO
does have a Monoid
. Those are monoidal operators. This seems like a confusion of the operator for the algebra. Wait, IO
is a Monoid
now, right?
Pretty sure it’s a monoid. GHC 8 and up, I think.
Well, that’s sort of making me feel better about saying “has an instance.” That type didn’t used to have a monoidal operation defined for it. But now that it has an instance defined, it is a monoid: a type with the appropriate operation defined over it.
Hmm, Real World Haskell says:
This says that we are declaring a typeclass namedBasicEq
, and we’ll refer to instance types with the lettera
. An instance type of this typeclass is any type that implements the functions defined in the typeclass… The key is that, when you list the types of your functions, you must use that name to refer to instance types… “For all typesa
, so long asa
is an instance ofBasicEq
,isEqual
…,
It’s like they start out saying “a
is an instance type” if it implements the functions, then they elide the “type” and start saying “a is an instance of Eq
” etc. I think I’m more comfortable saying “is an instance type” for “is a type for which an instance is defined” than just “is an instance” which on the surface seems to conflate the type declaration and the instance declaration.
“an instance type”? This is unfamiliar phrasing to me. I need to think about that.
Yeah, it’s unusual phrasing, I think. To me it suggests no one is super comfortable with what we’re talking about here.
I’m bothered immediately by the fact that it’s not necessarily a type. We’re really lacking a word that encompasses both types and type constructors.
Yes, there is that, too. It conflates those two things in a way that is sorta okay when you’re talking about Eq
but not good for Functor
and disastrous for the difference between Monoid
and Alternative
. Maybe “disastrous” is hyperbolic.
I’ve been trying to decide if i should try to include Monad
in that talk proposal about different species of monoids. It might be too much for 30 minutes. I could probably spend 30 minutes only talking about why Monad
is a monoid (in the category of endofunctors). I consider this nonobvious.
I was thinking about that recently. I couldn’t remember what an endofunctor is.
In Haskell, it’s just a functor. So, it’s a monoid of functors, like Alternative
is a monoid of applicative functors. Oh, and it’s “a monad in X is a monoid in the category of endofunctors of X” not just the category of endofunctors.
Right. Quoting Categories for the Working Mathematician: “An endofunctor, T : X → X” (in Haskell, a type constructor of kind * > *
with a Functor
instance).
This phrasing has always bothered me a bit, but I think it’s for similar reasons. It means “a monoid of type constructors that have instances of Functor
(or Applicative
in the case of Alternative
)”, or, if you prefer, type constructors that are functors or applicatives. But I’ve always thought it makes it sound like they are monoids of a sort of function called a functor, almost like they are monoids of the function type, but for a functorfunction. And since both Monad
and Applicative
involve a functor operation, it’s easy to make this mistake. But Monad
and Alternative
are monoids of type constructors that are functors (but Monad
also involves fmapping, whereas Alternative
does not). Applicative
differs from Monad
in where the extra “layer” comes from, so it needs join
instead of (implicitly, I guess) a conjunctive monoid.
During this next conversation, Julie was on airplanes, texting on her phone, wondering why we tolerate phone autocorrects.
I pushed the start of that dialogue about types and instances, if you want to take a look at it.
and it’s really “a monad in X is a monoid in the category of endofunctors of X” not just the category of endofunctors.
I think I missed that the first time around. That… makes a lot more sense. I’ve always read “the category of endofunctors” literally, to mean all endofunctors.
Yeah. I did too for a long time, and still forget it sometimes. Oh, I just had a thought. Is functor (or endofunctor) considered an algebraic structure? That is, a set with a functor operation over it?
I suppose it must be. It’s easy to forget about functor because it’s the best abstraction.
Suddenly it makes more sense to me. I guess a monad in Hask is a monoid in the category of functors of Hask, i.e. the type constructors that have Functor
instances.
There’s rarely any question about “which fmap
” to use; it’s nearly always clear, so we don’t talk about it like we say “a type and a semigroup operator”, but we don’t say “a type constructor and a functor operation” because it’s not “a” functor operation, it’s always “the” obvious/only fmap
implementation.
Right. I think it’s also less clear what the essence of a functor operation is, unlike semigroup/monoid. We don’t seem to talk much about what is a functor the way we do with semigroup/monoid. Joy is going to, though.
Can we say monad is a monoid in the category of type constructors for which there is a functor? That’s what screwed me up, thinking it was a monoid over the values, where really it’s a monoid over the type constructors, right? It’s also hard for me that we end up alternating between the set theory definition of monoid and the category theory definition.
Ooooh, what do you mean? Because I’m not aware of this, I don’t think.
What you and I usually talk about is the set theory notion of monoid: a set, one element of the set being an identity, a binary associative operation on the set.
Because in the category theory notion, there’s not a distinction between conjunction and disjunction and the operation details themselves don’t matter?
Category theory: a monoid is a category with exactly one object. We say “a monoid over X”, sometimes I puzzle between whether X refers to a set, or to the single object in a monoidal category.
I don’t think the notion of a oneobject category is all that useful, though, when we’re talking about Haskell. In the set theory case, I think “a set” means “a type” in Haskell. In the category theory it means “the set of types.” …And type constructors? I think at that level of abstraction the distinction between types and type constructors is irrelevant.
I don’t think the set theory notion is any different in that regard. The set definition of a monoid makes no mention of what the operation details are either.
Can’t quite agree with that.
I think they’re entirely equivalent definitions.
Technically equivalent, yes. But not functionally equivalent, by which I mean they allow us to know and talk about different things. What do you know about a Haskell Monoid
from knowing it’s a oneobject category with arrows pointing only to itself? It’s useful for some things but only after knowing about the set theory notion of monoids.
What do you know about a set theory monoid knowing there’s a binary associative operation, without talking about what that operation is?
Ah but we do know what those operations are in set theory because set theory is Boolean algebra. When we’re talking about sets, we know what the monoidal operations are.
What are they?
Conjunction and disjunction.
But if all you know if that you have a monoid, you don’t know which one it is.
That is true. At that level we don’t care yet. And most (all?) sets, like most types in Haskell, have at least two, a conjunctive and a disjunctive monoid.
The monoid abstraction removes that detail just as much as the category does.
Not as much. CT is a further level of abstraction.
If you map the category arrows to functions that they represent, now you can talk about conjunction and disjunction again.
Then we’re talking about set theory though, at least when the objects are sets. Which is my point. Because now we care about the objects and arrows in that category and what sorts of things they are. They lose some polymorphism in a sense. It’s like the relationship between parametricity and constrained polymorphism a bit.
Yeah, so it does take one restriction: the arrows have to be a set in order to go from category theory monoid to set theory monoid. Nothing stops me from talking about what the arrows in a category represent and using that to reason about the category, does it?
No, it’s just that then you’re not really at the level of abstraction that category theory exists for. Then you’re talking about something more concrete, even if not yet fully concrete.
We can talk about a monoidal category in which the arrows represent additions or multiplications.
Now I think you’re conflating the set theory notion with the category theory one.
But isn’t abstract algebra also the wrong level of abstraction for that?
Wrong for what purpose?
Talking about additions or multiplications solely as a monoidal algebra, not talking about their relationships in a ring or something.
I have lost the point of this. First, you said you were confused because you couldn’t tell sometimes if we were talking about the set theory notion or the category theory notion, but now you appear to be arguing that they’re the same thing.
Equivalent things.
They are and they aren’t; it depends on your point. It depends on what we are talking about them for. Usually when I’m talking about monoids, what I care about is closer to the set theory notion because the category theory notion is not very useful for thinking/talking about the monoidal operations, and it’s not meant to be.
Yeah, that makes sense.
Category theory exists so we can talk about things without caring about what kinds of things they are, only comparing them by what they have in common.
That confused me for a long time. Basically up to just now.
It’s like when we talk about very abstract syntax in linguistics. In some sense it maps to languages that people speak, but we’re abstracting out that far because we want to talk about certain properties of language without caring about the implementation details of how humans construct meaningful sentences and speak or write them.
That’s why I compared it to parametric versus constrained polymorphism. At the level of parametricity, we can’t know, don’t want to know, what the type will be, so there’s little that we do know about what we’re doing. That’s useful for some things. At the level of constrained polymorphism, we know some things (like when we talk about monoids in set theory) but not everything, not yet. It’s not until things become concrete (as they do when we talk about “integers form a monoid under…”) that we really know a lot of detail. We can talk about all those levels, they all have their uses, things can be equivalent between those levels, but we can still make useful distinctions and use those levels of abstraction for different purposes, to make it easier to talk about precisely the thing we care about at the time.
I’m about to get on the next plane. I’m all, “I can’t board right now; I’m too busy arguing about math.”"
I still feel fuzzy about a lot of this.
Yeah, a lot of what I just said was stuff I’ve never thought before, so I don’t know yet if I’m way off base or what. I have thought about how the kind of syntax I used to do is like the category theory of language, though.
I did work on real languages, too, and their specific syntax, but some of what I did is to be able to compare crosslinguistically without talking at all about what the grammar of any particular language looks like. Like category theory is so abstract that triangles and types and integers and functions maybe, depending on the category, can be seen as more similar than different because they’re all just objects. And multiplication and addition are the same too. The difference between them has been abstracted away. And yet it’s relevant to us, as programmers.
In set theory, I suppose the distinction between conjunction and integral multiplication for example has also been abstracted away. So what comparisons it allows us to make and examine and talk about are different from when we talk about more concrete things or category theory notions. There is purpose to all the layers of abstraction, though. At each layer we can “see” and talk about different things and it’s wonderful and exciting.
Category theory is so close to the metal, where “metal” means brain, something fundamental about how our brains work. It’s exciting as hell. Sorry. I’m having a religious moment here.
That said, I think the upshot here is don’t let a category theory sense of what a monoid is dictate what makes sense when we talk about Haskell.
Yes, this sounds good. I was intrigued when I saw Milewski lay out that notion of category theory being how our brains work.
I don’t think I’d heard anyone describe that.
I’ve had this thought about it and syntax being about how our brains really fundamentally work for a long time, like since the first time I heard Snively and Laucher’s talk about types that convinced me to learn Haskell because types are like generative syntax.
He makes a joke about how even the language of “arrows” is an allusion to something humans have been doing for tens of thousands of years.
Well, there’s Lakoff again.
oh?
METAPHORS. Metaphors are like the kan extensions of language.
do
notation
These dialogue snippets are from a series of conversations occurring over months, but we’ve edited them to try to present a mostly coherent presentation of what we think about do
notation and why.
The first time we realized that we have a disagreement came in a conversation about Applicative. This was right before I was going to teach a class about Applicative and was trying to figure out what syntax people unfamiliar with Applicative find easiest to read:
I know every time I unthinkingly put something like
(+) <$> Just 4 <*> Just 5
in front of a student who isn’t ready for it, they freak out.
I just recognize the pattern and mentally translate it into liftA2
.^{1}
Huh, I always forget liftA2
exists. And I think it obscures, although not as bad as do
syntax. So I think the way that will be the most helpful is to present (Just (+ 4)) <*> Just 5
first, then the version using pure
, then the infixonly version.
I think there’s a contrast between learningHaskell and realworldHaskell here.
There often is, but I want to know specifically what you mean.
When I’m writing software, I kinda want to “obscure” in that way with do
notation. The lifting and the applying, they are a distraction from the higherlevel idea I am trying to communicate. I know that thinking this way is often a trap, a seductive mistake. So I don’t know how to justify why I think it’s right in this case.
You think directly lifting/applying is more of a distraction than do
syntax is? This is curiosity, not argumentation, although I find do
syntax distracting.
Yeah, but I don’t know why.
I find it distracting because there is often more I have to read (or I feel like there is) before I know what’s happening. There’s this extra step of renaming stuff. That is not always the case — sometimes do
syntax is ok, depending on what’s inside the block.
This series of tweets led to another conversation:
Is that the book parser you’re writing all in do
notation?
Yeah.
Why? I don’t get it. You sound like it’s obviously better. It’s frustrating for me when people act like like something is obviously better in some way, but I can’t see why. Please tell me what I’m missing.
Hmm. I don’t think I know why it’s more clear to me. For small expressions I don’t think it makes much difference.
I guess what I like is how it lets you only specify the structure of the thing you’re parsing first, and then how to assemble the values, separately, on the last line, rather than having to think about both of those things at once.
I see. Fair enough, at least you have a reason.
Also it eliminates parens. Paren nesting can get excessive in parser expressions. Or perhaps my style could be improved.
I don’t know. For me do
notation is obviously bad, but I seem to be the only person on Earth who thinks so. Which is OK, I guess. I have my reasons, you have yours. But what’s obvious to me isn’t to anyone else I guess, and vice versa.
I’m snarking a little with the obviously bad. It’s not all bad.
Is this one of those things that is a result of learning Haskell first you think?
That thought had crossed my mind. It’s like… you seem to think more natively in syntax trees than the typical programmer, whereas “linear” thinking comes first to me. It’s a comfort to me when I can think of a computation in sequential steps. A parser that consists of parsing a
, then b
, then c
, then combining the results.
That could be related to linguistics too. But it could be I think like that and so am attracted to generative syntax and Haskell because they make sense to the way I already think. Or it could be that doing a lot of syntax made me think a certain way. Or some reinforcing combination.
I do think more sequentially, or linearly, sometimes. And if that’s how I’m thinking of it I sometimes use do
notation. But then I usually end up trying to refactor it once I — well, I’d say “once I understand what I’m really doing.” For me that probably means understand the underlying shape of what I’m doing, i.e. the tree. Part of why I dislike do
notation is for big things where I want to see some intermediate type information, it’s a bit harder, though ScopedTypeVariables
makes it tractable, I suppose.
And it feels to me like it’s hiding the functors, it’s hiding the mappings. I get this is not how everyone feels, and I asked you because usually you can explain why you said something, and it helps.
Yeah, I think it’s hiding the functors. There’s a sort of “magic” feeling to it at times.
I want my functors out in the open, Chris.
A couple days later, as Julie was editing the Parsers chapter of Haskell book, she noticed there is a ton of do
notation in that chapter. And that led to another conversation:
I just noticed there’s do
notation all over the Parsers chapter. I didn’t have much of an opinion about do
syntax at the time we wrote it; this is a developing crankiness on my part. Take this for example:
parseSection :: Parser Section
parseSection = do
skipWhitespace
skipComments
h < parseHeader
skipEOL
assignments < some parseAssignment
return $
Section h (M.fromList assignments)
I think that’s a good example of something that is easier for most people to read in do
notation than it otherwise would be.
Yeah. Because it has a lot of “skips”?
Yeah and not so much binding of results to different names, names that you then have to look through the rest of the block to see where they get used.
A little while later:
“Functions are in my comfort zone; syntax that hides them takes me out of my comfort zone.”
— Paul Hudak on do
notation. I am in good company with my bad opinions!
On the other hand, I like this perspective as well.
Maybe it’d have been culturally better if do
notation had been an extension.
I don’t know. It’s probably correct that it’d have even less wide adoption now without do
syntax. But, uh, avoid success at all costs, right?
I think thinking of it as an advanced feature might be helpful. I agree it obscures what’s going on, and when I use it it’s often in situations where I don’t want to think about what’s going on, not at that low level.
Are you suggesting I think at low levels? devilish grin. More seriously, yeah, I get that.
Making it an extension would make it clear that you don’t need it for anything which might help.
Yes. I’m just doing this thing where I’m surrounded by people who seem to all agree on their love of do
notation, so I feel like my opinion is actually wrong but I just don’t know it because I’m not a real enough programmer. So I’m seeking validation that it’s OK to have my opinion. You already have the confidence to have your opinion, but I do not.
My opinions change a lot, though.
It doesn’t matter, because you have great confidence in them while you hold them! But Paul Hudak is on my team here and he is a very real programmer — or was, before he died.
It seems we end up discussing Chris’s controversial tweets a lot. This conversation started from this series of tweets. It turns out that Chris has a lot of opinions about for
and traverse
and we’re always ready to argue with each other (amicably):
Ha, I forgot that
traverse print [1..10]
prints the list of unit values at the end.
Yeah, it really should have been traverse_
.
Yes, it was a mistake but now I got me a list of units.
I ran it in the REPL, and it looked good enough. I didn’t notice the result value.
How did you miss the big list of units? Heh.
If I had to do it all over again I’d
for_ [1..10] print
I like the foldMap
one better. I forget we have a for_
and it scares me.
Yes, foldMap
is better. Gabriel wins.
I’ve never seen anyone write Haskell the way you do sometimes. It’s both frightening and impressive.
Our shake
config uses for_
a couple times. And most of the main
of haskelltotex
is in a for_
.
Figures. You would do that.
Javaborn, for
loops in my blood.
I think all the manners of traversals is something we can help clear up in the book.
That’s funny what you said, though, since for
is flip traverse
, isn’t it? That’s why traverse
is clearly easier. To be honest, things like for
irritate me for other, completely unrelated reasons as well.
So how would you rewrite this expression?
for_ (Map.toList (fileSnippets file)) $ \(name, snippet) > do
putStrLn $
"File " <> name <> "  " <>
tshow (Foldable.length snippet) <> " paragraph(s)"
writeFile (outDir </> Text.unpack name) $ fold
[ "% Generated from "
, Text.pack (takeFileName inFile)
, "\n\n"
, "% Generated by haskelltotex"
, Text.pack (showVersion version)
, "\n\n"
, renderSnippet snippet
, "\n"
]
I’m not saying I would. I’m sure there are times when it makes sense. But if you don’t know for_
exists, then you’d find a way, right? So, how would you write it if you didn’t know for_
existed?
In that case I’d introduce a named function and reverse the order of the arguments.
Sure, that’s what I would have done. I do that a lot anyway because it’s easier for me. It is probably a failure of my brain.
It’s usually easier.
I do not want to manipulate big things in my head, all at once. I need little things, little parts. It’s similar to what you said about why you like do
for your parsers: you can think one step at a time instead of having to consider the whole progression.
for_
might be a special case, for people like me used to it being a special construct in other languages.
Yeah, that’s part of my worry about it.
Yeah, that makes sense.
If all my Java and Scala dev students find out about it, they will assume it’s the same thing they’re used to and not learn about traverse
.
I wonder if that’s part of the root of our difference about do
notation, too. They’re things that make it easier to write large expressions.
For sure it is.
When the goal should be: don’t do that.
Pedagogically, I want people to be able to see how the types work out. I want them to get used to thinking in types before they start relying on things like that hide that information from them. This may not apply to for_
but it’s not great with huge do
blocks. You just end up with a big incomprehensible chunk. Like in Java. I’ve seen people do that.
Big do
blocks also lead to situations where you just have no idea what any of the types are
so type annotations help — at which point, you might as well also give them a name and break them off.
Yes, exactly. I think most of the conflict we have like this is coming essentially from two things: 1. Our backgrounds (you from Javaland, me just not knowing anything about programming except a little bit about Haskell), and 2. I’m always thinking about the pedagogical ramifications rather than what I want when I write code. Or, heaven forbid, software.
Yes, I’m rewriting the haskelltotex
thing in smaller pieces without do
or for_
and it looks nice.
It wasn’t a criticism of your code. You don’t have to do this.
The bigger improvement in that code was I had a bunch of stuff that could be moved out of IO
functions.
I’ve been thinking about starting a FITEME series of blog posts, including one about do
notation, so i may quote you:
“Don’t focus on connecting Monad and Applicative to do
notation; that’s true, but it’s a distraction.”^{2}
Hue hue.
Bonus:
I am distracted today. I’ve made fantastic progress at learning some things I really wanted to know. They just aren’t relevant at all to the actual work i need to get done.
Surely you’ve noticed this about learning computer things, though: All the irrelevant things end up being part of some picture. It all ends up being useful toward the gestalt computer.
Yessss. You are my distraction enabler.
^{1} UPDATE: This is no longer true.
Secondlanguage learners first speak by translating what they want to say from their native tongue to the target language. That process is slow and errorprone. It’s better, when you can, to “think in” the target language; the same is true of trying to understand Haskell via connections to other programming languages.
Programmers usually learn “Haskell as a second language” and try to connect its concepts to things they understand from past experience. Java programmers ask whether Haskell’s typeclasses are like Java’s interfaces. This is okay for initial intuition, but it obscures some important differences. Better, as you become able, to learn to “think in Haskell.”
Most answers to this question assume the reader knows Java but not Haskell. Haskell natives are scarce, but one of the authors here learned Haskell as a first programming language. So this post will assume the opposite, and in doing so we hope to demonstrate that Java is just as difficult to a Haskell native as Haskell is to a Java native.
First let’s do a quick Haskell recap to be clear on terminology.
Haskell data structures are algebraic datatypes (specifically, sums and products) defined with the data
keyword.^{1} A typeclass declaration gives type signatures for operations, which are implemented in different ways by typeclass instances that define the implementations for a specific type. We can write functions that operate over any type that has an instance of some typeclass X
and use the operations that X
defines; when such a function is used with some particular type, the type determines which implementation is used. This is our way to abstract over types.
Defines a data structure  Defines an abstraction over types  

Algebraic datatype 
✓


Typeclass 
✓

This is an example of a typeclass:
class Eq a where
(==), (/=) :: a > a > Bool
x /= y = not (x == y)
Class methods are the operations that a typeclass declares. The Eq
typeclass has two methods, (==)
and (/=)
.
For operations that are derivable from other methods in the class, you can write a minimal set of implementations and rely on the default methods to fill in the rest of the operations. The example above has one default method, (/=)
.
The typeclass instance is where the operations described in the typeclass declaration are implemented for a particular type. An instance is a unique relationship between a type and a typeclass.
This is an instance of the Eq
typeclass instance for Bool
(a type) that defines (==)
and takes the default implementation of (/=)
:
instance Eq Bool where
True == True = True
False == False = True
_ == _ = False
A type consists of the combination of a data structure and its associated typeclass instances.
We might start explaining interfaces to our Haskell native by highlighting the similarities with a simplified model of Java, omitting some constructs that don’t have close Haskell analogs.
In our simplified Java, a type is either an interface or a class (specifically, a final class, which we’ll discuss later. An interface gives type signatures for operations which are implemented in different ways by each class, and the class defines the implementations for a specific subtype. We can write code that interacts with a value belonging to interface X
and uses the operations that X
defines; the class to which the value belongs determines which implementation is used. This is our way to abstract over types — like a typeclass!
Defines a data structure  Defines an abstraction over types  

Class 
✓


Interface 
✓

But this obscures a number of differences between this and the Haskell system.
A method is sort of like a toplevel function definition in Haskell, but it is defined as part of a type, and it has an implicit first argument of that type.
This is an example of an interface:
interface Comparator<A> {
Ordering compare(A x, A y);
boolean lessThanOrEqual(A x, A y) {
Ordering o = compare(x, y);
return o == LT  o == EQ;
}
}
This Comparator
interface has two methods, compare
and lessThanOrEqual
. The compare
method is just a type signature with no implementation. lessThanOrEqual
provides an implementation, so it is called a default method, similar to the terminology used for Haskell typeclasses.
This is an example of a class that implements that interface:
class IntComparator implements Comparator<Integer> {
boolean reverse;
Ordering compare(A x, A y) {
if (x.equals(y))
return EQ;
else if ((x < y) ^ reverse)
return LT;
else
return GT;
}
}
A class has fields that define a data structure (specifically, the structure is the product of its fields, and Java has no direct way to encode sum types). The IntComparator
class above has one field, reverse
. The class also implements the compare
method from Comparator
.
The relationship between classes and interfaces is subtyping. A class definition can specify that it implements any number of interfaces, and it inherits the interfaces’ methods. (More jargon: We say that the class is a subtype of each interface, and the interfaces are its supertypes.)
Notice that both Haskell and Java have a concept called method which consists of an overloaded name and a type signature. The differences, which we’ll expand on later lie in the discrepancies between the exact definitions of method in the two languages, and the specific ways in which types are related to the abstractions over them.
We wouldn’t be giving full justice to the complexity of Java’s type system unless we also brought up the other kinds of classes. Subtyping gets weird when we introduce types that both have fields and permit subtypes (highlighted in orange below). These are both data structures and abstractions over types.
Complete data structure
(is instantiable)

Partial data structure
(can have fields)

Abstraction over types
(can have subtypes)



Final class 
✓

✓


Class 
✓

✓

✓

Abstract class 
✓

✓


Interface 
✓

The fields in an abstract class constitute “part of” a data structure, which can be augmented by additional fields in a subtype.
Classes that are final cannot have subtypes. A type that is nonfinal serves as an abstraction over its subtypes in the same way that an interface does.
Classes that are not abstract are instantiable, meaning that we can call their constructors to create instances of the class.
Types in Java serve as, among other roles, templates for defining other types. A type can be “incomplete” in the sense that it doesn’t define all of the things that you would need in order to actually use it. Such a type only exists in order to build subtypes from it. Its subtypes fill in these blanks, so to speak. If a type has no holes — no pieces of its definition left undefined — only then is the type instantiable.
We didn’t mention these things in our original description of interfaces to the native Haskeller because none of these things have good analogs in Haskell. But they’re important to understanding Java on its own terms.
From 30,000 feet up, these two language features have a resemblance: They define abstractions that codify similarities between types, and these abstractions permit generic code that works across multiple types.
Translation between the two languages can work, but not always.
Consider this typeclass Semigroup
:
class Semigroup a where
(<>) :: a > a > a
and a function triple
that uses a Semigroup
constraint:
triple :: Semigroup a => a > a
triple x = x <> x <> x
We can consider two ways to write this code into a Java using interfaces.
The first is a direct translation:
interface Semigroup<A> {
A append(A x, A y);
}
Instances of this Semigroup
interface are the semigroups themselves. Note that the relationship between a Semigroup
instance and its type parameter A
is not unique; there’s nothing stopping us from creating multiple semigroups for any type. When we use this interface, then, we have to explictly pass a Semigroup
argument to specify which semigroup we’re using.
<A> A triple(A x, Semigroup<A> semigroup) {
return semigroup.append(semigroup.append(x, x), x);
}
If we want to create a unique association between a type and a semigroup, and not have to manually pass the semigroup instance everywhere we use it, we can do a little refactor. Since the x
parameter has type A
, we can remove it and instead let types with semigroups implement the interface, turning the explicit x
parameter into the implicit this
parameter that refers to the instance of the class that A
that implements Semigroupal<A>
.
interface Semigroupal<A> {
A appendTo(A y);
}
<A extends Semigroupal<A>> A triple(A x) {
return x.appendTo(x).appendTo(x);
}
That is the more natural way a native Java user thinks about abstraction. But the set of Haskell typeclasses that can be converted to Java interfaces in this way is limited. The conversion from Semigroup
to Semigroupal
in this example relied on append
having an argument of type A
. What happens when that isn’t the case?
Consider another typeclass, Monoid
:
class Semigroup a => Monoid a where
mempty :: a
We can write the direct translation of this into Java as before:
interface Monoid<A> extends Semigroup<A> {
A mempty();
}
But the more idiomatic Java variation simply cannot be written.
interface Monoidal<A> extends Semigroupal<A> {
???
}
This thing that Java cannot do is called return type polymorphism. The implementation of mempty
is implied not by the type of an argument to the function, but by the type that we need to get from the function.
We’re not here to criticize Java but to challenge the popular wisdom that Haskell is more difficult to learn. If Haskell is your first language, then Java is hard, Java is alien. Java has a steep learning curve too; many Javahabituated Haskell learners forget that.
Some of the difficulty in crossing between these languages is just the bramble of shared terminology with different meanings: constructor, method, type, concrete type, class, instance, field, polymorphic. But some of it is a deep paradigm conflict: Haskell has full compiletime type resolution, Java has subtyping and dynamic dispatch.
It’s tempting to cling to your understanding of one while you learn the other. But the abstractions that typeclasses and interfaces provide over types are disparate, and it is difficult to reconcile them. You’re better off making a clean break from what you know and starting over with a full embrace of the language you’re learning.
Footnotes
^{1} Usually it’s data
. There are other keywords that can introduce types but their effects on how types are defined and relate to typeclasses are minimal. Some Haskell features, such as existentiallyquantified types, are more complex, but these are not core concepts.
We are two friends who love Haskell.
We understood the fundamental concepts of Haskell, but still felt there was so much to learn.
We were ready to start on interesting projects but didn’t know our way around the Haskell ecosystem.
We realized the Prelude
has some hidden dangers and wondered what, if anything, Haskellers do about that.
We spent late nights wishing we had a glossary of the terminology we hear Haskellers toss around rather casually.
We saw that the world was rich in monoids and functors and we wanted to befriend them all.
We wanted examples. We wanted the language extensions we heard about to be discoverable. We wanted to know if a monad is actually just a monoid in the category of endofunctors and what that means.
If you recognize yourself in any of this, then we have good news:
Friend, we are writing this book for you.
We’ve spent a few months figuring out what book we, as Haskell users, want to see. The Joy of Haskell is a work in progress, the culmination of those discussions, the kind of book we want to help us write better, happier Haskell.
We chose the title because joy describes our feeling when we discover new things in Haskell, when we write Haskell, when we talk to our friends about Haskell.
Joy is the feeling we want to infuse into the community. Joy is what we want you to experience when you write Haskell.
We aim to make not just a book, but a work of art. A free verse celebration of math and code. An illuminated manuscript of functional paradigms. A playlist of carefully selected love songs dedicated to this language that, well, sometimes plays a little hard to get.
Your learning path as you go deeper into Haskell is unique to you and what you want to do. Maybe it’s the category theory that grabs your interest; maybe you want examples for some of Haskell’s libraries to facilitate writing topnotch code. The Joy of Haskell will be formatted in such a way that you can turn from one page to the next to follow one path, or jump to any of the crossreferenced, related material.
The Joy of Haskell probably shouldn’t be the first Haskell book you work through. This book will assume you already understand the fundamental concepts. This book will help you find your way from there.
If you are looking for a book to learn Haskell from the ground up, we recommend Julie’s previous book, Haskell Programming from First Principles, but there are other options available.
]]>