Case classes/wrapper classes

147 views
Skip to first unread message

Luke deGruchy

unread,
Jun 9, 2014, 6:14:57 PM6/9/14
to ceylon...@googlegroups.com
After reading this article:

http://techblog.realestate.com.au/the-abject-failure-of-weak-typing/

I'm wondering if a useful feature is missing from Ceylon.

In particular, I'm referring to that of simple wrapper/case classes that implement semantics for hashcode/equals/tostring.

For example, if I want a simple class for an email String:

class Email(shared String emailStr) { /* possible validation code in initializer */}

I would need to implement my own hashcode/equals/string attributes/methods, especially if I want to use them as part of a collection, would I not? Then again, I suppose there are inherent risks to the compiler generating such code, even on a limited basis?

Tako Schotanus

unread,
Jun 9, 2014, 6:42:21 PM6/9/14
to ceylon...@googlegroups.com
Hi Luke,

you might be interested in this issue: https://github.com/ceylon/ceylon-spec/issues/971

Cheers.
-Tako



-Tako



--
You received this message because you are subscribed to the Google Groups "ceylon-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ceylon-users...@googlegroups.com.
To post to this group, send email to ceylon...@googlegroups.com.
Visit this group at http://groups.google.com/group/ceylon-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/a6a7985b-c037-4512-9801-7b0032cab2ed%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Luke deGruchy

unread,
Jun 9, 2014, 7:06:59 PM6/9/14
to ceylon...@googlegroups.com, ta...@codejive.org
Hi Tako,

Wasn't aware of this.  It looks promising!

Gavin King

unread,
Jun 9, 2014, 10:14:10 PM6/9/14
to ceylon...@googlegroups.com
Nice article, though I think he seriously overstates the case against
exceptions. (Even though we use them _less_ in Ceylon, they _do_ have
an important role to play.)

As mentioned by Tako, I would eventually like to introduce
annotation-driven autogeneration of equals()/hash/string, and perhaps
even of compare().

On Tue, Jun 10, 2014 at 12:14 AM, Luke deGruchy <ldeg...@gmail.com> wrote:
> --
> You received this message because you are subscribed to the Google Groups "ceylon-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to ceylon-users...@googlegroups.com.
> To post to this group, send email to ceylon...@googlegroups.com.
> Visit this group at http://groups.google.com/group/ceylon-users.
> To view this discussion on the web visit https://groups.google.com/d/msgid/ceylon-users/a6a7985b-c037-4512-9801-7b0032cab2ed%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Gavin King
ga...@ceylon-lang.org
http://profiles.google.com/gavin.king
http://ceylon-lang.org
http://hibernate.org
http://seamframework.org

Luke deGruchy

unread,
Jun 10, 2014, 8:16:39 AM6/10/14
to ceylon...@googlegroups.com
Apple's Swift has left out exceptions entirely, although I'm wondering if developer feedback will force them to add them back.

At the very least, Ceylon needs runtime exceptions to be compatible with the JVM and Java libraries.

I'm assuming the equals()/hash/string annotations will make use of the metamodel?

Gavin King

unread,
Jun 10, 2014, 8:33:52 AM6/10/14
to ceylon...@googlegroups.com
Look, the argument that (unchecked) exceptions are a hole in the type
system is, by definition, correct. And it's intentional. Their purpose
is to let you handle a problem far away from where it occurs. I don't
know *any* other way to do that. You certainly can't do it with
union/sum-typed return values which by nature *have* to be handled by
the immediate caller.
Experience from Java (checked exceptions) and Haskell (typed side
effects via stuff like the IO monad) has shown that it's an utter pain
to maintain code which indicates all its possible side-effects in the
signatures of its methods. In Java you end up with "throws Exception"
all over the place. In Haskell you end up writing everything in the IO
monad.

Another thing the author of that article misses is *void* functions.
There's no way (in a language like Scala or Ceylon) to force the
caller to handle an exceptional case when the function is being called
for its side-effect. (To be fair, that's not an issue in a language
like Haskell, but it is for us).
> https://groups.google.com/d/msgid/ceylon-users/6e683219-ca3f-4110-b892-4e7d327d4a9e%40googlegroups.com.

Philippe Lhoste

unread,
Jun 18, 2014, 6:46:23 AM6/18/14
to ceylon...@googlegroups.com
On Tuesday, 10 June 2014 00:14:57 UTC+2, Luke deGruchy wrote:
In particular, I'm referring to that of simple wrapper/case classes that implement semantics for hashcode/equals/tostring.

