与HKT匹配的模式 - 避免必须转换返回类型

时间:2018-02-17 16:38:17

标签: scala pattern-matching higher-kinded-types

在下面的示例中,虽然模式匹配在技术上是正确的,但显然铸造是错误的。我可以在不使用asInstanceOf的情况下重新表述吗?即更改模式匹配的写入方式或调整Transform的接口。

trait Pat[A] {
  def transform(t: Transform): Pat[A] = ???
  def expandList: List[A]
}

trait Transform {
  def apply[X](in: Pat[X]): Pat[X]
}

case class FoldLeft[B, A](outer: Pat[Pat[B]], z: Pat[A], itIn: Pat[B], 
                          itCarry: Pat[A], inner: Pat[A]) extends Pat[A] {
  def expandList: List[A] = ???

  def test(): Unit = {
    val outerList: List[Pat[B]] = outer.expandList
    outerList.foldLeft(z) { (y: Pat[A], x: Pat[B]) =>
      val t = new Transform {
        def apply[X](in: Pat[X]): Pat[X] = in match {
          case `itIn`     => x.asInstanceOf[Pat[X]]  // ugly cast
          case `itCarry`  => y.asInstanceOf[Pat[X]]  // ugly cast
          case other      => other
        }
      }
      t[A](inner).transform(t)
    }
  }
}

如果您对FoldLeft含义感到好奇,这里有一些例子:

Pat(Pat(1), Pat(2), Pat(3)).foldLeft(0) { (y, x) => y + x }

(你明白了; Pat就像一个Stream 描述符,所以我抓住了这个过程的AST,即不是执行折叠 - 左边,我正在创建代表该过程的FoldLeftPat(1, 2, 3)变为outer0变为z,闭包被“评估”为虚拟模式itInx)和itCarryy),生成闭包的模式版本inner

FoldLeft(Pat(Pat(1), Pat(2), Pat(3)), Pat(0), It("a"), It("b"),
  BinaryOp(Plus, It("b"), It("a")))  // similar to this

1 个答案:

答案 0 :(得分:1)

