案例类、模式匹配和可变参数
假设我有这样的类层次结构:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
让我们回答这里涉及的不同问题。我确实会推荐这种语法:
但答案取决于您的编码示例。那么让我们看看如何在模式匹配中使用可变参数、何时使用
List
以及 WrappedArray 的问题。一点小评论:
Expr
和ExpList
之间的不一致(有或没有“r”?)在输入并试图记住哪个是哪个时会出现问题 - 坚持一个约定,Exp
足够清晰并且经常使用。可变参数和模式匹配
让我们首先考虑这个声明:
这个代码示例:
产生:
我在这里使用的:
ExpList(a, b)
匹配具有两个子项的 ExpList;ExpList(a)
匹配具有一个子项的 ExpList。_*
是匹配A
类型的值序列的模式,可以是任意长(包括 0)。我还使用模式绑定器,
identifier@pattern
,它允许绑定一个对象,同时还可以使用另一个模式进一步解构它;它们适用于任何模式,而不仅仅是_*
。当使用
identifier @ _*
时,identifier
绑定到类型Seq[A]
。所有这些构造也适用于
Seq
;但如果我们在声明中使用Seq
,如下所示:相同的 case 子句会从(例如)
case ExpList(a, b, c, d @ _*) =>;
到case ExpList(Seq(a, b, c, d @ _*)) =>
。所以语法更加混乱。从语法上讲,
Expr*
唯一“难”的是编写以下函数,该函数从表达式列表构造 ExpList:(再次)注意
_*
的使用代码>这里。当您在列表头构造函数上进行模式匹配时,
List
类List
使用起来很方便,如xs match { case head :: tail =>; ...情况无=> }
。但是,通常可以使用折叠来更紧凑地表达此代码,并且如果您不以这种风格编写代码,则无需使用List
。特别是在界面中,仅需要代码需要的内容通常是一种很好的做法。可变性
我们上面讨论的是不变性。案例类的实例应该是不可变的。现在,当使用
Expr*
时,case 类的参数实际上具有collection.Seq[Expr]
类型,并且该类型包含可变实例 - 事实上,ExprList 将接收子类WrappedArray
的实例,该实例是可变的。请注意,collection.Seq
是collection.mutable.Seq
和collection.immutable.Seq
的超类,后者别名为<默认情况下,代码>Seq。人们无法在不向下转换的情况下改变这样的值,但有人仍然有可能这样做(我不知道出于什么原因)。
如果您想阻止客户端执行此操作,则需要将收到的值转换为不可变序列 - 但在使用
case class ExpList(listExp: Expr*) extends Expr 声明 ExpList 时无法执行此操作
。您需要使用另一个构造函数。
要在其他代码中进行转换,由于
toSeq
返回原始序列,因此您必须使用列表内容作为可变参数调用Seq
的构造函数。因此,您可以使用上面显示的语法,Seq(listExpr: _*)
。目前这并不重要,因为
Seq
的默认实现是List
,但将来可能会改变(也许会变得更快,谁知道呢?)。擦除问题
不能声明同一方法的两个重载,一个采用
T*
,另一个采用Seq[T]
,因为在输出类中它们将变得相同。可以使用一个小技巧让 m 看起来不同并且有两个构造函数:这里我还将数组转换为不可变序列,如上所述。不幸的是,模式匹配已经完成,如上面的示例所示,其中 case 类接受
Seq[Expr]
而不是Expr*
。Let's answer the different questions involved here. I would indeed recommend this syntax:
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
andExpList
(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:
And this code example:
produces:
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 typeA
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 typeSeq[A]
.All these constructs also apply to
Seq
; but if we do useSeq
in the declaration, like this:the same case clauses change from (for instance)
case ExpList(a, b, c, d @ _*) =>
tocase 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:Note the use (again) of
_*
here.The
List
classList
is convenient to use when you pattern match on the list head constructor, as inxs 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 useList
. 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 typecollection.Seq[Expr]
, and this type contains mutable instances - in fact, ExprList will receive an instance of the subclassWrappedArray
, which is mutable. Note thatcollection.Seq
is a superclass of bothcollection.mutable.Seq
andcollection.immutable.Seq
, and the latter is aliased toSeq
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 callSeq
'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 isList
, 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 takingSeq[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: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 ofExpr*
.您可以同时拥有这两个构造函数:
可变参数会转换为
mutable.WrappedArray
,因此为了与不可变案例类的约定保持一致,您应该使用列表作为实际值。You can have both constructors:
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.正如对 Dan 解决方案的评论:如果您在函数中包含此内容,则由于 Scala 中的错误无法正常工作 https://issues.scala-lang.org/browse/SI-3772。您会得到类似这样的信息:
现在,解决方法就是将对象放在第一位。
我希望这能防止人们像我一样被这个错误绊倒。
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:
For now the workaround is simply to put the object first.
I hope that will prevent people from stumbling over this bug as I did.