39

In a pure functional language, the only thing you can do with a value is apply a function to it.

In other words, if you want to do anything interesting with a value of type a you need a function (for example) with type f :: a -> b and then apply it. If someone hands you (flip apply) a with type (a -> b) -> b, is that a suitable replacement for a?

And what would you call something with type (a -> b) -> b? Seeing as it appears to be a stand-in for an a, I'd be tempted to call it a proxy, or something from http://www.thesaurus.com/browse/proxy.

luqui
  • 59,485
  • 12
  • 145
  • 204
Mark Bolusmjak
  • 23,606
  • 10
  • 74
  • 129
  • 4
    And then you can take `f = id` and get the actual instance of `a`, or am I missing something. – harold Jul 24 '17 at 19:17
  • @harold indeed! That was right in front of my face. So, `(a -> b) -> b` is isomorphic to `a`. – Mark Bolusmjak Jul 24 '17 at 19:20
  • 7
    There is an ambiguity here. Do we mean the polymorphic type `forall b. (a -> b) -> b` or do we mean `(a -> b) -> b` for a specific `b`? – luqui Jul 24 '17 at 19:41
  • 8
    Cf. [Yoneda lemma](https://en.wikipedia.org/wiki/Yoneda_lemma#General_version) with `F = Id`. – gallais Jul 24 '17 at 20:01
  • sure, it's "iso", but only if ignoring control is not a big deal. but that is a big deal. your `a` might be on the other side of the planet, locked in a cage, whose key is in a well, held by an old man, etc.. – nicolas Jul 25 '17 at 22:09
  • 1
    "And what would you call something with type `(a -> b) -> b`" -- FWIW, I like "suspended computation", or "suspension", taking my cue from [this classic answer](https://stackoverflow.com/a/3323122/2751851). – duplode Apr 22 '18 at 13:26

3 Answers3

45

luqui's answer is excellent but I'm going to offer another explanation of forall b. (a -> b) -> b === a for a couple reasons: First, because I think the generalization to Codensity is a bit overenthusiastic. And second, because it's an opportunity to tie a bunch of interesting things together. Onwards!

z5h's Magic Box

Imagine that someone flipped a coin and then put it in a magic box. You can't see inside the box but if you choose a type b and pass the box a function with the type Bool -> b, the box will spit out a b. What can we learn about this box without looking inside it? Can we learn what the state of the coin is? Can we learn what mechanism the box uses to produce the b? As it turns out, we can do both.

We can define the box as a rank 2 function of type Box Bool where

type Box a = forall b. (a -> b) -> b

(Here, the rank 2 type means that the box maker chooses a and the box user chooses b.)

We put the a in the box and then we close the box, creating... a closure.

-- Put the a in the box.
box :: a -> Box a
box a f = f a

For example, box True. Partial application is just a clever way to create closures!

Now, is the coin heads or tails? Since I, the box user, am allowed to choose b, I can choose Bool and pass in a function Bool -> Bool. If I choose id :: Bool -> Bool then the question is: will the box spit out the value it contains? The answer is that the box will either spit out the value it contains or it will spit out nonsense (a bottom value like undefined). In other words, if you get an answer then that answer must be correct.

-- Get the a out of the box.
unbox :: Box a -> a
unbox f = f id

Because we can't generate arbitrary values in Haskell, the only sensical thing the box can do is apply the given function to the value it is hiding. This is a consequence of parametric polymorphism, also known as parametricity.

Now, to show that Box a is isomorphic to a, we need to prove two things about boxing and unboxing. We need to prove that you get out what you put in and that you can put in what you get out.

unbox . box = id
box . unbox = id

I'll do the first one and leave the second as an exercise for the reader.

  unbox . box
= {- definition of (.) -}
  \b -> unbox (box b)
= {- definition of unbox and (f a) b = f a b -}
  \b -> box b id
= {- definition of box -}
  \b -> id b
= {- definition of id -}
  \b -> b
= {- definition of id, backwards -}
  id

(If these proofs seem rather trivial, that's because all (total) polymorphic functions in Haskell are natural transformations and what we're proving here is naturality. Parametricity once again provides us with theorems for low, low prices!)

As an aside and another exercise for the reader, why can't I actually define rebox with (.)?

rebox = box . unbox

Why do I have to inline the definition of (.) myself like some sort of cave person?

rebox :: Box a -> Box a
rebox f = box (unbox f)

(Hint: what are the types of box, unbox, and (.)?)

Identity and Codensity and Yoneda, Oh My!

Now, how can we generalize Box? luqui uses Codensity: both bs are generalized by an arbitrary type constructor which we will call f. This is the Codensity transform of f a.

type CodenseBox f a = forall b. (a -> f b) -> f b

If we fix f ~ Identity then we get back Box. However, there's another option: we can hit only the return type with f:

type YonedaBox f a = forall b. (a -> b) -> f b

(I've sort of given away the game here with this name but we'll come back to that.) We can also fix f ~ Identity here to recover Box, but we let the box user pass in a normal function rather than a Kleisli arrow. To understand what we're generalizing, let's look again at the definition of box:

box a f = f a

Well, this is just flip ($), isn't it? And it turns out that our other two boxes are built by generalizing ($): CodenseBox is a partially applied, flipped monadic bind and YonedaBox is a partially applied flip fmap. (This also explains why Codensity f is a Monad and Yoneda f is a Functor for any choice of f: The only way to create one is by closing over a bind or fmap, respectively.) Furthermore, both of these esoteric category theory concepts are really generalizations of a concept that is familiar to many working programmers: the CPS transform!

In other words, YonedaBox is the Yoneda Embedding and the properly abstracted box/unbox laws for YonedaBox are the proof of the Yoneda Lemma!

TL;DR:

forall b. (a -> b) -> b === a is an instance of the Yoneda Lemma.

Rein Henrichs
  • 15,437
  • 1
  • 45
  • 55
  • 2
    Great stuff. I'm now wondering about a `type ApplicativeBox f a = forall b. f (a -> b) -> f b` – minimalis Jul 25 '17 at 12:04
  • 1
    For those interested: https://www.reddit.com/r/haskell/comments/6pgsy3/is_having_a_a_b_b_equivalent_to_having_an_a/dkpcxvt/ – minimalis Jul 25 '17 at 21:27
  • If I'm grokking the discussion on reddit: not only is `b` allowed to be same as `a`; it can _only_ be `a` (if we exclude non-terminating expressions/bottom values). So type `forall b.(a -> b) -> b` can only get instantiated as `(a -> a) -> a`. Which is obviously equivalent to `a`. – AntC Jul 26 '17 at 02:40
  • No, that's not true. The caller can choose any `b` they want. Try it! – Rein Henrichs Jul 26 '17 at 02:41
  • @ReinHenrichs, in your answer: "we can't generate arbitrary values in Haskell". So we can't generate an arbitrary value for some arbitrary type `b`. We could choose `b ~ Int`; then we get `(a -> Int) -> Int`. We could only satisfy that with some constant function that ignores the `a` (say `const 17`). That's not type `forall b.(a -> b) -> b`. – AntC Jul 26 '17 at 03:09
  • No, that's not true. We can't generate *arbitrary* values, but we aren't trying to generate arbitrary values when we give the box a function. The function generates the values. Consider: `box True (bool 1 2)`. This is literally just `bool 1 2 True`. What the box *can't* do is give back `3` or some other arbitrary value. It can only give you a (non-bottom) value by applying the function. – Rein Henrichs Jul 26 '17 at 03:11
  • Now you've instantiated `a ~ Bool`. So we have `forall b. (Bool -> b) -> b`. I'm not seeing how this is parametric in `a` and `forall b.`. You say "the box maker chooses `a` and the box user chooses `b`." The box user has to know 'in advance' what the box maker has chosen for `a`. Then we have to overload the function (or something) to depend on the choice for `a`. – AntC Jul 26 '17 at 03:34
  • 2
    The box user must know what the box maker has chosen for `a` because the box maker has already chosen `a` when they made the box. Anyway, there's no point in arguing about this when you could convince yourself by spending 30 seconds in GHCi. – Rein Henrichs Jul 26 '17 at 03:36
  • Reading that something is "just `flip ($)`", I just *have* to add the observation (because I was so amazed when I first realized it) that `($)` is just `id` restricted to function types. – Christian Sievers Jul 26 '17 at 12:16
  • @ReinHenrichs, SO was getting grumpy about our exchange, and suggested we take it to a chat room. I've set one up (I hope ;-). All welcome! https://chat.stackoverflow.com/rooms/150304/https-stackoverflow-com-questions-45287954-is-having-a-a-b-b-equivalent-to-havi – AntC Jul 27 '17 at 13:13
  • BTW the `unbox . box = id` proof is the easy one. `box . unbox = id` requires a parametricity assumption, and so is not a similarly trivial proof. – luqui Jul 02 '18 at 20:35
  • @luqui why do you think I did that one? ;) – Rein Henrichs Jul 14 '18 at 19:59
26

This question is a window into a number of deeper concepts.

First, note there is an ambiguity in this question. Do we mean the type forall b. (a -> b) -> b, such that we can instantiate b with whatever type we like, or do we mean (a -> b) -> b for some specific b that we cannot choose.

We can formalize this distinction in Haskell thus:

newtype Cont b a = Cont ((a -> b) -> b)
newtype Cod a    = Cod (forall b. (a -> b) -> b)

Here we see some vocabulary. The first type is the Cont monad, the second is CodensityIdentity, though my familiarity with the latter term isn't strong enough to say what you should call that in English.

Cont b a can't be equivalent to a unless a -> b can hold at least as much information as a (see Dan Robertson's comment below). So, for example, notice that you can never get anything out of ContVoida.

