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?
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.
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.
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.
List
classList
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.
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?).
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*
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With