Scala 关联两个类型参数
假设我有一个操作序列,其中一些操作依赖于前面操作的一些结果。大概是这样的:Scala 关联两个类型参数,scala,types,type-parameter,existential-type,path-dependent-type,Scala,Types,Type Parameter,Existential Type,Path Dependent Type,假设我有一个操作序列,其中一些操作依赖于前面操作的一些结果。大概是这样的: 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) = "
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
这是可行的,但结果列表中的任何看起来都很难看:(
有什么办法吗?我可以声明它以保证元组的第二个元素是第一个元素声明的
#Result
类型的一部分吗?有几种方法可以消除任何
。以下是我目前可以想到的选项列表:
将结果与操作“关联”
针对某些解决方案的
问题标题似乎正好询问了一些
的:
(Operation[X], X) forSome { type X }
这里,类型变量X
由forSome
量词绑定,它保证列表中的元组只能存储匹配类型的操作和输出
虽然它禁止出现像(SomeOperation[String],Int)
这样的元组,但实例化变得有点麻烦:
val newResult: (Operation[Y], Y) forSome { type Y } = head match {
case op: Operation[t] => (op -> op(results))
}
t
在那里是一个名称。这有时有助于处理存在主义,因为它允许我们为存在主义类型命名,在本例中是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)
将返回操作
的相应方法添加到操作
,
一切都变得相当简单:
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
也许它应该只是一个单子?
最后,你问题的第一句话包含了这个短语
操作顺序,其中一些操作取决于以前操作的某些结果
在我看来,这几乎像是对单子的完美实用定义,因此,也许你想用来表示计算序列,而不是存在类型的列表。这里是一个粗略的草图:
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)
我将操作链延长了一个操作,以证明一元理解中的操作当然可以访问所有先前定义的中间结果(在本例中,foo
和fbr
),而不仅仅是前一个结果。您可以为某些{type X}设置results=List[(操作[X],X)]
,或者简单地引入一个新的案例类Result[X](op:Operation[X],output:X)
,而不是使用元组。但这无助于您摆脱标记为foobar
的行中的任何。您确定首先需要一个包含存在类型的操作列表吗?因为“操作顺序,其中一些操作取决于以前操作的某些结果“听起来很像monad,而且这些都可以很好地用scala的类型系统来实现。@Andreytukin我知道monad是什么…但不确定你在这里得到了什么。flatMap
在这里不太有效,因为我的操作不一定取决于上一个(为了通用性,我需要整个列表)。实际上,在标有//foobar
的行中没有Any
(这只是Operation[\u]
),Any
在第一行,声明结果。对于某些{type X},将其更改为list[(操作[X],X)]
不编译:(head->head(results))::results
因“found(Operation[\u$1],Any)…”而失败。我想,问题是X
对于所有元素都必须是相同的,这不是这里的情况……是的,它以某种方式推断出\u$1
和Any
,而不是针对某些{type t}的(Op[t],t)
。我的答案的第一部分显示了如何通过在模式匹配的左侧引入类型变量t
来解决这个问题:scala的模式匹配的一个被低估的特性,imho。我添加了存在类型
标记,它似乎比路径依赖类型
更接近问题,因为有一些\uuuu
但是没有p.T
-s。monad解决方案的问题就是我想避免硬编码操作。这个想法是将一个列表传递到applyAll
,而不是在中拼写出每一个调用,以理解的内容……实际上列表相当大。但是,操作[X]
thingy似乎有效(我可以发誓我试过了,但它不允许我用通配符添加到列表中,但我猜,我在列表中输入了错误的内容)@Dima您可能在操作
上没有返回操作
的方法,而是尝试在applyAll
中实例化新操作
,因此失败的原因与您在下面的评论中提到的第一次尝试对某些
的原因相同。如果您想省略opWithResult
,您将再次需要类似于head match{case op:Operation[t]=>new OpRes(op,op.apply(results))
-使用类型参数t
的构造来实例化OpRes
。您可能尝试了一些非常类似的方法,但很快就将其视为不可编译/可修复。
previous: foobar, pre-previous: foo