Search code examples
haskelllambdanumberschurch-encoding

Church naturals, exponentiation function and type checking


I have a definition of the natural numbers in lambda calculus as follow, which was my main goal.

-- Apply a function n times on x
apply = \f -> \n -> \x -> foldr ($) x $ replicate n f

-- Church numbers
churchZero = \f -> id

churchOne = \f -> \x -> apply f 1 x

churchTwo = \f -> \x -> apply f 2 x

churchNatural = \n -> \f -> \x -> apply f n x

Then, the next step was defining the operators churchSum, churchMul and churchExp.

churchSum = \n -> \m -> \f -> \x -> n f (m f x)

churchMul = \n -> \m -> \f -> \x -> n (m f) x

churchExp = \n -> \m -> n m

Fine, it works, the first two functions are "easy" to deduce, but the exponentiation is not. For me at least. To understand a little bit more, i did the beta normalization on the lambda term:
(λf.λx. f(f x))(λf.λx f(f x)) to see that effectively the exponentiation is correct.

beta reduction

So, my question is: how could i deduce this lambda term for the exponentiation without known it? Even more, why something like λ> churchTwo churchTwo type checks on Haskell when the types are λ> churchTwo :: (b -> b) -> b -> b? Inside it do the beta normalization of the function?


Solution

  • Your exp is a bit backwards:

    ((\f x -> f$f$f$x) `exp` (\f x -> f$f$x)) (+1) 0 == 8
    --      3            ^          2                 = 8???
    -- But 2^3 = 8
    

    The correct(-er-ish) version would be

    exp n m = m n
    ((\f x -> f$f$f$x) `exp` (\f x -> f$f$x)) (+1) 0 == 9
    --      3            ^          2                 = 9
    

    because it maintains the familiar order. This doesn't really affect how you might go around defining exp.


    Exponentation is repeated multiplication: nm is n multiplied by itself m times. Church numerals represent repeated application of a function to a value. So, churchMul n is a function that multiplies a numeral by n, m is function that repeats a function m times, and churchOne is the base value (identity of multiplication). Put them together, then simplify:

    -- n^m is the repeated multiplication of 1 by n, m times
    exp n m = m (churchMul n) churchOne
    -- expand definitions x2; simplify churchOne
    exp n m = m (\o f x -> n (o f) x) (\f x -> f x)
    -- eta contract x2
    exp n m = m (\o f -> n (o f)) (\f -> f)
    -- definition of (.), id
    exp n m = m (\o -> n . o) id
    -- eta contract
    exp n m = m (n .) id
    -- eta expand
    exp n m f = m (n .) id f
    -- Assume m has the type forall b. (b -> b) -> b -> b
    -- This assumption may actually be false here, because the implicit type of exp
    -- does not require that m, n have that type. The difference is that you could define
    -- bogus _ _ _ = 0
    -- (which isn't a church numeral) and still pass it to exp, which would no longer
    -- act like exponentiation:
    -- exp n bogus = bogus (n .) id = const 0
    -- which also isn't a church numeral
    
    -- Polymorphic functions like m give rise to theorems that can be derived
    -- entirely from their types. I used http://www-ps.iai.uni-bonn.de/cgi-bin/free-theorems-webui.cgi
    -- to get this one automatically.
    -- Free theorem of the type of m
    forall a b (g :: a -> b) (p :: a -> a) (q :: b -> b).
       (forall (x :: a). g (p x) = q (g x)) ->
         (forall (y :: a). g (m p y) = m q (g y))
    
    -- Instantiate g = ($ f), p = (n .), q = n, y = id
      (forall x. (n . x) f = n (x f)) -> (m (n .) id f = m n f)
    
    -- definition of (.)
      (n . x) f = n (x f)
    -- so...
      m (n .) id f = m n f
    -- transitive property
    exp n m f = m n f
    -- eta contract
    exp n m = m n
    

    The above stuff with the free theorem of m is really a rigorous version of the following argument (which probably translates better to the untyped lambda calculus):

    -- m, being a valid numeral, is of the form
    m f x = f $ f $ ... $ f $ f $ x
    
    m (n .) id = (n .) $ (n .) $ ... $ (n .) $ (n .) $ id
               = (n .) $ (n .) $ ... $ (n .) $ n . id
               = (n .) $ (n .) $ ... $ (n .) $ n
               = (n .) $ (n .) $ ... $ n . n
               ...
               = n . n . ... . n . n
    -- so
    m n = n . n . ... . n . n = m (n .) id
    

    As for why churchTwo churchTwo typechecks, note each occurrence in that expression has a different type, because churchTwo is polymorphic and describes an entire family of functions instead of just one.

    -- most general type
    churchTwo :: forall b. (b -> b) -> (b -> b)
    -- Each occurrence of churchTwo can have a different type, so let's give them
    -- different names.
    -- I'm using underscores because these variables haven't been solved yet
    churchTwo0 :: (_b0 -> _b0) -> (_b0 -> _b0)
    churchTwo1 :: (_b1 -> _b1) -> (_b1 -> _b1)
    churchTwo0 churchTwo1 :: _
    -- Since churchTwo0 is being applied, the whole expression must have the
    -- type on the right of the arrow
    churchTwo0 churchTwo1 :: _b0 -> _b0
    -- Since churchTwo0 is being applied to churchTwo1, the left side of the
    -- top level arrow in churchTwo0 must be equal to the type of churchTwo1
    (_b0 -> _b0) ~ ((_b1 -> _b1) -> (_b1 -> _b1))
    -- Therefore...
    (_b0 ~ (_b1 -> _b1))
    churchTwo0 churchTwo1 :: (_b1 -> _b1) -> (_b1 -> _b1)
    -- That's all the constraints we have, so replace the free variables
    -- with universally quantified ones
    chuchTwo0 churchTwo1 :: forall b. (b -> b) -> (b -> b)
    -- (which is the type of a numeral)