Consider this combinator:
S (S K)
Apply it to the arguments X Y:
S (S K) X Y
It contracts to:
X Y
I converted S (S K) to the corresponding Lambda terms and got this result:
(\x y -> x y)
I used the Haskell WinGHCi tool to get the type signature of (\x y -> x y) and it returned:
(t1 -> t) -> t1 -> t
That makes sense to me.
Next, I used WinGHCi to get the type signature of s (s k) and it returned:
((t -> t1) -> t) -> (t -> t1) -> t
That doesn't make sense to me. Why are the type signatures different?
Note: I defined s, k, and i as:
s = (\f g x -> f x (g x))
k = (\a x -> a)
i = (\f -> f)
Thanks to all who responded to my question. I have studied your responses. To be sure that I understand what I've learned I have written my own answer to my question. If my answer is not correct, please let me know.
We start with the types of
k
ands
:Let's first work on determing the type signature of
(s k)
.Recall
s
's definition:Substituting
k
forf
results in(\f g x -> f x (g x))
contracting to:Important The type of g and x must be consistent with
k
's type signature.Recall that
k
has this type signature:So, for this function definition
k x (g x)
we can infer:The type of
x
is the type ofk
's first argument, which is the typet1
.The type of
k
's second argument ist2
, so the result of(g x)
must bet2
.g
hasx
as its argument which we've already determined has typet1
. So the type signature of(g x)
is(t1 -> t2)
.The type of
k
's result ist1
, so the result of(s k)
ist1
.So, the type signature of
(\g x -> k x (g x))
is this:Next,
k
is defined to always return its first argument. So this:contracts to this:
And this:
contracts to this:
Okay, now we have figured out
(s k)
:Now let's determine the type signature of
s (s k)
.We proceed in the same manner.
Recall
s
's definition:Substituting
(s k)
forf
results in(\f g x -> f x (g x))
contracting to:Important The type of
g
andx
must be consistent with(s k)
's type signature.Recall that
(s k)
has this type signature:So, for this function definition
(s k) x (g x)
we can infer:The type of
x
is the type of(s k)
's first argument, which is the type(t1 -> t2)
.The type of
(s k)
's second argument ist1
, so the result of(g x)
must bet1
.g
hasx
as its argument, which we've already determined has type(t1 -> t2)
. So the type signature of(g x)
is((t1 -> t2) -> t1)
.The type of
(s k)
's result ist1
, so the result ofs (s k)
ist1
.So, the type signature of
(\g x -> (s k) x (g x))
is this:Earlier we determined that
s k
has this definition:That is, it is a function that takes two arguments and returns the second.
Therefore, this:
Contracts to this:
And this:
contracts to this:
Okay, now we have figured out
s (s k)
.Lastly, compare the type signature of
s (s k)
with the type signature of this function:The type of
p
is:p
ands (s k)
have the same definition(\g x -> g x)
so why do they have different type signatures?The reason that
s (s k)
has a different type signature thanp
is that there are no constraints onp
. We saw that thes
in(s k)
is constrained to be consistent with the type signature ofk
, and the firsts
ins (s k)
is constrained to be consistent with the type signature of(s k)
. So, the type signature ofs (s k)
is constrained due to its argument. Even thoughp
ands (s k)
have the same definition the constraints ong
andx
differ.We start with the types of
k
ands
So passing
k
as the first argument ofs
, we unifyk
's type with the type ofs
's first argument, and uses
at the typehence we obtain
Then in
s (s k)
, the outers
is used at the type (t3 = t1 -> t2
,t4 = t5 = t1
)applying that to
s k
drops the type of the first argument and leaves us withAs a summary: In Haskell, the type of
s (s k)
is derived from the types of its constituent subexpressions, not from its effect on its argument(s). Therefore it has a less general type than the lambda expression that denotes the effect ofs (s k)
.The type system you are using is basically the same as simply-typed lambda calculus (you are not using any recursive functions or recursive types). Simply-typed lambda calculus is not fully general; it is not Turing-complete, and it cannot be used to write general recursion. SKI combinator calculus is Turing-complete, and can be used to write fixed-point combinators and general recursion; therefore, the full power of SKI combinator calculus cannot be expressed in simply-typed lambda calculus (although it can be in untyped lambda calculus).