关联两个类型参数

时间:2018-03-25 13:47:57

标签: scala types type-parameter existential-type path-dependent-type

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

#Result

这样可行,但结果列表中的.chomp看起来很难看:( 有办法解决吗?我可以以某种方式声明它以保证元组的第二个元素是第一个元素声明的\n类型吗?

1 个答案:

答案 0 :(得分:1)

有几种方法可以摆脱Any。以下是我目前可以提出的选项列表:

  1. 使用forSome来"关联"结果与操作
  2. 定义包含操作和结果的自定义类
  3. 将整个设计从列表转换为monad
  4. 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

    操作的自定义类+结果

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

    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

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

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

      

    操作序列,其中一些取决于之前的一些结果

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

    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
    

    我已经将操作链的操作时间延长了一半,以证明当前的monadic操作中的操作可以访问所有之前定义的中间结果(在本例中为{ {1}}和foo),不仅仅是之前的