- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Equality and case objects
Observe the following code:
abstract class MyList{
def toList : List[Int] = this match {
case Nope => Nil
case Cons(x, y) => x :: y.toList
}
override def equals(that : Any) = that match {
case (that : MyList) => this.toList == that.toList;
case _ => false;
}
}
case class Cons(x : Int, y : MyList) extends MyList;
case object Nope extends MyList;
object Test{
def main(args : Array[String]){
println(Nope.toList)
}
}
Care to take a guess what happens?
Alas:java.lang.StackOverflowError
at MyList.equals(mylist.scala:7)
at MyList.toList(mylist.scala:2)
...
Alas, pattern matching on case objects (*not* case classes) invokes their equals method. So when the equals method is defined in terms of something which is defined in terms of pattern matching... yep. Badness happens.
I propose the following alteration to the spec: Case objects (not objects in general) will always override equality to mean objects equality, unless a parent defined equality as a final method or the user explicitly provides an equals method in the object definition
Any objections?
abstract class MyList{
def toList : List[Int] = this match {
case Nope => Nil
case Cons(x, y) => x :: y.toList
}
override def equals(that : Any) = that match {
case (that : MyList) => this.toList == that.toList;
case _ => false;
}
}
case class Cons(x : Int, y : MyList) extends MyList;
case object Nope extends MyList;
object Test{
def main(args : Array[String]){
println(Nope.toList)
}
}
Care to take a guess what happens?
Alas:java.lang.StackOverflowError
at MyList.equals(mylist.scala:7)
at MyList.toList(mylist.scala:2)
...
Alas, pattern matching on case objects (*not* case classes) invokes their equals method. So when the equals method is defined in terms of something which is defined in terms of pattern matching... yep. Badness happens.
I propose the following alteration to the spec: Case objects (not objects in general) will always override equality to mean objects equality, unless a parent defined equality as a final method or the user explicitly provides an equals method in the object definition
Any objections?










