案例类、模式匹配和可变参数

发布于 2025-01-03 23:58:08 字数 316 浏览 0 评论 0原文

假设我有这样的类层次结构:

abstract class Expr
case class Var(name: String) extends Expr
case class ExpList(listExp: List[Expr]) extends Expr

像这样定义 ExpList 的构造函数会更好吗:

case class ExpList(listExp: Expr*) extends Expr

我想知道,每个定义在模式匹配方面的缺点/优点是什么?

Let's say I have such class hierarchy:

abstract class Expr
case class Var(name: String) extends Expr
case class ExpList(listExp: List[Expr]) extends Expr

Would it be better to define constructor of ExpList like this:

case class ExpList(listExp: Expr*) extends Expr

I would like to know, what are drawbacks/benefits of each definitions regards pattern matching?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

猥琐帝 2025-01-10 23:58:08

让我们回答这里涉及的不同问题。我确实会推荐这种语法:

case class ExpList(listExp: Expr*) extends Expr

但答案取决于您的编码示例。那么让我们看看如何在模式匹配中使用可变参数、何时使用 List 以及 WrappedArray 的问题。
一点小评论:ExprExpList 之间的不一致(有或没有“r”?)在输入并试图记住哪个是哪个时会出现问题 - 坚持一个约定, Exp 足够清晰并且经常使用。

可变参数和模式匹配

让我们首先考虑这个声明:

abstract class Expr
case class ExpList(listExp: Expr*) extends Expr
case class Var(name: String) extends Expr

这个代码示例:

val v = Var("a")
val result = for (i <- Seq(ExpList(v), ExpList(v, v), ExpList(v, v, v))) yield (i match {
  case ExpList(a) => "Length 1 (%s)" format a
  case ExpList(a, b, c, d @ _*) => "Length >= 3 (%s, %s, %s, %s...)" format (a, b, c, d)
  case ExpList(args @ _*) => "Any length: " + args
})
result foreach println

产生:

Length 1 (Var(a))
Any length: WrappedArray(Var(a), Var(a))
Length >= 3 (Var(a), Var(a), Var(a), WrappedArray()...)

我在这里使用的:
ExpList(a, b) 匹配具有两个子项的 ExpList; ExpList(a) 匹配具有一个子项的 ExpList。
_* 是匹配 A 类型的值序列的模式,可以是任意长(包括 0)。
我还使用模式绑定器,identifier@pattern,它允许绑定一个对象,同时还可以使用另一个模式进一步解构它;它们适用于任何模式,而不仅仅是 _*

当使用 identifier @ _* 时,identifier 绑定到类型 Seq[A]

所有这些构造也适用于 Seq;但如果我们在声明中使用 Seq ,如下所示:

case class ExpList(listExp: Seq[Expr]) extends Expr

相同的 case 子句会从(例如) case ExpList(a, b, c, d @ _*) =>;case ExpList(Seq(a, b, c, d @ _*)) =>。所以语法更加混乱。

从语法上讲,Expr* 唯一“难”的是编写以下函数,该函数从表达式列表构造 ExpList:(

def f(x: Seq[Expr]) = ExpList(x: _*)

再次)注意 _* 的使用代码>这里。

当您在列表头构造函数上进行模式匹配时, List

List 使用起来很方便,如 xs match { case head :: tail =>; ...情况无=> }。但是,通常可以使用折叠来更紧凑地表达此代码,并且如果您不以这种风格编写代码,则无需使用 List。特别是在界面中,仅需要代码需要的内容通常是一种很好的做法。

可变性

我们上面讨论的是不变性。案例类的实例应该是不可变的。现在,当使用 Expr* 时,case 类的参数实际上具有 collection.Seq[Expr] 类型,并且该类型包含可变实例 - 事实上,ExprList 将接收子类 WrappedArray 的实例,该实例是可变的。请注意,collection.Seqcollection.mutable.Seqcollection.immutable.Seq 的超类,后者别名为<默认情况下,代码>Seq。

人们无法在不向下转换的情况下改变这样的值,但有人仍然有可能这样做(我不知道出于什么原因)。

