Scala:基于类型的列表分区 [英] Scala: type-based list partitioning
问题描述
我有这段代码想要改进:
I have this code that I would like to improve on:
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]]) = {
// ...
}
我想在以下方面进行改进:
I would like to improve on this in the following respects:
- 我是否可以更改
partition
的返回类型,使其表明第二个列表中没有Foo[B]
? - 我可以去掉
Foo
的类型参数T
吗(即将Foo
改成case class Foo(a: A)
) 并且仍然声明partition
具有相同的类型保证?(显然,它必须返回与(List[Foo], List[Foo])
不同的内容.)
- Can I change
partition
's return type so that it states that there is noFoo[B]
in the second list? - Can I get rid of
Foo
's type parameterT
(i.e. changeFoo
tocase class Foo(a: A)
) and still declarepartition
with the same type guarantees? (Obviously, it would have to return something different than(List[Foo], List[Foo])
.)
P.S.:如果无形"标签与此问题无关,请告诉我.
P.S.: Let me know if "shapeless" tag is not relevant for this question.
推荐答案
这个问题有点棘手,因为 Scala 的混合方式 代数数据类型(比如你的A
)和子类型.在大多数带有 ADT 的语言中,B
、C
和 D
根本不是类型——它们只是构造函数"(从某种意义上说,这与 OOP 构造函数相似但又不同).
This question is a little tricky because of the way that Scala mixes up algebraic data types (like your A
) and subtyping. In most languages with ADTs, B
, C
, and D
wouldn't be types at all—they'd just be "constructors" (in a sense that's similar to but not the same as OOP constructors).
在这些语言(如 Haskell 或 OCaml)中谈论 Foo[B]
没有意义,但在 Scala 中你可以,因为 Scala 将 ADT 实现为案例类(和类对象)扩展基本特征或类.不过,这并不意味着您应该到处谈论 Foo[B]
,而且一般来说,如果您想用 FP 术语思考并将类型系统用于您的优势,最好不要这样做.
It wouldn't make sense to talk about a Foo[B]
in these languages (like Haskell or OCaml), but in Scala you can, because Scala implements ADTs as case classes (and class objects) extending a base trait or class. That doesn't mean you should go around talking about Foo[B]
, though, and in general if you want to think in FP terms and use the type system to your advantage, it's a good idea not to.
回答您的具体问题:
- 不,不是很方便.您可以使用带标签的联合(带有
Either[Foo[C], Foo[D]]
元素的列表)或类似 Shapeless 的Coproduct
(带有Foo[C] :+: Foo[D] :+: CNil
元素)表示A
类型的事物列表,但不是B
",但这两种方法都是相当繁重的机器,首先可能不是最好的主意. - 我建议不要在
A
的子类型上参数化Foo
,但是如果您希望能够表示aFoo
在类型级别包含B
",您将需要保持当前的方法.
- No, not really in any convenient way. You could use a tagged union (a list with
Either[Foo[C], Foo[D]]
elements) or something like Shapeless'sCoproduct
(a list withFoo[C] :+: Foo[D] :+: CNil
elements) to represent "a list of things of typeA
, but notB
", but both approaches are fairly heavy machinery for something that probably isn't the best idea in the first place. - I'd recommend not parametrizing
Foo
on the subtype ofA
, but if you want to be able to represent "aFoo
that contains aB
" at the type level, you're going to need to keep your current approach.
为了解决您的后记:如果您想对 ADT 进行概括,Shapeless 绝对适用——请参阅我的博客文章 此处 关于通过构造函数进行分区.但是,如果您只为 A
执行此操作,Shapeless 可能不会给您带来太多好处.
To address your postscript: if you want to generalize over ADTs, Shapeless is definitely applicable—see my blog post here about partitioning by constructor, for example. If you're only doing this for A
, though, Shapeless probably won't buy you much.
作为一个脚注,如果我真的需要一个划分 Foo[B]
类型元素的分区操作,我可能会这样写:
As a footnote, if I really needed a partitioning operation that split out elements of type Foo[B]
, I'd probably write it like this:
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]]
,最好有一个 List[Foo[~B]]
代表剩菜——但还不错.
It's not ideal—if we really want a List[Foo[B]]
, it'd be nice to have a List[Foo[~B]]
to represent the leftovers—but it's not too bad.
这篇关于Scala:基于类型的列表分区的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!