Search code examples
f#functional-programmingtype-inferencelambda-calculuschurch-encoding

Church Numerals in F#


I have been trying to implement church numerals in F#. They were briefly introduced in a course at college and I may have gone down the rabbit hole a bit since then. I have working Predecessor, Successor, Add and Operations but I can't get subtract to work. I'm trying to implement subtract b applying predecessor multiple times. What I find peculiar is that the penultimate line in my code works but what I assume is equivalent, the last line, does not work. There is a type mismatch.

I'm very new to F# so any help would be appreciated. Thank you.

//Operations on tuples
let fst (a,b) = a
let snd (a,b) = b
let pair a b = (a,b)

//Some church numerals
let c0 (f:('a -> 'a)) = id
let c1 (f:('a -> 'a)) = f 
let c2 f = f << f
let c3 f = f << f << f
let c4 f = f << f << f << f

// Successor and predecessor
let cSucc (b,(cn:('a->'a)->('a->'a))) = if b then (b, fun f -> f << (cn f)) else (true, fun f -> (cn f))
let cPred (cn:('a->'a)->('a->'a)) = fun f -> snd (cn cSucc (false, c0)) f
//let cSucc2 cn = fun f -> f << (cn f)

// Add, Multiply and Subtract church numerals
let cAdd cn cm = fun f -> cn f << cm f
let cMult cn cm = cn >> cm
let cSub cn cm = cm cPred cn

//Basic function for checking validity of numeral operations
let f = (fun x -> x + 1)

//This works
(cPred << cPred) c3 f 0

//This doesn't
c2 cPred c3 f 0

This is the type mismatch error given (Intellisense says this is an error with cPred on the last line of the code). I can see the output type is inferred wrong. Is there a way to fix it or is there something fundamentally wrong with how I have written this implementation?

'((bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> (bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)'    
but given a
    '((bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> bool * (('a -> 'a) -> 'a -> 'a) -> bool * (('a -> 'a) -> 'a -> 'a)) -> ('a -> 'a) -> 'a -> 'a'    
The types ''a' and 'bool * (('a -> 'a) -> 'a -> 'a)' cannot be unified.

Solution

  • In the below explanation I will assume a definition of type CN<'a> = ('a -> 'a) -> 'a -> 'a (where "CN" stands for "Church Numeral") in order to shorten the explanation and reduce clutter.

    Your attempted application of c2 to cPred fails, because c2 expects an argument of type 'a -> 'a, but cPred is not such function.

    You may expect cPred to match the expected type, because you have declared it as CN<'a> -> CN<'a>, but that is not the true type. Because you are applying argument cn to type bool*CN<'a> -> bool*CN<'a> (which is the type of cSucc), the compiler infers that cn must have type of CN<bool*CN<'a>>, and therefore cPred gets the type of CN<bool*CN<'a>> -> CN<'a>, which does not match what c2 expects.

    All of this comes down to this fact: functions lose their genericity when you pass them around as values.

    Consider a simpler example:

    let f (g: 'a -> 'a list) = g 1, g "a"
    

    Such definition will not compile, because 'a is a parameter of f, not a parameter of g. Therefore, for a given execution of f, a specific 'a must be chosen, and it cannot be both int and string at the same time, and therefore, g cannot be applied to both 1 and "a".

    Similarly, cn in cPred gets fixed to type bool*CN<'a> -> bool*CN<'a>, thus making the type of cPred itself incompatible with CN<_>.

    In simple cases, there is an obvious workaround: pass g twice.

    let f g1 g2 = g1 1, g2 "a"
    let g x = [x]
    f g g
    // > it : int list * string list = [1], ["a"]
    

    This way, g will lose genericity both times, but it will be specialized to different types - the first instance to int -> int list, the second - to string -> string list.

    However, this is only a half-measure, suitable for the simplest cases only. A general solution will require the compiler to understand that we want 'a to be a parameter of g, not a parameter of f (this is usually referred to as "higher-rank type"). In Haskell (more specifically, GHC), there is a straightforward way to do this, with the RankNTypes extension enabled:

    f (g :: forall a. a -> [a]) = (g 1, g "a")
    g x = [x]
    f g
    ==> ([1], ["a"])
    

    Here, I explicitly tell the compiler that the parameter g has its own generic parameter a by including forall a in its type declaration.

    F# does not have such explicit support for this, but it does offer a different feature that can be used to accomplish the same result - interfaces. Interfaces may have generic methods, and these methods do not lose genericity when interface instances are passed around. So we can reformulate the above simple example like this:

    type G = 
        abstract apply : 'a -> 'a list
    
    let f (g: G) = g.apply 1, g.apply "a"
    let g = { new G with override this.apply x = [x] }
    f g
    // it : int list * string list = ([1], ["a"])
    

    Yes, the syntax for declaring such "higher-rank functions" is heavy, but that's all F# has to offer.

    So, applying this to your original problem, we need to declare CN as an interface:

    type CN = 
        abstract ap : ('a -> 'a) -> 'a -> 'a
    

    Then we can construct some numbers:

    let c0 = { new CN with override __.ap f x = x }
    let c1 = { new CN with override __.ap f x = f x }
    let c2 = { new CN with override __.ap f x = f (f x) }
    let c3 = { new CN with override __.ap f x = f (f (f x)) }
    let c4 = { new CN with override __.ap f x = f (f (f (f x))) }
    

    Then cSucc and cPred:

    let cSucc (b,(cn:CN)) = 
        if b 
        then (b, { new CN with override __.ap f x = f (cn.ap f x) }) 
        else (true, cn)
    
    let cPred (cn:CN) = snd (cn.ap cSucc (false, c0))
    

    Note that cPred now has inferred type of CN -> CN, exactly what we need.
    Arithmetic functions:

    let cAdd (cn: CN) (cm: CN) = { new CN with override __.ap f x = cn.ap f (cm.ap f x) }
    let cMult (cn: CN) (cm: CN) = { new CN with override __.ap f x = cn.ap cm.ap f x }
    let cSub (cn: CN) (cm: CN) = cm.ap cPred cn
    

    Note, all of them get the inferred type of CN -> CN -> CN, as expected.

    And finally, your examples:

    let f = (fun x -> x + 1)
    
    //This works
    ((cPred << cPred) c3).ap f 0
    
    //This also works now
    (c2.ap cPred c3).ap f 0