Cod a is equivalent to a. To see this it is enough to witness the isomorphism:

toCod :: a -> Cod a
fromCod :: Cod a -> a

whose implementations I'll leave as an exercise. If you want to really do it up, you can try to prove that this pair really is an isomorphism. fromCod . toCod = id is easy, but toCod . fromCod = id requires the free theorem for Cod.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • 3
    As gallais hinted at, `Codensity Identity a` also happens to be isomorphic to `Yoneda Identity a` (`Yoneda f a = forall b. (a -> b) -> f b`), so this is a good place to start learning about the Yoneda lemma and Kan extensions. (It’s worth pointing out that this is a natural transformation from `(->) a` to `f`.) You can also think of Yoneda as corresponding to a kind of Church/CPS encoding of a functor. I also don’t know what you’d call that in plain English, haha – Jon Purdy Jul 24 '17 at 23:31
  • 2
    I would probably say that `cont b a` can be equivalent to `a` even if `b` contains less information than `a`. What matters is how much information `b^a` can hold. For example any countable and recursively enumerable type `a` with equality is equivalent to `cont a Bool` and if a type is bounded and supports interval bisection (e.g functions taking natural numbers to $\{0,1\}$, i.e. Computable real numbers in [0,1] (roughly)) then it is isomorphic to `Cont a Ord`. – Dan Robertson Jul 25 '17 at 10:54
  • @DanRobertson, great observation! I made a note of it in the answer. – luqui Jul 25 '17 at 11:07
