Scala:基于类型的列表分区 [英] Scala: type-based list partitioning

查看:50
本文介绍了Scala:基于类型的列表分区的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这段代码想要改进:

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:

  1. 我是否可以更改 partition 的返回类型,使其表明第二个列表中没有 Foo[B]?
  2. 我可以去掉Foo的类型参数T吗(即将Foo改成case class Foo(a: A)) 并且仍然声明​​ partition 具有相同的类型保证?(显然,它必须返回与 (List[Foo], List[Foo]) 不同的内容.)
  1. Can I change partition's return type so that it states that there is no Foo[B] in the second list?
  2. Can I get rid of Foo's type parameter T (i.e. change Foo to case class Foo(a: A)) and still declare partition 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 的语言中,BCD 根本不是类型——它们只是构造函数"(从某种意义上说,这与 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.

回答您的具体问题:

  1. 不,不是很方便.您可以使用带标签的联合(带有 Either[Foo[C], Foo[D]] 元素的列表)或类似 Shapeless 的 Coproduct(带有 Foo[C] :+: Foo[D] :+: CNil 元素)表示A 类型的事物列表,但不是 B",但这两种方法都是相当繁重的机器,首先可能不是最好的主意.
  2. 我建议不要在 A 的子类型上参数化 Foo,但是如果您希望能够表示a Foo在类型级别包含 B",您将需要保持当前的方法.
  1. 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's Coproduct (a list with Foo[C] :+: Foo[D] :+: CNil elements) to represent "a list of things of type A, but not B", but both approaches are fairly heavy machinery for something that probably isn't the best idea in the first place.
  2. I'd recommend not parametrizing Foo on the subtype of A, but if you want to be able to represent "a Foo that contains a B" 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