Scala:基于类型的列表分区

Tom*_*ula 4 types scala 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]]) = {
  // ...
}
Run Code Online (Sandbox Code Playgroud)

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

  1. 我可以更改其partition返回类型,以便它表明Foo[B]第二个列表中没有吗?
  2. 我可以摆脱Foo类型参数T(即更改Foocase class Foo(a: A))并仍然partition使用相同的类型保证声明吗?(显然,它必须返回不同的东西(List[Foo], List[Foo]).)

PS:让我知道"无形"标签是否与此问题无关.

Tra*_*own 9

这个问题有点棘手,因为Scala混合了代数数据类型(如你的A)和子类型.在与ADT的大多数语言,B,C,并D不会在类型都-航空公司仅仅是"建设者"(在一定意义上,它类似于但不一样的OOP构造函数).

Foo[B]在这些语言(如Haskell或OCaml)中讨论a 是没有意义的,但在Scala中你可以,因为Scala将ADT实现为扩展基本特征或类的case类(和类对象).但这并不意味着你应该四处谈论Foo[B],一般而言,如果你想用FP术语思考并使用类型系统来获得优势,那么最好不要这样做.

回答您的具体问题:

  1. 不,不是真的以任何方便的方式.您可以使用带标记的联合(带有Either[Foo[C], Foo[D]]元素的列表)或类似Shapeless Coproduct(带有Foo[C] :+: Foo[D] :+: CNil元素的列表)来表示"类型的事物列表A,但不是B",但这两种方法都是相当重的机器,可能不是首先是最好的主意.
  2. 我建议不要对子Foo类型进行参数化A,但是如果你想在类型级别表示" Foo包含a B",那么你将需要保持当前的方法.

为了解决您的附言:如果你想概括了ADT的,无形的,绝对是适用的,看我的博客文章在这里有关分区的构造,例如.但是,如果你只是这样做A,那么Shapeless可能不会给你带来太大的收获.


作为一个脚注,如果我真的需要一个分割出类型元素的分区操作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)
  }
Run Code Online (Sandbox Code Playgroud)

这不是理想的 - 如果我们真的想要一个List[Foo[B]],那么有一个List[Foo[~B]]代表残羹剩饭很好- 但它并不太糟糕.