如果您想阻止客户端执行此操作,则需要将收到的值转换为不可变序列 - 但在使用 case class ExpList(listExp: Expr*) extends Expr 声明 ExpList 时无法执行此操作

您需要使用另一个构造函数。
要在其他代码中进行转换,由于 toSeq 返回原始序列,因此您必须使用列表内容作为可变参数调用 Seq 的构造函数。因此,您可以使用上面显示的语法,Seq(listExpr: _*)
目前这并不重要,因为 Seq 的默认实现是 List,但将来可能会改变(也许会变得更快,谁知道呢?)。

擦除问题

不能声明同一方法的两个重载,一个采用 T*,另一个采用 Seq[T],因为在输出类中它们将变得相同。可以使用一个小技巧让 m 看起来不同并且有两个构造函数:

case class ExpList(listExp: Seq[Expr]) extends Expr
object ExpList {
 def apply(listExp: Expr*)(implicit d: DummyImplicit) = new ExpList(Seq(listExp: _*))
}

这里我还将数组转换为不可变序列,如上所述。不幸的是,模式匹配已经完成,如上面的示例所示,其中 case 类接受 Seq[Expr] 而不是 Expr*

Let's answer the different questions involved here. I would indeed recommend this syntax:

case class ExpList(listExp: Expr*) extends Expr

But the answer depends on your coding example. So let's see how to use varargs in pattern matching, when to use List, and the problem with WrappedArray.
A small remark: the inconsistency between Expr and ExpList (with or without 'r'?) is problematic when typing and trying to remember which is which - stick to one convention, Exp is clear enough and often used.

Varargs and pattern matching

Let us first consider this declaration:

abstract class Expr
case class ExpList(listExp: Expr*) extends Expr
case class Var(name: String) extends Expr

And this code example:

val v = Var("a")
val result = for (i <- Seq(ExpList(v), ExpList(v, v), ExpList(v, v, v))) yield (i match {
  case ExpList(a) => "Length 1 (%s)" format a
  case ExpList(a, b, c, d @ _*) => "Length >= 3 (%s, %s, %s, %s...)" format (a, b, c, d)
  case ExpList(args @ _*) => "Any length: " + args
})
result foreach println

produces:

Length 1 (Var(a))
Any length: WrappedArray(Var(a), Var(a))
Length >= 3 (Var(a), Var(a), Var(a), WrappedArray()...)

What I use here:
ExpList(a, b) matches a ExpList with two children; ExpList(a) matches a ExpList with one child.
_* is a pattern which matches sequences of values of type A which can be arbitrarily long (including 0).
I also use pattern binders, identifier @ pattern, which allow to bind an object while also further destructuring it with another pattern; they work with any pattern, not just _*.

When using identifier @ _*, identifier is bound to type Seq[A].

All these constructs also apply to Seq; but if we do use Seq in the declaration, like this:

case class ExpList(listExp: Seq[Expr]) extends Expr

the same case clauses change from (for instance) case ExpList(a, b, c, d @ _*) => to case ExpList(Seq(a, b, c, d @ _*)) =>. So more syntactic clutter.

Syntactically speaking, the only thing which is 'harder' with Expr* is writing the following function, which constructs a ExpList from an expression list:

def f(x: Seq[Expr]) = ExpList(x: _*)

Note the use (again) of _* here.

The List class

List is convenient to use when you pattern match on the list head constructor, as in xs match { case head :: tail => ... case Nil => }. However, usually this code can be expressed more compactly using folds, and if you are not writing code in this style, you needn't use List. Especially in an interface, it is often good practice to require only what your code is going to need.

Mutability

What we discussed above concerned immutability. Instances of case classes should be immutable. Now, when using Expr*, the parameter of the case class has in fact type collection.Seq[Expr], and this type contains mutable instances - in fact, ExprList will receive an instance of the subclass WrappedArray, which is mutable. Note that collection.Seq is a superclass of both collection.mutable.Seq and collection.immutable.Seq, and the latter is aliased to Seq by default.

