关联两个类型参数

Correlate two type parameters

假设,我有一系列操作,其中一些操作依赖于之前操作的一些结果。像这样:

 type Results = List[(Operation[_], Any)] // ???
 trait Operation[Out] { 
   type Result = Out
   def apply(results: Results): Out
 }

 class SomeOp extends Operation[String] {
    def apply(results: Results) = "foo"
 }

 class OtherOp extends Operation[String] {
    def apply(results: Results) = results
      .collectFirst { case (_: SomeOp, x: String) => x } 
      .getOrElse("") + "bar"
 }

 def applyAll(
  ops: List[Operation[_]], 
  results: Results = Nil
): Results = ops match {
  case Nil => results.reverse
  case head :: tail => applyAll(tail, (head -> head(results)) :: results)
}

applyAll(List(new SomeOp, new OtherOp)).last._2 // foobar

这行得通,但结果列表中的 Any 看起来很难看:( 有办法解决吗?我能以某种方式声明它以保证元组的第二个元素是第一个元素声明的 #Result 类型吗?

有几种方法可以摆脱 Any。以下是到目前为止我能想到的选项列表:

  1. 使用forSome到"correlate"的结果与操作
  2. 定义一个包含操作和结果的自定义 class
  3. 将整个设计从列表转换为 monad

forSome解决方案

问题标题似乎问的正是 forSome:

(Operation[X], X) forSome { type X }

在这里,类型变量 XforSome 量词绑定,它保证列表中的元组只能存储匹配类型的操作和输出。

虽然它禁止出现像(SomeOperation[String], Int)这样的元组,但是实例化变得有点麻烦:

    val newResult: (Operation[Y], Y) forSome { type Y } = head match {
      case op: Operation[t] => (op -> op(results))
    }

那里的ttype pattern on the left hand side of the match-case。这有时有助于处理存在类型,因为它允许我们为存在类型命名,在本例中为 t.

这是一个如何使用它的演示:

type Results = List[(Operation[X], X) forSome { type X }]
trait Operation[Out] { 
  type Result = Out
  def apply(results: Results): Out
}

class SomeOp extends Operation[String] {
   def apply(results: Results) = "foo"
}

class OtherOp extends Operation[String] {
   def apply(results: Results) = results
     .collectFirst { case (_: SomeOp, x: String) => x } 
     .getOrElse("") + "bar"
}

def applyAll(
  ops: List[Operation[_]], 
  results: Results = Nil
): Results = ops match {
  case Nil => results.reverse
  case head :: tail => {
    val newResult: (Operation[Y], Y) forSome { type Y } = head match {
      case op: Operation[t] => (op -> op(results))
    } 
    applyAll(tail, newResult :: results)
  }
}

println(applyAll(List(new SomeOp, new OtherOp)).last._2)

它只是像以前一样输出 foobar


自定义 class 操作 + 结果

与其使用具有复杂存在性的元组,可能更容易 定义自定义类型以将操作与结果保存在一起:

case class OpRes[X](op: Operation[X], result: X)

将相应的方法返回 OpRes 添加到 Operation, 一切都变得相当简单:

  def opWithResult(results: Results): OpRes[Out] = OpRes(this, apply(results))

这是一个完整的可编译示例:

case class OpRes[X](op: Operation[X], result: X)
type Results = List[OpRes[_]]
trait Operation[Out] { 
  type Result = Out
  def apply(results: Results): Out
  def opWithResult(results: Results): OpRes[Out] = OpRes(this, apply(results))
}

class SomeOp extends Operation[String] {
   def apply(results: Results) = "foo"
}

class OtherOp extends Operation[String] {
   def apply(results: Results) = results
     .collectFirst { case OpRes(_: SomeOp, x: String) => x } 
     .getOrElse("") + "bar"
}

def applyAll(
  ops: List[Operation[_]], 
  results: Results = Nil
): Results = ops match {
  case Nil => results.reverse
  case head :: tail => applyAll(tail, head.opWithResult(results) :: results)
}

println(applyAll(List(new SomeOp, new OtherOp)).last.result)

再次,它输出 foobar,和以前一样。


也许它应该只是一个单子?

最后,您问题的第一句包含短语

sequence of operations, some of which depend on some of the results of previous ones

在我看来,这几乎就像是 monad 的完美实用定义,所以也许您想用 for-comprehensions 而不是存在类型列表来表示计算序列。这是一个粗略的草图:

trait Operation[Out] { outer =>
  def result: Out
  def flatMap[Y](f: Out => Operation[Y]): Operation[Y] = new Operation[Y] {
    def result: Y = f(outer.result).result
  }
  def map[Y](f: Out => Y) = new Operation[Y] {
    def result: Y = f(outer.result)
  }
}

object SomeOp extends Operation[String] {
   def result = "foo"
}

case class OtherOp(foo: String) extends Operation[String] {
   def result = foo + "bar"
}

case class YetAnotherOp(foo: String, bar: String) extends Operation[String] {
  def result = s"previous: $bar, pre-previous: $foo"
}

def applyAll: Operation[String] = for {
  foo <- SomeOp
  fbr <- OtherOp(foo)
  fbz <- YetAnotherOp(foo, fbr)
} yield fbz

println(applyAll.result)

它打印

previous: foobar, pre-previous: foo

我已经将操作链的一个操作加长了,以证明单子 for-comprehension 中的操作当然可以访问 all 先前定义的中间结果(在这种情况下,foofbr),不仅是 前一个