Is having a `(a -> b) -> b` equivalent to having an `a`?

后端 未结 3 595
面向向阳花
面向向阳花 2021-01-30 10:50

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 t

3条回答
  •  一生所求
    2021-01-30 11:21

    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.

提交回复
热议问题