gracelang / language

Design of the Grace language and its libraries
GNU General Public License v2.0
6 stars 1 forks source link

methods of graceObject #155

Closed apblack closed 1 year ago

apblack commented 6 years ago

In the spec it says that any object not inheriting from anything else inherits from graceObject, and that graceObject defines the following methods:

Method Return value
isMe (other:Object) → Boolean; confidential true if other is the same object as self
(other:Object) → Boolean the inverse of ==
asString → String a string describing self
asDebugString → String a string describing the internals of self
:: (other:Object) → Binding a Binding object with self as key and other as value

The method was put there (by me) as a placeholder, until I had implemented traits; once we have a trait identityEquaility defined by something like

method identityEquaility {
    def hashCache = random.integerBetween 0 and maxHash
    object {
        method == (other) { isMe(other) }
        method ≠(other) { (self == other).not }
        method hash { hashCache }
    }
}

there is no reason to put in graceObject.

I've also realized that, since == is no longer in graceObject, :: should not be in graceObjecteither: if you can't test an object for equality, you can't use it as a key in a dictionary, which is the motivation for ::. So we should move :: to traits identityEquaility and abstractEquality(the latter being a trait that requires the user to define == and hash and provides and ::).

Much as I really liked the conclusion in The Left hand of Equals essay — that we should have a confidential isMe method in every object, which the object can choose to expose if it so wishes — we don't really need isMe either. This is because we also admitted in that essay that we needed an identity-testing method in mirror, and once we have that, the identityEquality trait can define

    method = (other) { mirror.isIdential (self, other) }

and the user of the identityEquality trait won't see any difference.

This leaves graceObject with just the two stringification methods asString and asDebugString. In practice, I really like having those methods be available by default, and I've taken some pains in minigrace to try to make the default implementations be useful. So, for example,

def horrible = object { }
print(horrible)

prints "a horrible", not "an object". But the fact remains that the type Object is now the same as the type Done, which doesn't feel right. Object and Done still have different pragmatics: one is intended to be the supertype of all other types, while the other is intended to be the return type of an operation (such as an assignment) that has no result. Our type system doesn't reflect this difference, though.

To some extent we can sidestep this issue by saying that graceObject (the superobject off all objects) is defined by the current dialect, and thus is not part of the core language, whereas Done is defined by the core language, because it characterizes the object returned by an assignment (which I think has to be defined in core Grace). If we do that, though, we need a way of defining graceObject in a dialect that does not require graceObject to be already defined. I've thought of using

    inherit none

for this, but then we have to ask: where is none defined? And so on.

@kjx has argued that we should be "pure" and get rid of graceObject altogether, so that type Object would be empty and there would be no default inheritance. I've argued against that because of the enormous practical advantages of having every object support asString and asDebugString.

Like most issues in OO, the real concern here is not whether we have default code to stringify any object — we need that regardless, because user-written asDebugString methods can fail — but where to put it.

kjx commented 6 years ago

So, the thing is, once you've got rid of everything in graceObject except asString and debugString we should think hard about whether they should stay there too.

