【问题标题】:How to implement the SKI combinator calculus with match types?如何使用匹配类型实现 SKI 组合器演算?
【发布时间】:2020-12-11 08:06:37
【问题描述】:

我试图在 Dotty 中使用匹配类型实现 SKI combinator calculus

SKI 组合子演算的简要说明:

  • SKI 是术语
  • (xy) 是一个术语,如果 xy 是术语并且类似于函数应用程序
  • (((Sx)y)z)(与Sxyz相同)返回xz(yz)(与(xz)(yz)相同)
  • ((Kx)y)(与Kxy相同)返回x
  • (Ix) 返回x

以下是我使用的(R 尽可能减少该术语)。术语(xy) 写为元组(x,y)SKI 是特征。

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
}

但是,以下 2 行无法编译(出于相同的原因)(Scastie):

val check: (K, K) = ??? : R[(((S,I),I),K)]
val check2: (K, K) = ??? : R[((I,K),(I,K))]

错误说它需要(K,K),但找到了R[((I, K), (I, K))]。我希望它首先看到 S 并将其转换为 (IK)(IK)R[((I,K),(I,K))],然后它应该匹配第一个 (I, K) 的评估并看到它是 K,这与 @987654354 不同@,让它返回R[(R[(I,K)], R[(I,K)])],变成R[(K,K)],变成(K,K)

但是,它不超出R[((I,K),(I,K))]。显然,如果它是嵌套的,它不会减少术语。这很奇怪,因为我尝试了相同的方法,但使用了一个实际的运行时函数,而且它似乎可以正常工作 (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
}

println(r((((S, I), I), K))) 的输出是 (K,K),正如预期的那样。

有趣的是,删除K 的规则可以让它编译,但它不会将(K, K)R[(K, K)] 识别为同一类型。也许这就是导致问题的原因? (Scastie)

def check2: (K, K) = ??? : R[(K, K)]
//Found:    R[(K, K)]
//Required: (K, K)

【问题讨论】:

    标签: scala pattern-matching scala-3 dotty match-types


    【解决方案1】:

    SKI 不是不相交的。路口K with I等有人居住:

    println(new K with I)
    

    在匹配类型中,仅当类型不相交时才跳过大小写。所以,例如这失败了:

    type IsK[T] = T match {
      case K => true 
      case _ => false
    }
    @main def main() = println(valueOf[IsK[I]])
    

    因为case K => _ 分支不能被跳过,因为I 的值 Ks。所以,例如在你的情况下,R[(K, K)] 卡在case (I, x) => _ 上,因为有一些(K, K)s 也是(I, x)s(例如(new K with I, new K {}))。你永远不会到达case (a,b) => _,它会把我们带到(K, K)

    您可以创建SKIclasses,这会使它们不相交,因为您不能同时从两个classes 继承。

    class S
    class K
    class 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
    }
    
    @main def main(): Unit = {
      println(implicitly[R[(K, K)] =:= (K, K)])
      println(implicitly[R[(((S,I),I),K)] =:= (K, K)])
    }
    

    Scastie

    另一种解决方案是将它们全部设为单例类型:

    object S; type S = S.type
    object K; type K = K.type
    object I; type I = I.type
    // or, heck
    type S = 0
    type K = 1
    type I = 2
    

    【讨论】:

    • 嗯,这很有趣。它完全解决了问题,但有点令人困惑。我想当你匹配一个类型时,它只关心 那个 类型而不是它假设的后代。感谢您的回答
    • 这基本上意味着匹配类型只对最终类和单例类型有用?
    • @Jasper-M 哦,我犯了一个错误。 Dotty 理解单继承,所以它只具有SKI 非最终classes,因为你不能以某种方式继承两个类。如您所见,match 确实适用于非最终课程。
    猜你喜欢
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多