jml's notebook

Thoughts on Rich Hickey’s “Maybe Not”

Thoughts on Rich Hickey’s “Maybe Not”

Attention conservation notice: interesting to the degree that you care about my opinion about Rich Hickey’s opinion. You need to watch the talk first.

Written up version of a rant on Slack.

I got nerd-sniped into watching Rich Hickey’s “Maybe Not” talk. It’s about an hour long, but can be easily enjoyed at double speed.

The talk is largely about the shortcomings of optional types, like Haskell’s Maybe, and outlines an alternative approach.

Its structure is more implicit than explicit, so it’s hard to respond in a structured way. What follows is a loosely structured set of reactions to it, from my point of view as a fan of both Haskell and Rich Hickey.

It’s not meant to be a formal rebuttal, and I’m not observing scholarly quoting discipline or references. Lots of my opinions could be due to my own lack of understanding. This is my notebook and I’ll do what I want.

tl;dr: I don’t think he understands how to write Haskell programs, nor much about type theory.

Product types

Hickey says it’s wrong to call Either a sum type, because it’s not commutative or associative. In this, he’s both right and wrong.

He’s right in that Either Int String and Either String Int are different types. But in another sense, he’s completely wrong. The two types are isomorphic.

There are a lot of interesting things one could say about this. For example, elsewhere, Matt Parsons explores the consequences of Either not being associative or commutative in the context of error handling. I’d be really interested to see support for anonymous sum types in Haskell that respected this!

Also, “isomorphic” is always about circumstances—a particular context, a particular point of view—so we could have an interesting conversation about when it’s the right point of view for types, and when it’s not.

Variance

I think he made a very interesting point about covariance & contravariance, although he didn’t use those words. Instead he spoke of restricting return values and expanding allowed arguments.

But there are interesting questions!

If you have f :: a -> Maybe b, and you figure out a way to write f such that it will never return Nothing, what happens next? What should happen next? Is it right that we change the signature and force all the call sites to update? What other ways might there be to adjust the contract without forcing all callers to adapt instantly?

Hickey dismisses having to update all the call sites out of hand, but maybe there’s more to be explored there.

Similarly with changing arguments to be more generous, e.g. g :: a -> b changing to g :: Maybe a -> b.

I hadn’t thought about it before, but there’s a sense in which a is a subtype of Maybe a.

How types communicate

Hickey’s jibe at reverse :: [a] -> [a] is what made me think he doesn’t know much about type theory.

He says it doesn’t communicate anything, and that what you want is a spec that says that the return value is derived from the argument.

But! But! But! Theorems for Free! Published in 1989, it’s a famous, highly readable paper about how this is exactly the case!

Roughly, given type reverse :: [a] -> [a], reverse can only do a narrow range of things: - crash - return a constant [a], regardless of input - return a list with the original elements repeated somehowThe original post omitted this option. Thanks to DRMacIver for pointing out the omission. - return a sublist of the input list, possibly reorded, but the new ordering can be based only on positions, not values

That last is exactly what Hickey says that you want a spec to do.

He also says something about how a spec will help you generate tests way better than any type will. I honestly have no idea what he could possibly mean by that.

Records

Say you have:

data Car = Car { make :: String, model :: Maybe String, year :: Maybe Int }

Hickey contrasts this with a Clojure map, which is heterogeneous. He says that in Clojure maps, the keys are also functions, and implies this is much better than what Haskell does.

But in the above example, make is a function, make :: Car -> String. Thus, I’ve got no idea what his point is.

From this same example, he makes much hay over Maybe Int not really telling you anything about what a year is. He’s right! That’s why most Haskellers would do something like this:

newtype Make = Make String
newtype Model = Model String
newtype Year = Year Int

data Car = Car { make :: Make, model :: Maybe Model, year :: Maybe Year }

which starts to look a lot like the examples in spec.

Put another way, he doesn’t explain why formally describing free-floating attributes is better than formally describing values.

Specs & selection

This seems like a pretty cool idea if you’re committed to open systems.

I’m going to be cheeky, then I’m going to be charitable.

It looks like he has complected things. He’s constructed himself a problem of “how do I aggregate data and use it as an aggregate when I only need some of it?” How about… you disaggregate it? (I don’t really like using this style, but I just endured an hour of it!)

The Haskell way of saying “I’ve got a function that needs a make and definitely needs a model but doesn’t need a year is this: “

f :: Make -> Model -> whatever

Being more charitable, maybe he actually has a reason for why this isn’t good. If so, it wasn’t made clear to me in the talk.

nil

The talk opens with Hickey being flippant about Sir Tony’s “billion dollar mistake”. Flippancy is basically not worth responding to.

However, I really would have liked to see him circle back to this opening point. What should we do in the presence of possible nulls? How do specs help?

Unfortunately, to explain what I mean, I have to do the work that he should have done.

Say you have a list of cars as above, and you have to do something with them based on their make & model (e.g. as f above). How do you handle the fact that sometimes they won’t have models?

This isn’t a Haskell problem. If a car is implemented as a map, then sometimes the map won’t have a model.

This is where I struggle, because Haskell’s type notation is really good, and I find it hard not to reach for it. But on the other hand, Hickey hasn’t provided an alternative.

Let me try with Haskell:

g :: [Car] -> [whatever]
g cars = mapMaybe doTheThing cars
  where
    doTheThing car = case model car of
      Nothing -> Nothing
      Just model -> Just (f (make car) model)

That is, if f needs a Make, and you have a value that might not have a Make, you have to do a runtime check to decide whether to call f, and you have to decide what to do when you don’t have a Make.

It would be really interesting if spec let you do something genuinely different.

Meta

Rich Hickey’s excellent talk Hammock-Driven Development introduced me to Polya, which in turn introduced me to things like varying notation as an explicit problem-solving technique, and the importance of writing down problem statements. In that talk, Hickey strongly emphasised reading about an area before haring off and building a solution.

In that light, it was really discouraging to watch “Maybe Not”, a talk that rambles about a half-defined problem, ignores the literature, and swaps formal notation for put-downs and crummy analogies.