gracelang / language

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

Under the ADT model, what about primitives for all objects? #153

Open kjx opened 6 years ago

kjx commented 6 years ago

The Full William Cook ADT Model #152 apparently works OK for primitive objects --- but what about the primitive behaviour for all objects? In the Smalltalk/Self/NS object model, each object conceptually has a primitive data part that can be though of has holding its identity (at least), perhaps class too, etc, and then primitive behaviours can be invoked by whatever mechanism to access that primitive data part. Because every object has that primitive data, those primitives (at least) apply to all objects.

I guess the super-hardcore approach would be to say: OK - another cookie for (manipulable) identity.

import "vmObjectADT" as vmObjectADT
class graceObject -> Object  { 
   def cookie is public = vmObjectADT.newUniqueIdentity
   method ==(other : Object) -> Boolean { boolean( vmObjectADT.eq(self.cookie, other.cookie) ) }
   method hash -> Number { integer( vmObjectADT.hash(self) ) }
}

OK, I'm actually tempted to step back to an NS-style solution because I don't think there's anything too terrible about objects having identity built in:

import "vmObjectADT" as vmObjectADT
class graceObject -> Object  { 
   method ==(other : Object) -> Boolean { makeBoolean( vmObjectADT.eq(self, other) ) }
   method hash -> Number { integer( vmObjectADT.hash(self) ) }
}

(Note that because subclasses can override == you've only got a left-handed equals.)
But do we want every object to have an identity-based equals whether we want them to or not? Time, then, to go to one more remove:

trait equality {
   method ==(other) { isMe(other) }
   method !=(other) {! ==(other) }
   method hash { identityHash }

   method isMe(other) is required { } 
   method identityHash is required { } 
}

trait identity {
   use equality
   method isMe(other) is confidential { makeBoolean( vmObjectADT.eq(self, other) ) }
   method identityHash is confidential -> Number { integer( vmObjectADT.hash(self) ) }
}

If you want to support equality operations, then use equality. You'll have to define isMe and hash, of course, but that's OK. If you're equality really is object identity, then use identity and that's what you get - predefined correct versions of everything. Keeping the names isMe and identityHash makes things clearer, and means if you want to have a more complex equality, you can override == and != without having to alias anything...

While we're at it, what's makeBoolean? Well we want boolean to be the abstract superclass of true and false and other truey and falsey things: but somehow the VM has to return a truth value that will get lifted to the appropriate Grace Boolean. Something like this:

trait boolean { 
 method ifTrue(block) { ifTrue( block ) ifFalse( done ) } 
 //lots of boolean auxiliary methods
} 
object true = {
 inherit boolean
 method prefix! { false }
 method ifTrue(block)ifFalse(_) { block.apply } 
 ...
}
object false = {
 inherit boolean
 method prefix! { true }
 method ifTrue(_)ifFalse(block) { block.apply } 
 ...
}

method mkBoolean( b : VmBoolean ) -> Boolean {
    vmObjectADT.if(b) then(true) else(false)
}
apblack commented 6 years ago

I'm not sure what this "issue" has to do with the language definition. In particular, I don't think that the language definition should prescribe the implementation of numbers, booleans and strings. Different implementations will do it differently. The same goes for exceptions.

Pragmatically, the language spec should say that numbers and booleans exist, and describe their methods. It should also say that you can't "reuse" true or 10.

It would be useful if the language specified a standard way of adding methods to the intrinsic classes like number, boolean and string. One way that we could do this would be to say that if a dialect defines a trait called numberTrait, then any methods therein would be available on numbers. The effect would as if the the built-in number implementation does a use on that trait, but this effect could be obtained in any way that the implementor chose. [Perhaps it is cleaner to require that NumberTrait be defined, and put the non-basic number operations in that trait, written in Grace. (% for example, which takes care to observe the correct equivalences, could be defined in Grace).

We can do the same with booleanTrait. This would not contain the implementation of the most basic boolean operations, such as ifTrue(_)ifFalse(_) and and(_), but might contain the rarely used ones like nand. AN implementation would still be in control of th representation of booleans, and would be free to inline conditionals, while-loops, etc.