我有这个代码,我想改进:
sealed abstract class A
case class B() extends A
case class C() extends A
case class D() extends A
case class Foo[+T <: A](a: T)
/** Puts instances that match Foo(B()) in the first list and everything else,
* i.e. Foo(C()) and Foo(D()), in the second list. */
def partition(foos: List[Foo[_ <: A]]): (List[Foo[B]], List[Foo[_ <: A]]) = {
// ...
}
我想在以下方面对此进行改进:
partition
的返回类型,以便它说明第二个列表中没有Foo[B]
吗?Foo
的类型参数T
(即将Foo
更改为case class Foo(a: A)
)并仍然使用partition
声明(List[Foo], List[Foo])
相同的类型保证? (显然,它必须返回与{{1}}不同的东西。)P.S。:让我知道是否&#34;无形&#34;标签与此问题无关。
答案 0 :(得分:9)
这个问题有点棘手,因为Scala混淆了algebraic data types(就像你的A
)和子类型。在大多数使用ADT的语言中,B
,C
和D
根本不是类型 - 他们只是&#34;构造函数&#34; (在某种意义上,它与OOP构造函数相似但不相同)。
在这些语言(如Haskell或OCaml)中讨论Foo[B]
是没有意义的,但在Scala中你可以,因为Scala实现了ADT作为案例类(和类对象)扩展了基本特征或阶级。这并不意味着你应该四处谈论Foo[B]
,而且一般来说,如果你想用FP术语思考并使用类型系统来发挥你的优势,那么#&# 39;最好不要。
回答您的具体问题:
Either[Foo[C], Foo[D]]
元素的列表)或类似于Shapeless的Coproduct
(带有Foo[C] :+: Foo[D] :+: CNil
元素的列表)来表示&#34;列表A
类型的东西,但不是B
&#34;,但这两种方法都是相当重的机器,可能不是最好的想法。Foo
的子类型上对A
进行参数化,但是如果您希望能够表示包含{Foo
的{{1}} 1}}&#34;在类型级别,您需要保持当前的方法。要解决您的后记:如果您想要概括ADT,Shapeless绝对适用 - 请参阅我的博客文章here,例如关于按构造函数进行分区。如果你只为B
做这件事,那么,Shapeless可能不会给你带来太大的收获。
作为一个脚注,如果我真的需要一个分割出A
类型元素的分区操作,我可能会这样写:
Foo[B]
这不理想 - 如果我们真的想要一个def partition(foos: List[Foo[A]]): (List[Foo[B]], List[Foo[A]]) =
foos.foldRight((List.empty[Foo[B]], List.empty[Foo[A]])) {
case (Foo(B()), (bs, others)) => (Foo(B()) :: bs, others)
case (other, (bs, others)) => (bs, other :: others)
}
,那么让List[Foo[B]]
代表残羹剩饭会很高兴 - 但它也不是坏。
答案 1 :(得分:2)
我要展示的不是一个非常灵活的解决方案。它的用处完全取决于你试图建模的东西,但我认为这将是一个有趣的方法。
您可以在层次结构中引入第二个特征,例如T
,并从中扩展所有非B
个案例类。
sealed trait A
sealed trait T extends A
case class B() extends A
case class C() extends A with T
case class D() extends A with T
case class Foo[+T <: A](a: A)
def partition(foos: List[Foo[A]]): (List[Foo[B]], List[Foo[T]]) = ???