For example, if I want a simple class for an email String:

class Email(shared String emailStr) { /* possible validation code in initializer */}

I would need to implement my own hashcode/equals/string attributes/methods, especially if I want to use them as part of a collection, would I not? Then again, I suppose there are inherent risks to the compiler generating such code, even on a limited basis?

Guava offers functions for easy / correct implementation of hashCode, equals and toString (in Java):

    @Override
    public boolean equals(Object obj)
    {
        if (!(obj instanceof SomeClass)
          return false;
        SomeClass sc = (SomeClass) obj;
        return Objects.equal(sc.field1, field1) && Objects.equal(sc.field2, field2);
    }

    @Override
    public int hashCode()
    {
        return Objects.hashCode(field1, field2);
    }

    @Override
    public String toString()
    {
        return Objects.toStringHelper(this).add("field1", field1).add("field2", field2).toString();
    }

Perhaps Ceylon can offer similar facilities.
The compiler cannot generated these classes, as there are many cases where the identity relies only on a limited set of fields. You might want to exclude the color or state of some visual object to test them for equality.

BTW, I was (mildly) shocked to see the example in the Tour of Ceylon: http://ceylon-lang.org/documentation/1.0/tour/inheritance/ where hash wasn't using the same fields than equals... :-)

Gavin King

unread,
Jun 18, 2014, 8:59:38 AM6/18/14
to ceylon...@googlegroups.com
On Wed, Jun 18, 2014 at 12:46 PM, Philippe Lhoste <philh...@gmail.com> wrote:
> The compiler cannot generated these classes, as there are many cases where
> the identity relies only on a limited set of fields. You might want to
> exclude the color or state of some visual object to test them for equality.

That's what annotations are for :-)

> BTW, I was (mildly) shocked to see the example in the Tour of Ceylon:
> http://ceylon-lang.org/documentation/1.0/tour/inheritance/ where hash wasn't
> using the same fields than equals... :-)

It is acceptable for hash to use a subset of the fields used to
determine equality. In that example, that is probably a rather
inefficient hash function, but it's not *incorrect*.

Michael Piefel

unread,
Dec 3, 2014, 3:48:15 PM12/3/14
to ceylon...@googlegroups.com
Am Mittwoch, 18. Juni 2014 12:46:23 UTC+2 schrieb Philippe Lhoste:
Guava offers functions for easy / correct implementation of hashCode, equals and toString (in Java):


Even easier in most cases are the annotations from Project Lombok with annotation such as @ToString that generate all the code for you.

John Vasileff

unread,
Dec 3, 2014, 4:40:06 PM12/3/14
to ceylon...@googlegroups.com
Until something like that is available, you can use streams to cut down on boilerplate:

// Setup

shared Integer computeHash({Anything*} items) {
  variable value hash = 1;
  for (item in items) {
    hash *= 31;
    if (exists item) {
      hash += item.hash;
    }
  }
  return hash;
}

shared Boolean equalItems({Anything*} first, {Anything*} second) {
  function nullEqual(Anything first, Anything second) {
    if (exists first, exists second) {
      return first == second;
    } else {
      return !(first exists || second exists);
    }
  }
  return corresponding(first, second, nullEqual);
}

// Usage

shared class Point2d(x, y, label, desc) {
  shared variable Integer x;
  shared variable Integer y;
  shared variable String? label;
  shared variable String? desc;

  shared default {Anything*} identifyingValues = { x, y, label };

  shared actual default String string => identifyingValues.string;
  shared actual default Integer hash => computeHash(identifyingValues);
  shared default Boolean canEqual(Object other) => other is Point2d;
  shared actual default Boolean equals(Object that) {
    if (is Point2d that, canEqual(that) && that.canEqual(this)) {
      return this === that ||
        equalItems(this.identifyingValuesthat.identifyingValues);
    }
    return false;
  }
}

shared class Point3d(Integer x, Integer y, z, String? label, String? desc)
    extends Point2d(x, y, label, desc) {

  shared variable Integer z;
  shared actual {Anything*} identifyingValues = { x, y, z, label };
  shared actual default Boolean canEqual(Object other) => other is Point3d;
}

The identifyingValues property actually makes a case for a protected access level! It’s pretty useless outside of the class hierarchy. BTW, this won't always work right now due to https://github.com/ceylon/ceylon-compiler/issues/1888.

John
Reply all
Reply to author
Forward
0 new messages