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.
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