Re: Equality and case objects
Thanks. :-)
Back on the previous subject, what should this code do?
object Test {
def foo(x : Any) : Any = {
case class Foo();
println(x match {
case Foo() => true
case _ => false;
})
new Foo;
}
def main(args : Array[String]){
foo(foo(new AnyRef))
}
}
The two Foos come from different invocations of the method (which are
morally equivalent to different instances of an enclosing class in my
opinion), but they still match as eachother. This seems wrong, but I'm
not sure what to suggest as an alternative.
On Sun, Mar 1, 2009 at 1:15 PM, martin odersky wrote:
> Hi David,
>
> That's fine. Thanks for doing this in the first place! I completely
> understand that there are constraints how much time you can put into
> this when.
>
> Cheers
>
> -- Martin
>
>
> On Sun, Mar 1, 2009 at 2:10 PM, David MacIver wrote:
>> On Sun, Mar 1, 2009 at 9:43 AM, martin odersky wrote:
>>> On Sat, Feb 28, 2009 at 12:15 AM, David MacIver wrote:
>>>> On Fri, Feb 27, 2009 at 11:11 PM, Lex Spoon wrote:
>>>>>
>>>>> On Feb 28, 2009, at 12:23 AM, David MacIver wrote:
>>>>>>
>>>>>> So the match expression would be translated to:
>>>>>>
>>>>>> x match {
>>>>>> case (_: C) => ...
>>>>>> case (_: object O) => ...
>>>>>> case y if (y == this.V) => ...
>>>>>> }
>>>>>>
>>>>>> What's your opinion on this?
>>>>>>
>>>>>> I agree that the errors caused by this case are somewhat problematic, but
>>>>>> I'm not sure they occur commonly enough for it to be worth the spec
>>>>>> complication, particularly if (as suggested above) matching on inner objects
>>>>>> of a class becomes a warning. I'm also a little concerned that it's not
>>>>>> caught statically. If x is known to come from a different instance of Outer,
>>>>>> why is this not being rejected as the types of the matches not being
>>>>>> subtypes of the matched object? Between that and warning on matching inner
>>>>>> case classes I think that would catch most of the use cases adequately.
>>>>>
>>>>> This is my initial reaction, too, but more broadly. How important is it
>>>>> to support custom equality methods for case class hierarchies?
>>>>
>>>> I think it's pretty important. Case classes are basically used for creating
>>>> custom interesting data structures, and it can be unnecessarily difficult to
>>>> ensure that the user desired value of equality always matches that of the
>>>> case class.
>>>>
>>>>>
>>>>> If people have to use eq for case objects, then it makes those equals
>>>>> methods ugly, but it might simplify some more wide-reaching parts of Scala
>>>>> semantics.
>>>>>
>>>>> Specifically, it's a pity if a case object pattern matches differently
>>>>> than a val that attempts to have the same meaning as the case object.
>>>>> Someone might naturally refactor their code to change this:
>>>>>
>>>>> case object Foo extends Bar
>>>>>
>>>>> into this:
>>>>>
>>>>> class GeneralFoo extends Bar
>>>>> val Foo = new Bar
>>>>>
>>>>> Unfortunately, if the pattern matcher does different things, this will no
>>>>> longer be a safe refactoring.
>>>>
>>>> Well, that's why I proposed a version in which case object matching is
>>>> equivalent to stable identifier matching. :-)
>>>>
>>> I just checked that in
>>>
>>> class Outer {
>>> case class Inner()
>>> val i = new Inner()
>>> def isI(x: Any) = x match {
>>> case Inner() => true
>>> case _ => false
>>> }
>>> }
>>>
>>> The pattern match in isI will check the this-prefix. I.e. a.isI(b.i) is false.
>>> Somehow I was under the impression before that this was not the case.
>>> I think this tips the balance rather heavily in favor of David's
>>> proposal. I.e.
>>>
>>> case Inner() => checks this prefx
>>> case x: Inner => checks this prefix
>>> asInstanceOf[Inner] does not check this prefix but should.
>>>
>>> Previously I thought that the middle x: Inner was the odd man out, but
>>> it turns out it is really asInstanceOf which is different from the
>>> others. So I think we should fix asInstanceOf. This means some
>>> refactoring from the pattern matchewr to erasure where asInstanceOf is
>>> handled. Right now, in the pattern matcher we complement the
>>> asInstanceOf with an equality check of the prefix. This will be no
>>> longer necessary once it's done by the asInstanceOf itself.
>>
>> That seems reasonable.
>>
>> Now is as good a time as any to mention that, as you've probably
>> noticed, I'm not having much luck with the pattern matcher at the
>> moment. :-) Between work and other time sinks I'm only able to put
>> about a day's work here and there into it right now, and that's not
>> enough for me to really break the back of most of the remaining
>> problems with it, let alone start to make substantial changes to its
>> logic. My plan is that at some point in the not too distant future
>> I'll take a week off work to devote some solid time to it, and
>> hopefully I'll be able to make enough progress in the course of that
>> to get things back on track. Making changes to the way most of this
>> works will probably have to wait until then.
>>
>>
>
Re: Equality and case objects
On Sun, Mar 1, 2009 at 3:48 PM, David MacIver wrote:
> Thanks. :-)
>
> Back on the previous subject, what should this code do?
>
> object Test {
>
> def foo(x : Any) : Any = {
> case class Foo();
> println(x match {
> case Foo() => true
> case _ => false;
> })
> new Foo;
> }
>
> def main(args : Array[String]){
> foo(foo(new AnyRef))
> }
> }
>
> The two Foos come from different invocations of the method (which are
> morally equivalent to different instances of an enclosing class in my
> opinion), but they still match as eachother. This seems wrong, but I'm
> not sure what to suggest as an alternative.
Paul has pointed out a good solution: Simply create a "local scope"
object for the call and treat all classes defined within the scope to
be inner classes of that object.
Re: Equality and case objects
Sorry. "often" rather than "basically". There are of course other usages.