print(horrible prints "a horrible", not "an object".

How does it do that? Seems to me saying that print does not depend on asString or debugString - and then does magic - is better than adding complexity to the inheritance mechanism to inherit that magical implementation. There's nothing wrong with print or anything else testing to see whether than object supports either of these requests, but that doesn't mean that

the real concern here is not whether we have default code to stringify any object — we need that regardless, because user-written asDebugString methods can fail — but where to put it.

right - and the complexity costs different choices impose on the rest of the language.

Printing/stringification, I think, we can handle in various ways. Equality seems more important to me. Ideally we would look through @KimBruce's book (or other curricula) to answer questions like:

kjx commented 6 years ago

see also #153, #49...

kjx commented 6 years ago

inherit none

None could be defined by magic. It could even (almost) be done - but see comments on #152

kjx commented 6 years ago

I tried looking through Kim's book - as far as I can see, == etc are only used on primitives or library objects, graceObject is mentioned on p421, key descriptions are on 431-434, and 438. Pretty much every example of a comparison - even after that - seems to be on primitives or library objects. There are only 4 occurrences of "method ==" in the book --- and very worryingly, no mentions of overriding hash when overriding equals...

HMM: turns out we've been over this already. https://github.com/gracelang/language/issues/49#issuecomment-223725001

seems like Kim would prefer ==, != and friends in all objects, but is happy with a trait to include them. I think the simplification of inheritance is worth it.

apblack commented 6 years ago

Yes, we did discuss this already, when I removed == from graceObject in minigrace. I opened the issue again because of my realization that :: didn't make sense without ==.

What I propose it try in SmallGrace is as follows:

  1. graceObject is intrinsic. It has the stringification methods, and confidential methods isMe(_) and identityHash. Because these methods are all intrinsic, they can do "magic" stuff. (For example, the way that minigrace's asString returns the name of the class or the def that created an object is "magic", in the sense that it looks at the context surrounding the definition.)

  2. The trait identityEquality (is alwaysEquality a better name?) adds methods == (defined using isMe(_)) and (defined to be the inverse of ==), and hash (defined using identityHash). This can be in standardGrace: it requires no magic. identityEquality will also define ::

  3. A trait abstractEquality (or maybe nowEquality?) requires the programmer to specify == and hash, and adds , and ::. It can also be defined in standardGrace.

A related point is that requires is not the same as abstract, I think.

kjx commented 6 years ago

A related point is that requires is not the same as abstract, I think.

answered back at https://github.com/gracelang/language/issues/32#issuecomment-364731803

kjx commented 6 years ago

(is alwaysEquality a better name?)
(or maybe nowEquality?)

yeah I think so. now, anyway.

kjx commented 6 years ago

we adopt the small grace design

apblack commented 6 years ago

You can see what's in SmallGrace in the github repo.

Right now, the trait that implements identity equality is called identityEquality; I can certainly change that to alwaysEqualityif we wish. I suppose that there should also be a trait abstractEquality, or nowEquality, which would require client-supplied hash and == methods, but would supply ::, and . That's easy to add. There is also a type EqualityObject which is effectively what Object used to be:

    type EqualityObject = Object & interface {
        ::(_:Object) -> Binding
        ==(_:Object) -> Boolean
        ≠(_:Object) -> Boolean
        hash -> Number
    }

It describes objects with either of the equality traits.

apblack commented 5 years ago

Here are the changes that I propose making to the spec. If you want to see them nicely formatted and in color, view them on github

Otherwise, here is the diff; + is an addition, and - is a deletion.

@@ -1216,17 +1216,36 @@ default methods.
 |  Method                            |    Return value                                           |
 | :-----------------------------------------------  | :------------------------------------------- |
 | `isMe (other:Object) → Boolean`; _confidential_  |    true if other is the same object as self |
-| $\neq$ `(other:Object) → Boolean`|    the inverse of ==                                 |
+| `myIdentityHash → Number`; _confidential_  |   a hash code characteristic of this object  |
 | `asString → String`               |    a string describing self                          |
 | `asDebugString → String`          |    a string describing the internals of self         |
-| `:: (other:Object) → Binding`     |  a Binding object with `self` as key and `other` as value|

-Notice that `graceObject` implements $\neq$ but not `==`.
-This is to help ensure that, when an object chooses to implement `==`,
-$\neq$ is also available, and is the inverse of `==`.
-If desired, the _confidential_ method `isMe` can be used in the implementation of a
-public `==` method.
+Notice that `graceObject` implements neither `==` nor `≠`.
+In the _standardGrace_ dialect, the trait `equality` is available to help
+in their implementation.
+
+
+    trait equality {
+        method == (other) is required
+        method hash is required     // should obey invariant (a == b) => (a.hash == b.hash).
+        method ≠ (other)  { (self == other).not }
+        method :: (obj) { binding.key (self) value (obj) }
+    }
+    
+As the `is required` indicates, an object using this trait must provide an `==` method,
+and a corresponding `hash` method.
+One way to define these methods is by combining the equality and hash on the
+results of all the observer methods;
+another is to use `identityEquality`, which defines `==` as object identity and `hash` as 
+identity hash.
+
+
+    trait identityEquality {
+        use equality
+        method == (other) { self.isMe(other) }
+        method hash { self.myIdentityHash }
+    }

 # Method Requests

@@ -1819,21 +1838,27 @@ Type `None` is completely empty; it has no methods.

 ### Type Object

-The type `Object` includes methods to which most
-objects respond --- the [Default Methods] declared in
-`graceObject`. Some objects, notably `done`, do not conform to `Object`.
-
+In _standardGrace_, type `Object` includes just the public [Default Methods] declared in
+`graceObject`.

-    type Object = {
-       != (other: Object) -> Boolean          // the inverse of ==
+    type Object = interface {
        asString -> String                     // a string for use by the client
        asDebugString -> String                // a string for use by the implementor
-       :: (other:Object) -> Binding           // a binding with self as the key
     }

-Notice that `isMe`, although present in [`graceObject`](#default-methods), is not present in
-type `Object`, because it is *confidential*.
-Also notice that neither `graceObject` nor type `Object` include `==`.
+Notice that `isMe`, and `myIdentityHash`. although present in [`graceObject`](#default-methods),
+are not present in type `Object`, because they are *confidential*.
+
+### Type EqualityObject
+
+In _standardGrace_, type `EqualityObject` adds the family of equality methods to `Object`:
+
+    type EqualityObject = Object & interface {
+        ::(o:Object) -> Binding
+        ==(other:Object) -> Boolean
+        ≠(other:Object) -> Boolean
+        hash -> Number
+    }

 ### Type Self
KimBruce commented 5 years ago

Seems reasonable to me.

Kim

On Sep 12, 2018, at 10:25 PM, Andrew Black notifications@github.com wrote:

Here are the changes that I propose making to the spec. If you want to see them nicely formatted and in color, view them on github https://github.com/gracelang/language/compare/master...equalityChanges Otherwise, here is the diff; + is an addition, and - is a deletion.

@@ -1216,17 +1216,36 @@ default methods. Method Return value
isMe (other:Object) → Boolean; confidential true if other is the same object as self
- $\neq$ (other:Object) → Boolean the inverse of ==
+ myIdentityHash → Number; confidential a hash code characteristic of this object
asString → String a string describing self
asDebugString → String a string describing the internals of self
- :: (other:Object) → Binding a Binding object with self as key and other as value

-Notice that graceObject implements $\neq$ but not ==. -This is to help ensure that, when an object chooses to implement ==, -$\neq$ is also available, and is the inverse of ==. -If desired, the confidential method isMe can be used in the implementation of a -public == method. +Notice that graceObject implements neither == nor . +In the standardGrace dialect, the trait equality is available to help +in their implementation. + +

  • trait equality {
  • method == (other) is required
  • method hash is required // should obey invariant (a == b) => (a.hash == b.hash).
  • method ≠ (other) { (self == other).not }
  • method :: (obj) { binding.key (self) value (obj) }
  • }
  • +As the is required indicates, an object using this trait must provide an == method, +and a corresponding hash method. +One way to define these methods is by combining the equality and hash on the +results of all the observer methods; +another is to use identityEquality, which defines == as object identity and hash as +identity hash.

  • trait identityEquality {
  • use equality
  • method == (other) { self.isMe(other) }
  • method hash { self.myIdentityHash }
  • }

    Method Requests

@@ -1819,21 +1838,27 @@ Type None is completely empty; it has no methods.

Type Object

-The type Object includes methods to which most -objects respond --- the [Default Methods] declared in -graceObject. Some objects, notably done, do not conform to Object.

+In standardGrace, type Object includes just the public [Default Methods] declared in +graceObject.

  • type Object = {
  • != (other: Object) -> Boolean // the inverse of ==
  • type Object = interface { asString -> String // a string for use by the client asDebugString -> String // a string for use by the implementor
  • :: (other:Object) -> Binding // a binding with self as the key }

-Notice that isMe, although present in graceObject, is not present in -type Object, because it is confidential. -Also notice that neither graceObject nor type Object include ==. +Notice that isMe, and myIdentityHash. although present in graceObject, +are not present in type Object, because they are confidential. + +### Type EqualityObject + +In standardGrace, type EqualityObject adds the family of equality methods to Object: +