Closed apblack closed 1 year 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:
use identityEquality
on every object that's going to be looked up in a collection?use equalityTrait
as part of that definition?see also #153, #49...
inherit none
None could be defined by magic. It could even (almost) be done
- but see comments on #152
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.
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:
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.)
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 ::
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.
A related point is that
requires
is not the same asabstract
, I think.
answered back at https://github.com/gracelang/language/issues/32#issuecomment-364731803
(is alwaysEquality a better name?)
(or maybe nowEquality?)
yeah I think so. now, anyway.
we adopt the small grace design
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 alwaysEquality
if 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.
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
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
; confidentialtrue if other is the same object as self - $\neq$ (other:Object) → Boolean
the inverse of == + myIdentityHash → Number
; confidentiala 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 andother
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 methodisMe
can be used in the implementation of a -public==
method. +Notice thatgraceObject
implements neither==
nor≠
. +In the standardGrace dialect, the traitequality
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 correspondinghash
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 useidentityEquality
, which defines==
as object identity andhash
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, notablydone
, do not conform toObject
.+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 ingraceObject
, is not present in -typeObject
, because it is confidential. -Also notice that neithergraceObject
nor typeObject
include==
. +Notice thatisMe
, andmyIdentityHash
. although present ingraceObject
, +are not present in typeObject
, because they are confidential. + +### Type EqualityObject + +In standardGrace, typeEqualityObject
adds the family of equality methods toObject
: +
- type EqualityObject = Object & interface {
- ::(o:Object) -> Binding
- ==(other:Object) -> Boolean
- ≠(other:Object) -> Boolean
- hash -> Number
}
Type Self
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/gracelang/language/issues/155#issuecomment-420887478, or mute the thread https://github.com/notifications/unsubscribe-auth/ABuh-uCri360NN6ctwNsp37_1F6m35LGks5uaew6gaJpZM4R5LOB.
In the spec it says that any object not inheriting from anything else inherits from graceObject, and that graceObject defines the following methods:
isMe (other:Object) → Boolean
; confidential(other:Object) → Boolean
asString → String
asDebugString → String
:: (other:Object) → Binding
self
as key andother
as valueThe method
≠
was put there (by me) as a placeholder, until I had implemented traits; once we have a traitidentityEquaility
defined by something likethere is no reason to put
≠
ingraceObject
.I've also realized that, since
==
is no longer ingraceObject
,::
should not be ingraceObject
either: 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 traitsidentityEquaility
andabstractEquality
(the latter being a trait that requires the user to define==
andhash
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 needisMe
either. This is because we also admitted in that essay that we needed an identity-testing method inmirror
, and once we have that, theidentityEquality
trait can defineand the user of the
identityEquality
trait won't see any difference.This leaves
graceObject
with just the two stringification methodsasString
andasDebugString
. 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,prints "a horrible", not "an object". But the fact remains that the type
Object
is now the same as the typeDone
, which doesn't feel right.Object
andDone
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, whereasDone
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 defininggraceObject
in a dialect that does not requiregraceObject
to be already defined. I've thought of usingfor 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 supportasString
andasDebugString
.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.