13

The other answers have done a great job describing the relationship between the types forall b . (a -> b) -> b and a but I'd like to point out one caveat because it leads to some interesting open questions that I have been working on.

Technically, forall b . (a -> b) -> b and a are not isomorphic in a langauge like Haskell which (1) allows you to write an expression that doesn't terminate and (2) is either call-by-value (strict) or contains seq. My point here is not to be nitpicky or show that parametricity is weakened in Haskell (as is well-known) but that there may be neat ways to strengthen it and in some sense reclaim isomorphisms like this one.

There are some terms of type forall b . (a -> b) -> b that cannot be expressed as an a. To see why, let's start by looking at the proof Rein left as an exercise, box . unbox = id. It turns out this proof is actually more interesting than the one in his answer, as it relies on parametricity in a crucial way.

box . unbox
= {- definition of (.) -}
  \m -> box (unbox m)
= {- definition of box -}
  \m f -> f (unbox m)
= {- definition of unbox -}
  \m f -> f (m id)
= {- free theorem: f (m id) = m f -}
  \m f -> m f
= {- eta: (\f -> m f) = m -}
  \m -> m
= {- definition of id, backwards -}
  id

The interesting step, where parametricity comes into play, is applying the free theorem f (m id) = m f. This property is a consequence of forall b . (a -> b) -> b, the type of m. If we think of m as a box with an underlying value of type a inside, then the only thing m can do with its argument is apply it to this underlying value and return the result. On the left side, this means that f (m id) extracts the underlying value from the box, and passes it to f. On the right, this means that m applies f directly to the underlying value.

Unfortunately, this reasoning doesn't quite hold when we have terms like the m and f below.

m :: (Bool -> b) -> b
m k = seq (k true) (k false)

f :: Bool -> Int
f x = if x then ⊥ else 2`

Recall we wanted to show f (m id) = m f

f (m id)
= {- definition f -}
  if (m id) then ⊥ else 2
= {- definition of m -}
  if (seq (id true) (id false)) then ⊥ else 2
= {- definition of id -}
  if (seq true (id false)) then ⊥ else 2
= {- definition of seq -}
  if (id false) then ⊥ else 2
= {- definition of id -}
  if false then ⊥ else 2
= {- definition of if -}
  2

m f
= {- definition of m -}
  seq (f true) (f false)
= {- definition of f -}
  seq (if true then ⊥ else 2) (f false)
= {- definition of if -}
  seq ⊥ (f false)
= {- definition of seq -}
  ⊥

Clearly 2 is not equal to so we have lost our free theorem and the isomorphism between a and (a -> b) -> b with it. But what happened, exactly? Essentially, m isn't just a nicely behaved box because it applies its argument to two different underlying values (and uses seq to ensure both of these applications are actually evaluated), which we can observe by passing in a continuation that terminates on one of these underlying values, but not the other. In other words, m id = false isn't really a faithful representation of m as a Bool because it 'forgets' the fact that m calls its input with both true and false.

The problem is a result of the interaction between three things:

  1. The presence of nontermination.
  2. The presence of seq.
  3. The fact that terms of type forall b . (a -> b) -> b may apply their input multiple times.

There isn't much hope of getting around points 1 or 2. Linear types may give us an opportunity to combat the third issue, though. A linear function of type a ⊸ b is a function from type a to type b which must use its input exactly once. If we require m to have the type forall b . (a -> b) ⊸ b, then this rules out our counterexample to the free theorem and should let us show an isomorphism between a and forall b . (a -> b) ⊸ b even in the presence of nontermination and seq.

This is really cool! It shows that linearity has the ability to 'rescue' interesting properties by taming effects that can make true equational reasoning difficult.

One big issue remains, though. We don't yet have techniques to prove the free theorem we need for the type forall b . (a -> b) ⊸ b. It turns out current logical relations (the tools we normally use to do such proofs) haven't been designed to take into account linearity in the way that is needed. This problem has implications for establishing correctness for compilers that do CPS translations.

Nick Rioux
  • 972
  • 5
  • 13