如果你被迫在所有类型都在你面前的代码中使用asInstanceOf,那么它通常意味着你的代码中的某些抽象保证了它在类型签名中无法实现的东西。你代码中的几个地方似乎很可疑:

  1. Pat[A] - trait尝试跟踪类型,但在下一行inner二进制操作foldLeft正文中没有说明它包含的孔类型。它现在是哪一种,是类型安全,还是(Any, Any) => A的AST,其类型为Any

  2. Transform附带了签名apply[X](x: Pat[X]): Pat[X]的方法,该方法基本上说:“我是自然转换,我会对待所有{{1}同样!“,但是当你第一次实例化它时,你会以不同的方式对待XA

  3. B悄悄进入您的代码,因为它试图维护这两个抽象asInstanceOfPar执行他们假装在签名中所做的事情的错觉。

    要摆脱Transform,您必须以非闭合术语跟踪变量的类型。这可以通过以下方式实现:

    1. 使用asInstanceOf代表仅关闭类型Par[A]的表达式
    2. 为表达式创建单独的特征A 类型Par2[V1, V2, A]V1
    3. 的自由变量
    4. 而不是假装平等对待每个V2的{​​{1}},而是创建一个Transform特征,在其签名中明确指出它只能填充Par[X]类型的漏洞和Graft2[V1, V2]
    5. V1V2Var2_1的子类)对Var2_2
    6. 的方法进行动态调度

      然后你可能得到这样的东西(编译,工作,没有Par2):

      Graft2

      这将打印以下符号化评估的AST(我修复了缩进并用asInstanceOf替换了丑陋的匿名lambda名称/** A pattern that represents closed expressions * that evaluate to something of type `X`. */ sealed trait Pat[X] case class IntPat(i: Int) extends Pat[Int] case class BinopPat[A, B, C]( a: Pat[A], b: Pat[B], op: (A, B) => C ) extends Pat[C] case class FoldLeft[A, B]( bs: List[Pat[B]], z: Pat[A], op: PatFunc2[A, B, A] ) extends Pat[A] { /** Symbolically executes the `foldLeft`-operation */ def eval: Pat[A] = bs.foldLeft(z)(op.graft) } /** Symbolic function with two arguments of * type `V1` and `V2` that returns values * of type `R`. */ case class PatFunc2[V1, V2, R]( v1: Var2_1[V1, V2], v2: Var2_2[V1, V2], body: Pat2[V1, V2, R] ) { def graft(arg1: Pat[V1], arg2: Pat[V2]): Pat[R] = body.graft(Graft(v1, arg1, v2, arg2)) } /** A pattern that represents non-closed * expression with holes of two types `V1` and `V2`, * which, once some patterns are plugged into the * holes, evaluates to a value of type `A`. */ sealed trait Pat2[V1, V2, A] { def graft(g: Graft2[V1, V2]): Pat[A] } case class IntPat2[V1, V2](i: Int) extends Pat2[V1, V2, Int] { def graft(g: Graft2[V1, V2]): Pat[Int] = IntPat(i) } case class Var2_1[V1, V2](name: String) extends Pat2[V1, V2, V1] { def graft(g: Graft2[V1, V2]): Pat[V1] = g(this) // no cast! } case class Var2_2[V1, V2](name: String) extends Pat2[V1, V2, V2] { def graft(g: Graft2[V1, V2]): Pat[V2] = g(this) // no cast! } case class BinopPat2[V1, V2, A, B, C]( a: Pat2[V1, V2, A], b: Pat2[V1, V2, B], op: (A, B) => C ) extends Pat2[V1, V2, C] { def graft(g: Graft2[V1, V2]): Pat[C] = BinopPat(a graft g, b graft g, op) } /** Grafting operation that can fill holes of two types * `V1` and `V2` in expressions with free variables of * those two types. */ trait Graft2[V1, V2] { def apply(v1: Var2_1[V1, V2]): Pat[V1] def apply(v2: Var2_2[V1, V2]): Pat[V2] } object Graft { /** Helper method to simplify the construction * of a `Graft2` when there are exactly two * variables. */ def apply[V1, V2]( v1: Var2_1[V1, V2], arg1: Pat[V1], v2: Var2_2[V1, V2], arg2: Pat[V2] ): Graft2[V1, V2] = new Graft2[V1, V2] { def apply(w1: Var2_1[V1, V2]): Pat[V1] = { if (v1 == w1) arg1 else throw new NoSuchElementException("No binding for variable " + w1) } def apply(w2: Var2_2[V1, V2]): Pat[V2] = { if (v2 == w2) arg2 else throw new NoSuchElementException("No binding for variable " + w2) } } } val test = FoldLeft( List(IntPat(1), IntPat(2), IntPat(3)), IntPat(42), { val a = Var2_1[Int, Int]("a") val b = Var2_2[Int, Int]("b") PatFunc2(a, b, BinopPat2(a, b, (_: Int) + (_: Int))) } ) println(test.eval) ):

      _ + _

      我希望这大致是您希望在代码中实现的目标。至少这是我从你的编辑和评论中理解的。

      请注意,我在代码段中的每个地方都使用了“graft”而不是“substitute”。 Grafting 是一个更简单的术语重写操作,因为它忽略了变量名称捕获的问题。如果你开始在函数内部使用函数,它可能会开始表现得很奇怪,因为变量名可能会发生冲突。

      此外,如果您采用这种方法,则需要+LambdaBinopPat( BinopPat( BinopPat( IntPat(42), IntPat(1), +Lambda ), IntPat(2), +Lambda ), IntPat(3), +Lambda ) ,...,Graft1等内容,因为您实际上是在为{{1}创建符号替换标准库中的},...,Graft2。但是,请注意我的Graft22实现基于变量名称进行调度,因此Function1 是变量的类型数,而不是不同变量的数量(不同变量的数量可以大于Function22)。

      如果所有这些都太尴尬,那么你可以做什么:完全删除变量名并使用普通的Scala闭包。