关联两个类型的参数 [英] Correlate two type parameters
问题描述
假设我有一系列操作,其中一些操作取决于先前操作的一些结果.像这样:
Suppose, I have a sequence of operations, some of which depend on some of the results of previous ones. Something like this:
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
类型的吗?
This works, but Any
in the result list looks ugly :(
Is there a way around it? Can I declare it somehow to guarantee that second element of the tuple is the of the #Result
type declared by the first element?
推荐答案
有几种方法可以摆脱Any
.这是到目前为止我可以想到的选项列表:
There are several ways to get rid of Any
. Here is the list of options I could come up with so far:
- 使用
forSome
将结果与操作关联" - 定义一个既包含操作又包含结果的自定义类
- 将整个设计从列表转换为monad
- Use
forSome
to "correlate" result with the operation - Define a custom class that holds both operations and results
- Convert the whole design from lists to monad
forSome
解决方案
The forSome
solution
问题标题似乎恰好是关于forSome
的问题:
The question title seems to ask exactly about the forSome
:
(Operation[X], X) forSome { type X }
在这里,类型变量X
由forSome
量词绑定,并保证列表中的元组只能存储匹配类型的操作和输出.
Here, the type variable X
is bound by the forSome
quantifier, and it guarantees that the tuples in your list can store only operations and outputs of matching types.
虽然它禁止像(SomeOperation[String], Int)
这样的元组的出现,但实例化却有点麻烦:
While it prohibits the occurrence of tuples like (SomeOperation[String], Int)
, the instantiation becomes a bit cumbersome:
val newResult: (Operation[Y], Y) forSome { type Y } = head match {
case op: Operation[t] => (op -> op(results))
}
Here is a demo of how this can be used:
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
.
It simply outputs foobar
, as before.
用于操作和结果的自定义类别
与其使用具有复杂的存在性的元组,不如使用它更容易 定义一个自定义类型以将操作与结果一起保存:
Instead of using tuples with complex existentials, it might be easier to define a custom type to hold operations together with results:
case class OpRes[X](op: Operation[X], result: X)
使用返回OpRes
的相应方法添加到Operation
,
一切都变得非常简单:
With a corresponding method returning OpRes
added to Operation
,
everything becomes rather straightforward:
def opWithResult(results: Results): OpRes[Out] = OpRes(this, apply(results))
这是一个完整的可编译示例:
Here is a full compilable example:
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
.
Again, it outputs foobar
, as before.
也许应该只是一个单子吗?
最后,您的问题的第一句话包含短语
Finally, the first sentence of your question contains the phrase
操作顺序,其中一些取决于先前操作的结果
sequence of operations, some of which depend on some of the results of previous ones
在我看来,这几乎像一个monad的完美实用定义,因此也许您想用for
-comprehensions而不是存在类型的列表来表示计算序列.这是一个粗略的草图:
This seems to me almost like the perfect practical definition of what a monad is, so maybe you want to represent sequences of computations by for
-comprehensions instead of existentially typed lists. Here is a rough sketch:
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
我已将操作链延长了一个操作,以证明单子理解的操作当然可以访问以前定义的 all 个中间结果(在本例中为foo
和fbr
),不仅限于上一个.
I've made the chain of operations one operation longer to demonstrate that an operation in a monadic for-comprehension of course has access to all previously defined intermediate results (in this case, foo
and fbr
), not only to the previous one.
这篇关于关联两个类型的参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!