I was trying to implement the SKI combinator calculus in Dotty using match types.
A quick description of the SKI combinator calculus:
S
,K
, andI
are terms(xy)
is a term ifx
andy
are terms and is like function application(((Sx)y)z)
(same asSxyz
) returnsxz(yz)
(same as(xz)(yz)
)((Kx)y)
(same asKxy
) returnsx
(Ix)
returnsx
Below is what I used (R
reduces the term as much as possible). A term (xy)
is written as a tuple (x,y)
, and S
, K
, and I
are traits.
trait S
trait K
trait I
type R[T] = T match {
case (((S,x),y),z) => R[((x,z),(y,z))]
case ((K,x),y) => R[x]
case (I,x) => R[x]
case (a,b) => R[a] match {
case `a` => (a, R[b])
case _ => R[(R[a], R[b])]
}
case T => T
}
However, the following 2 lines don't compile (both for the same reason) (Scastie):
val check: (K, K) = ??? : R[(((S,I),I),K)]
val check2: (K, K) = ??? : R[((I,K),(I,K))]
The error says that it required (K,K)
but found R[((I, K), (I, K))]
. I expected it first see the S and turn it into (IK)(IK)
, or R[((I,K),(I,K))]
, after which it should match the evaluation of the first (I, K)
and see that it is K
, which is not the same as (I, K)
, making it return R[(R[(I,K)], R[(I,K)])]
, which becomes R[(K,K)]
, which becomes just (K,K)
.
However, it doesn't go beyond R[((I,K),(I,K))]
. Apparently, it does not reduce the term if it's nested. This is odd, because I tried the same approach but with an actual runtime function, and it appears to work properly (Scastie).
case object S
case object K
case object I
def r(t: Any): Any = t match {
case (((S,x),y),z) => r(((x,z),(y,z)))
case ((K,x),y) => r(x)
case (I,x) => r(x)
case (a,b) => r(a) match {
case `a` => (a, r(b))
case c => (c, r(b))
}
case _ => t
}
The output from println(r((((S, I), I), K)))
is (K,K)
, as expected.
Interestingly enough, removing the rule for K
lets it compile, but it doesn't recognize (K, K)
and R[(K, K)]
as the same type. Perhaps this is what is causing the problem? (Scastie)
def check2: (K, K) = ??? : R[(K, K)]
//Found: R[(K, K)]
//Required: (K, K)