Reputation: 1408
I have watched the amazing talk by John Hughes titled Why Functional Programming Matters a couple of times and only recently decided to actually try implementing the "minimalist" version of booleans, integers, and of course factorial, as shown in the video.
I implemented true
, false
, ifte
, zero
, one
, two
, iszero
, decr
and finally fact
here as follow:
-- boolean
true x y = x
false x y = y
ifte bool t e = bool t e
-- positive integers
three f x = f $ f $ f x
two f x = f $ f x
one f x = f x
zero f x = x
-- add and multiplication
add m n f x = m f $ n f x
mul m n f x = m (n f) x
-- is zero check
iszero n = n (\_ -> false) true
-- decrement
decr n =
n (\m f x -> f (m f zero))
zero
(\x->x)
zero
-- factorial
fact n =
ifte (iszero n)
one
(mul n (fact (decr n)))
I tested every function, and they all compile and work perfectly, except for decr
and fact
.
Even though John Hughes promises at 6:37 that his implementation of decr
works, it fails to compile with the following error:
error: Variable not in scope: incr
I am not certain how incr
should be implemented in (\m f x -> f (m incr zero))
.
Now if I define them as incr = (+1)
and change the definition of decr
to the following, then decr
compiles and works fine, but fact
still causes compilation failure.
decr n =
n (\m f x -> f (m incr x))
zero
(\x->x)
zero'
Is there a bug in the lambda (\m f x -> f (m incr zero))
used in the definition of decr
, or should incr
be defined differently?
When I define incr
as incr n = (\f x -> f (n f x))
, decr n
works fine, but fact n
fails to compile. Here's the readable portion of the error message:
factorial.hs:30:1: error: • Occurs check: cannot construct the infinite type:
...
| fact n =
| ^^^^^^^^......
factorial.hs:33:6: error: • Occurs check: cannot construct the infinite type:
...
• In the third argument of ‘ifte’, namely ‘(mul n (fact (decr n)))’ In the expression: ifte (iszero n) one (mul n (fact (decr n))) In an equation for ‘fact’: fact n = ifte (iszero n) one (mul n (fact (decr n)))
...
| (mul n (fact (decr n)))
| ^^^^^^^^^^^^^^^^^^^^^
Note: the complete error message is several pages long.
Upvotes: 1
Views: 233
Reputation: 120711
First, let's try to explicitly type everything. Naïvely, all this stuff is parameterised on some type that the Church functions deal with:
type Logical a = a -> a -> a
type Nat a = (a->a) -> a->a
-- boolean
true, false :: Logical a
true x y = x
false x y = y
ifte :: Logical a -> a -> a -> a
ifte = id
incr :: Nat a -> Nat a
incr n f = f . n f
-- integer “literals”
zero, one, two, three :: Nat a
three = incr two
two = incr one
one = incr zero
zero _ = id
-- addition and multiplication
add, mul :: Nat a -> Nat a -> Nat a
add m n f = m f . n f
mul m n f = m $ n f
-- zero check
isZero :: Nat a -> Logical a
isZero n = n (const false) true
...ok, here we run into the first problem:
• Couldn't match expected type ‘Logical a’ with actual type ‘a’
‘a’ is a rigid type variable bound by
the type signature for:
isZero :: forall a. Nat a -> Logical a
at /tmp/wtmpf-file7834.hs:25:1-28
• In the expression: n (const false) true
The issue is that we try to use the Nat
-church-numbers as a function not on the underlying a
type that the result Logical
is supposed to work with, but on those logicals themselves. I.e. it's actually
isZero :: Nat (Logical a) -> Logical a
It gets worse for decr
– this doesn't work:
decr :: Nat a -> Nat a
decr n = n (\m f x -> f (m incr x))
zero
id
zero
because you're trying to use the number for both a logical purpose as in isZero
, which requires injecting another Nat
layer, but also for just passing on/incrementing. In traditional Hindley-Milner, you'd need to decide on one of these, so it's not possible to make it typecheck.
In modern Haskell, you can make it typecheck, by making the argument polymorphic:
{-# LANGUAGE RankNTypes, UnicodeSyntax #-}
decr :: (∀ α . Nat α) -> Nat a
To avoid carrying around the quantifier, we might make another synonym:
type ANat = ∀ α . Nat α
then it's just
decr :: ANat -> Nat a
And that technique works for the factorial as well:
fact :: ANat -> Nat a
fact n = ifte (isZero n)
one
(mul n $ fact (decr n))
Upvotes: 1
Reputation: 135187
It looks like you're really close
I can show you how to do this using Church's encodings in JavaScript, but not in Haskell, because I don't know how to make some simple combinators type-check in Haskell (U
below)
Because JavaScript is strictly evaluated, predicate branches must be wrapped in a lambda
Go ahead and run the snippet – we calculate 8!
const True = a => b =>
a ()
const False = a => b =>
b ()
const IsZero = n =>
n (x => False) (True)
const Succ = n =>
f => x => f (n (f) (x))
const Pred = n =>
f => x => n (g => h => h (g (f))) (u => x) (u => u)
const Mult = m => n =>
f => m (n (f))
const Add = m => n =>
m (Succ) (n)
const one = f => x =>
f (x)
const two =
Add (one) (one)
const four =
Add (two) (two)
const eight =
Add (four) (four)
const U = f => f (f)
const Fact = U (f => acc => n =>
IsZero (n)
(z => acc) // thunks used for predicate branches
(z => U (f) (Mult (acc) (n)) (Pred (n)))) (one)
const result =
Fact (eight)
// convert church numeral result to a JavaScript number
console.log ('8! =', result (x => x + 1) (0))
// 8! = 40320
If you cheat a little, you can achieve faux laziness by using JavaScript's ?:
ternary operator – I'm only showing this so you can see Fact
in a more readable form; the above implementation uses only lambdas
const IsZero = n =>
// cheat by returning JavaScript's true/false booleans
n (x => false) (true)
const Succ = n =>
f => x => f (n (f) (x))
const Pred = n =>
f => x => n (g => h => h (g (f))) (u => x) (u => u)
const Mult = m => n =>
f => m (n (f))
const Add = m => n =>
m (Succ) (n)
const one = f => x =>
f (x)
const two =
Add (one) (one)
const four =
Add (two) (two)
const eight =
Add (four) (four)
const U = f => f (f)
const Fact = U (f => acc => n =>
IsZero (n)
? acc // now we're sorta cheating using JavaScript's ternary here
: U (f) (Mult (acc) (n)) (Pred (n))) (one)
const result =
Fact (eight)
console.log ('8! =', result (x => x + 1) (0))
// 8! = 40320
Upvotes: 3