This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Re: RichString

6 replies
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.

On Thu, May 14, 2009 at 10:51:52PM +0200, martin odersky wrote:
> No, I think that's precisely what we have to do. We do it already so
> that a java.lang.Float(1.0) is the same as a java.lang.Double(1.0). We
> now have to add cases for arrays (because they now have by-element
> equality) as well as strings.

So what do you think about this approach? With a very small diff, this
is implemented so anyone can use it. I know having "===" suddenly be
special would be problematic, but ignoring that for a second:

+ val STRONGEQ = encode("===")

And then when equals method is generated:

+ lazy val leq = l.tpe.nonPrivateDecl(nme.STRONGEQ)
+ lazy val req = r.tpe.nonPrivateDecl(nme.STRONGEQ)
+
+ if (leq != NoSymbol) (leq, l, r)
+ else if (req != NoSymbol) (req, r, l)
+ else (definitions.Object_equals, l, r)

Seems to work great - if either tpe has defined an "===" member, that
method is preferred to the Object equals.

The one difficulty is that in this example l.tpe is the generic result
type of reverse rather than the type in RichString's override. So if I
only define === in RichString, it is not called, because the code thinks
it's comparing a VectorTemplate and a String.

I defined === in VectorTemplate and overrode it in RichString, and then
all went according to plan. But I haven't figure out why I have to do
it, and obviously this band-aid wouldn't fly for the general case.

scala> "bob".reverse == "bob"
l.tpe = scala.collection.generic.VectorTemplate r.tpe = java.lang.String("bob")

scala> ("bob".reverse : scala.runtime.RichString) == "bob"
l.tpe = scala.runtime.RichString r.tpe = java.lang.String("bob")

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: RichString

On Sat, May 16, 2009 at 5:36 PM, Paul Phillips wrote:
> On Thu, May 14, 2009 at 10:51:52PM +0200, martin odersky wrote:
>> No, I think that's precisely what we have to do. We do it already so
>> that a java.lang.Float(1.0) is the same as a java.lang.Double(1.0). We
>> now have to add cases for arrays (because they now have by-element
>> equality) as well as strings.
>
> So what do you think about this approach? With a very small diff, this
> is implemented so anyone can use it.  I know having "===" suddenly be
> special would be problematic, but ignoring that for a second:
>
> +    val STRONGEQ = encode("===")
>
> And then when equals method is generated:
>
> +        lazy val leq = l.tpe.nonPrivateDecl(nme.STRONGEQ)
> +        lazy val req = r.tpe.nonPrivateDecl(nme.STRONGEQ)
> +
> +        if (leq != NoSymbol) (leq, l, r)
> +        else if (req != NoSymbol) (req, r, l)
> +        else (definitions.Object_equals, l, r)
>
> Seems to work great - if either tpe has defined an "===" member, that
> method is preferred to the Object equals.
>
It's simple and elegant, but as you noted suffers from the problem that
static type abstraction changes behavior. I.e. there's no hope that

("bob": Object) == ("bob".reverse: Object)

would give true. I think we want it to be true in which case we need
to do something else (change BoxedRunTime.equals, probably).

Cheers

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: RichString

On Sat, May 16, 2009 at 06:14:41PM +0200, martin odersky wrote:
> I think we want it to be true in which case we need to do something
> else (change BoxedRunTime.equals, probably).

Exactly. Now it's this way, and this looks like a winner so far:

class RichString extends ... with StrongEquality {
...
def ===(other: Any) = other match {
case x: String => self == x
case _ => this == other
}
}

public interface StrongEquality {
public boolean $eq$eq$eq(Object other);
}

and in BoxesRunTime, the intervening equals:

if (a instanceof StrongEquality)
return (((StrongEquality)a).$eq$eq$eq(b));
if (b instanceof StrongEquality)
return (((StrongEquality)b).$eq$eq$eq(a));

return equals(a, b);

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: RichString

On Sat, May 16, 2009 at 8:23 PM, Paul Phillips wrote:
> On Sat, May 16, 2009 at 06:14:41PM +0200, martin odersky wrote:
>> I think we want it to be true in which case we need to do something
>> else (change BoxedRunTime.equals, probably).
>
> Exactly.  Now it's this way, and this looks like a winner so far:
>
> class RichString extends ... with StrongEquality {
>  ...
>  def ===(other: Any) = other match {
>    case x: String  => self == x
>    case _          => this == other
>  }
> }
>
> public interface StrongEquality {
>  public boolean $eq$eq$eq(Object other);
> }
>
> and in BoxesRunTime, the intervening equals:
>
>      if (a instanceof StrongEquality)
>          return (((StrongEquality)a).$eq$eq$eq(b));
>      if (b instanceof StrongEquality)
>          return (((StrongEquality)b).$eq$eq$eq(a));
>
>      return equals(a, b);
>
> --
> Paul Phillips      | Where there's smoke, there's mirrors!
> Imperfectionist    |
> Empiricist         |
> pal, i pill push   |----------* http://www.improving.org/paulp/ *----------
>
>

On second thought: Why do we need an === method? Is it not enough to
just use StrongEquals a s a marker interface, and implement all with
equals? The idea is that, in

a == b

if b is an instance of StrongEquals and a is not, we take b's equals
method instead of a's. So the
BoxedRunTime test becomes:

if (b.isInstanceOf[StrongEquals] && !a.isInstanceOf[StrongEquals])
b.equals(a)
else
a.equals(b)

This is simpler and faster.

Cheers

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: RichString

[Oops, the email I sent 5 seconds after the last one got randomly denied
at gmail.]

Oh, I guess we're switching from BoxesRunTime to the object equals
method. OK, I have to run so I'm not thinking clearly, but maybe this
is fine. I'll get back on it in a few hours.

(I was also kind of hoping our enhanced equals method could be strongly
typed and define == null to be false, but I hadn't gotten that far yet.)

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: RichString

On Sat, May 16, 2009 at 8:23 PM, Paul Phillips wrote:
> On Sat, May 16, 2009 at 06:14:41PM +0200, martin odersky wrote:
>> I think we want it to be true in which case we need to do something
>> else (change BoxedRunTime.equals, probably).
>
> Exactly.  Now it's this way, and this looks like a winner so far:
>
> class RichString extends ... with StrongEquality {
>  ...
>  def ===(other: Any) = other match {
>    case x: String  => self == x
>    case _          => this == other
>  }
> }
>
> public interface StrongEquality {
>  public boolean $eq$eq$eq(Object other);
> }
>
> and in BoxesRunTime, the intervening equals:
>
>      if (a instanceof StrongEquality)
>          return (((StrongEquality)a).$eq$eq$eq(b));
>      if (b instanceof StrongEquality)
>          return (((StrongEquality)b).$eq$eq$eq(a));
>
>      return equals(a, b);
>
Ah. Yes, that works. I'm all in favor!

Cheers

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: RichString

On Sat, May 16, 2009 at 08:39:28PM +0200, martin odersky wrote:
> if b is an instance of StrongEquals and a is not, we take b's equals
> method instead of a's. So the
> BoxedRunTime test becomes:

> if (b.isInstanceOf[StrongEquals] && !a.isInstanceOf[StrongEquals])
> b.equals(a)
> else
> a.equals(b)

The problem is recursion. If that logic is inside equals then you'll
just keep running into it. If equals discriminates based on the marker
interface, there's no way to undiscriminate just by calling it again
with the same objects.

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland