Scala:基于类型的列表分区

时间:2014-08-09 19:25:42

标签: scala types shapeless

我有这个代码,我想改进:

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]]) = {
  // ...
}

我想在以下方面对此进行改进:

  1. 我可以更改partition的返回类型,以便它说明第二个列表中没有Foo[B]吗?
  2. 我是否可以删除Foo的类型参数T(即将Foo更改为case class Foo(a: A))并仍然使用partition声明(List[Foo], List[Foo])相同的类型保证? (显然,它必须返回与{{1}}不同的东西。)
  3. P.S。:让我知道是否&#34;无形&#34;标签与此问题无关。

2 个答案:

答案 0 :(得分:9)

这个问题有点棘手,因为Scala混淆了algebraic data types(就像你的A)和子类型。在大多数使用ADT的语言中,BCD根本不是类型 - 他们只是&#34;构造函数&#34; (在某种意义上,它与OOP构造函数相似但不相同)。

在这些语言(如Haskell或OCaml)中讨论Foo[B]是没有意义的,但在Scala中你可以,因为Scala实现了ADT作为案例类(和类对象)扩展了基本特征或阶级。这并不意味着你应该四处谈论Foo[B],而且一般来说,如果你想用FP术语思考并使用类型系统来发挥你的优势,那么#&# 39;最好不要。

回答您的具体问题:

  1. 不,不是以任何方便的方式。您可以使用带标记的联合(带有Either[Foo[C], Foo[D]]元素的列表)或类似于Shapeless的Coproduct(带有Foo[C] :+: Foo[D] :+: CNil元素的列表)来表示&#34;列表A类型的东西,但不是B&#34;,但这两种方法都是相当重的机器,可能不是最好的想法。
  2. 我建议不要在Foo的子类型上对A进行参数化,但是如果您希望能够表示包含{Foo的{​​{1}} 1}}&#34;在类型级别,您需要保持当前的方法。
  3. 要解决您的后记:如果您想要概括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]]) = ???