- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Repost: wondering about case class inheritance
Fri, 2010-03-19, 08:18
I posted this on scala-user in the hopes that the answer would help
others, but I suspect that my question was either a) annoying or b) too
technical. In the case that the reason is (b), could someone point me
in the right direction of the answer? (Also, once I understand how
things work, I'm happy to write up something for a faq, blog entry, spec
comment, etc., since this issue seems to be a point of confusion.)
---
So, I've read up on all I could find about the deprecation of case class
inheritance. I'm *not* arguing that it shouldn't be deprecated; just the
mere fact that it causes things to get hairy is reason enough to push it
far, far away.
That said, something is definitely missing in my understanding of case
classes. I read that it causes problems for the copy method and the
pattern matcher, but what confuses me is the comparison to Effective C++
advice that non-leaf classes should be abstract
(http://www.informit.com/content/images/020163371x/items/item33.html).
Now, there are languages that enforce this behavior, e.g. Sather and
Fortress, and traits in Scala. But Scala *does* allow inheriting from
concrete classes, and a suggested workaround to the new deprecation is
to create regular classes with apply and unapply methods (e.g. on this
thread
http://old.nabble.com/-scala--Do-you-use-case-class-inheritance--td22869...).
My question is, why is it *not* problematic to create a concrete
subclass of a case class in the way that case class inheritance is? In
other words, we've still violated the "rule" of making non-leaf classes
abstract.
I suspect the answer is something along the lines of "case class
inheritance causes more badness than regular concrete class inheritance
because of the auto-generated methods and the pattern matcher," but I've
been unable to find an explanation of "why is this exactly?"
(Apologies if I've just missed some obvious answer on the web, and sorry
to beat a dead horse...)
thanks,
Donna
Fri, 2010-03-19, 21:17
#2
RE: Repost: wondering about case class inheritance
> Specificationwise, martin or others probably have more examples than I
> do, but here's one which comes to mind because I only came to understand
> the relevant part of the spec recently:
>
> case class A(x: Int)
> case class B(x: Int) extends A(x*2)
> def f = B(5) match { case A(_) => true }
>
> Should this match? You may be surprised to know (I sure was) that the
> specification actually demands this be a static type error.
>
> a.scala:4: error: constructor cannot be instantiated to expected type;
> found : o.A
> required: o.B
> def f = B(5) match { case A(_) => true }
> ^
>
> Even after martin said this was as specified I couldn't figure out why
> until he pointed at the right paragraph. See:
>
> https://lampsvn.epfl.ch/trac/scala/ticket/2904
Off the top of my head, I would think it would be a great kindness to Scala users if the
specification for the behavior of case classes in pattern matches followed the WWUD
("What would unapply do?") principle: a case class acts the same as a non-case class
with a hand-coded unapply method in its companion object.
That would mean that the above example should behave as in:
object Wwud {
class A (val x: Int)
object A {
def apply (x: Int): A = new A(x)
def unapply (a: A): Option[Int] = Some(a.x)
}
class B (override val x: Int) extends A(x * 2)
object B {
def apply (x: Int): B = new B(x)
def unapply (b: B): Option[Int] = Some(b.x)
}
def main (args: Array[String]) {
println(B(5) match { case A(_) => true })
}
}
which in fact compiles under 2.8.0.Beta1 and displays "true".
(Is this really the desired behavior for unapply? Would it really be feasible to reconcile
the specifications for hand-written unapply and case classes? I think questions like these
explain the lack of response to PaulP's craigslist ads.)
A
Hotmail: Trusted email with powerful SPAM protection. Sign up now.
Fri, 2010-03-19, 21:47
#3
RE: Repost: wondering about case class inheritance
> So, I've read up on all I could find about the deprecation of case class
> inheritance. I'm *not* arguing that it shouldn't be deprecated; just the
> mere fact that it causes things to get hairy is reason enough to push it
> far, far away.
>
> That said, something is definitely missing in my understanding of case
> classes. I read that it causes problems for the copy method and the
> pattern matcher, but what confuses me is the comparison to Effective C++
> advice that non-leaf classes should be abstract
> (http://www.informit.com/content/images/020163371x/items/item33.html).
>
> Now, there are languages that enforce this behavior, e.g. Sather and
> Fortress, and traits in Scala. But Scala *does* allow inheriting from
> concrete classes, and a suggested workaround to the new deprecation is
> to create regular classes with apply and unapply methods (e.g. on this
> thread
> http://old.nabble.com/-scala--Do-you-use-case-class-inheritance--td22869...).
> My question is, why is it *not* problematic to create a concrete
> subclass of a case class in the way that case class inheritance is? In
> other words, we've still violated the "rule" of making non-leaf classes
> abstract.
>
> I suspect the answer is something along the lines of "case class
> inheritance causes more badness than regular concrete class inheritance
> because of the auto-generated methods and the pattern matcher," but I've
> been unable to find an explanation of "why is this exactly?"
The rule that says to avoid inheritance from concrete classes is a useful rule
of thumb in designing complex systems, but like many rules of thumb, sometimes
you should violate it.
There are times when you have a concrete class A, and you want a thing B that is
just like A except for some small change. I think it's reasonable to use
concrete inheritance to achieve this, particularly if:
. The application in question is unlikely to evolve in such a way that an
abstraction of A would be independently useful, or
. A is not under your control (it is part of a library that did not include an
abstract superclass for A that does what you want).
Slavishly following the rule against concrete inheritance can lead to a world
where you reflexively create a separate abstract class or trait for every
concrete class, just in case someone might want to inherit from it; but such a
proliferation of classes is unkind to anyone who needs to read your code.
To reduce boilerplate in general, it would be nice if Scala supported,
independently of pattern matching, some of the features of case classes in a
way that is compatible with concrete inheritance (I elaborated on that idea in
http://old.nabble.com/2.8-copy-method-needs-improvement-td27681490.html and
http://old.nabble.com/Case-classes-and-their-discontents-td27526235.html. Some
of the responses in those threads answered my own "why?" questions, and might
answer yours).
A
Hotmail: Trusted email with Microsoft’s powerful SPAM protection. Sign up now.
On Fri, Mar 19, 2010 at 08:18:01AM +0100, Donna Malayeri wrote:
> I posted this on scala-user in the hopes that the answer would help
> others, but I suspect that my question was either a) annoying or b)
> too technical. In the case that the reason is (b), could someone
> point me in the right direction of the answer?
There are two independent classes of motivation: implementation and
specification. My own motivation was implementation, and I can't get
too specific here except to say that any number of bugs in the matcher
are triggered by it. The algorithm was implemented in a pretty
inscrutable way and makes a bunch of assumptions which are violated
(partly because, as I understand it, both case class inheritance and
extractors were implemented after the fact.) And having already lost the
best years of my life to the matcher and with an eerie silence to the
"help wanted, no pay, long tedious hours, must hate self" ad I put on
craigslist, I thought narrowing the spec was better than perpetuating
the bugginess.
Specificationwise, martin or others probably have more examples than I
do, but here's one which comes to mind because I only came to understand
the relevant part of the spec recently:
case class A(x: Int)
case class B(x: Int) extends A(x*2)
def f = B(5) match { case A(_) => true }
Should this match? You may be surprised to know (I sure was) that the
specification actually demands this be a static type error.
a.scala:4: error: constructor cannot be instantiated to expected type;
found : o.A
required: o.B
def f = B(5) match { case A(_) => true }
^
Even after martin said this was as specified I couldn't figure out why
until he pointed at the right paragraph. See:
https://lampsvn.epfl.ch/trac/scala/ticket/2904
There are a lot of variations on this I think, where in the face of case
class inheritance there are at least two reasonable behaviors in many
spots, and no matter what you choose it will surprise people. And I
don't want to have to read the spec to figure out how pattern matching
is going to proceed. It already has too many subtleties.