One cannot mutate such a value without downcasting it, but it is still possible for somebody to do it (I don't know for what reason).

If you want to prevent your client from doing it, you need to convert the value you receive to an immutable sequence - but you cannot do it when declaring ExpList with case class ExpList(listExp: Expr*) extends Expr.

You need instead to use another constructor.
To do the conversion in the other code, since toSeq returns the original sequence, you must call Seq's constructor with the content of the list as variadic arguments. Hence, you use the syntax I showed above, Seq(listExpr: _*).
Presently that does not matter so much since Seq's default implementation is List, but that might change in the future (maybe to something faster, who knows?).

Erasure problems

One cannot declare two overloads of the same method, one taking T* and one taking Seq[T], because in the output class they would become the same. A little trick to make the m look different and have two constructors can be used:

case class ExpList(listExp: Seq[Expr]) extends Expr
object ExpList {
 def apply(listExp: Expr*)(implicit d: DummyImplicit) = new ExpList(Seq(listExp: _*))
}

Here I also convert the array to an immutable sequence, as above. Pattern matching is done, unfortunately, as in the example above where the case class accepts Seq[Expr] instead of Expr*.

一个人的夜不怕黑 2025-01-10 23:58:08

您可以同时拥有这两个构造函数:

case class ExpList(listExp: List[Expr]) extends Expr
object ExpList {
  def apply(listExp: Expr*) = new ExpList(listExp.toList)
}

//now you can do
ExpList(List(Var("foo"), Var("bar")))
//or
ExpList(Var("foo"), Var("bar"))

可变参数会转换为 mutable.WrappedArray,因此为了与不可变案例类的约定保持一致,您应该使用列表作为实际值。

You can have both constructors:

case class ExpList(listExp: List[Expr]) extends Expr
object ExpList {
  def apply(listExp: Expr*) = new ExpList(listExp.toList)
}

//now you can do
ExpList(List(Var("foo"), Var("bar")))
//or
ExpList(Var("foo"), Var("bar"))

Variadic arguments are converted to a mutable.WrappedArray, so to keep in line with the convention of case classes being immutable, you should use a list as the actual value.

梦幻的心爱 2025-01-10 23:58:08

正如对 Dan 解决方案的评论:如果您在函数中包含此内容,则由于 Scala 中的错误无法正常工作 https://issues.scala-lang.org/browse/SI-3772。您会得到类似这样的信息:

scala> :paste
// Entering paste mode (ctrl-D to finish)
    def g(){
        class Expr {}
        case class ExpList(listExp: List[Expr]) extends Expr
        object ExpList {
          def apply(listExp: Expr*) = new ExpList(listExp.toList)
        }
    }
// Exiting paste mode, now interpreting.

<console>:10: error: ExpList is already defined as (compiler-generated) case cla
ss companion object ExpList
                    object ExpList {
                           ^

现在,解决方法就是将对象放在第一位。

scala> :paste
// Entering paste mode (ctrl-D to finish)
    def g(){
        class Expr {}
        object ExpList {
          def apply(listExp: Expr*) = new ExpList(listExp.toList)
        }
        case class ExpList(listExp: List[Expr]) extends Expr
    }

// Exiting paste mode, now interpreting.
g: ()Unit

我希望这能防止人们像我一样被这个错误绊倒。

Just as a comment to Dan's solution: If you have this inside a function it does, due to the bug in Scala not work https://issues.scala-lang.org/browse/SI-3772. You get something like:

scala> :paste
// Entering paste mode (ctrl-D to finish)
    def g(){
        class Expr {}
        case class ExpList(listExp: List[Expr]) extends Expr
        object ExpList {
          def apply(listExp: Expr*) = new ExpList(listExp.toList)
        }
    }
// Exiting paste mode, now interpreting.

<console>:10: error: ExpList is already defined as (compiler-generated) case cla
ss companion object ExpList
                    object ExpList {
                           ^

For now the workaround is simply to put the object first.

scala> :paste
// Entering paste mode (ctrl-D to finish)
    def g(){
        class Expr {}
        object ExpList {
          def apply(listExp: Expr*) = new ExpList(listExp.toList)
        }
        case class ExpList(listExp: List[Expr]) extends Expr
    }

// Exiting paste mode, now interpreting.
g: ()Unit

I hope that will prevent people from stumbling over this bug as I did.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文