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)
We start with the types of k
and s
k :: t1 -> t2 -> t1
k = (\a x -> a)
s :: (t3 -> t4 -> t5) -> (t3 -> t4) -> t3 -> t5
s = (\f g x -> f x (g x))
So passing k
as the first argument of s
, we unify k
's type with the type of s
's first argument, and use s
at the type
s :: (t1 -> t2 -> t1) -> (t1 -> t2) -> t1 -> t1
hence we obtain
s k :: (t1 -> t2) -> t1 -> t1
s k = (\g x -> k x (g x)) = (\g x -> x)
Then in s (s k)
, the outer s
is used at the type (t3 = t1 -> t2
, t4 = t5 = t1
)
s :: ((t1 -> t2) -> t1 -> t1) -> ((t1 -> t2) -> t1) -> (t1 -> t2) -> t1
applying that to s k
drops the type of the first argument and leaves us with
s (s k) :: ((t1 -> t2) -> t1) -> (t1 -> t2) -> t1
As 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 of s (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).
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
and s
:
k :: t1 -> t2 -> t1
k = (\a x -> a)
s :: (t3 -> t4 -> t5) -> (t3 -> t4) -> t3 -> t5
s = (\f g x -> f x (g x))
Let's first work on determing the type signature of (s k)
.
Recall s
's definition:
s = (\f g x -> f x (g x))
Substituting k
for f
results in (\f g x -> f x (g x))
contracting to:
(\g x -> k x (g x))
Important The type of g and x must be consistent with k
's type signature.
Recall that k
has this type signature:
k :: t1 -> t2 -> t1
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:
(t1 -> t2) -> t1 -> t1
Next, k
is defined to always return its first argument. So this:
k x (g x)
contracts to this:
x
And this:
(\g x -> k x (g x))
contracts to this:
(\g x -> x)
Okay, now we have figured out (s k)
:
s k :: (t1 -> t2) -> t1 -> t1
s k = (\g x -> x)
Now let's determine the type signature of s (s k)
.
We proceed in the same manner.
Recall s
's definition:
s = (\f g x -> f x (g x))
Substituting (s k)
for f
results in (\f g x -> f x (g x))
contracting to:
(\g x -> (s k) x (g x))
Important The type of g
and x
must be consistent with (s k)
's type signature.
Recall that (s k)
has this type signature:
s k :: (t1 -> t2) -> t1 -> t1
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:
((t1 -> t2) -> t1) -> (t1 -> t2) -> t1
Earlier we determined that s k
has this definition:
(\g x -> x)
That is, it is a function that takes two arguments and returns the second.
Therefore, this:
(s k) x (g x)
Contracts to this:
(g x)
And this:
(\g x -> (s k) x (g x))
contracts to this:
(\g x -> g x)
Okay, now we have figured out s (s k)
.
s (s k) :: ((t1 -> t2) -> t1) -> (t1 -> t2) -> t1
s (s k) = (\g x -> g x)
Lastly, compare the type signature of s (s k)
with the type signature of this function:
p = (\g x -> g x)
The type of p
is:
p :: (t1 -> t) -> t1 -> t
p
and s (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 than p
is that there are no constraints on p
. We saw that the s
in (s k)
is constrained to be consistent with the type signature of k
, and the first s
in s (s k)
is constrained to be consistent with the type signature of (s k)
. So, the type signature of s (s k)
is constrained due to its argument. Even though p
and s (s k)
have the same definition the constraints on g
and x
differ.
来源:https://stackoverflow.com/questions/9592191/the-type-signature-of-a-combinator-does-not-match-the-type-signature-of-its-equi