-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
What are primitive objects and methods, and how do you get to them? #152
Comments
I think that @kjx is making this more complicated that it needs to be. We can make classes How the intrinsics are defined is not part of the language spec. What the intrinsics do is part of the spec, but their implementation should be left to the language implementor. The implementation of the intrinsics will obviously differ from one implementation to another. In minigrace I've implemented the I really don't like appealing to ADTs (aren't objects good enough?), nor understand why doing so is any better than writing the intrinsic module using objects directly. Something like:
In fact, I don't see how the "ADT model" allows for bigNums or the Integer/Rational/Float hierarchy that we have talked about in the past. It seems to limit Grace to providing whatever cookies implement. Maybe I don't understand what it is really doing? |
I presume that what you meant to say is that the spec leaves open the question as to whether you can reuse built-in objects like booleans, numbers and strings. I don't agree with this. The spec says (or air least used to say) we can reuse the booleans. Why would the numbers and strings be any different? |
certainly!
sure. They have to be intrinsic some way or other. The question is whether we want those kind of magic classes or not. It's probably simpler if we do...
or rather, methods in the dialect can return the dialect-specify versions of integers etc.
well in most cases those methods would just be identity. Like you said elsewhere, the question is where the behaviour goes. In the objects, or elsewhere...
Hmm. Does that mean calls to things like |
not according to William Cook - those intrinsics really are ADTs, not objects.
Of course you can - in some sense, that's the whole point. The cookies reify platform primitive intrinsic data - that's all. In the ADT model, all interactions with platform primitive intrinsic data are mediated by Grace code. You can write literally whatever Grace code you want: you interact with platform primitive intrinsic data via the explicit interfaces provided by the ADTs. The model is pretty much exactly the same as the "platform/memory" interface works (low level methods to allocate primitive "arrays", and to read and write, but no other protocol; they're only effective when wrapped in a grace object. The twist is, ideally, we'd want the compiler to inline at least one of those primitive fields when possible. A bit harder to do for arrays which are mutable and shareable (but not impossible); but easier for machine floats and integers which are values.
Using intrinsic objects directly could be fine --- my cookies could just be your intrinsics. The catch, though is that either the intrinsics must have both primitive parts and object parts, or we can't extend them (normally) by writing Grace code. The temptation will always be there to use intrinsics directly. The ADT interface should be horrible enough to avoid that temptation --- while still permitting the cookies to be implemented directly as unboxed machine values --- on JS or the JVM, cookie numbers would be actual host VM cookies... |
OK so I shouldn't have said types - but on the other hand, I don't particularly like reuse meaning only inheritance but not composition.
It doesn't now:
Because they're literals, not generative, and because (unless you want to write one literal class for every Number or String value) I can't see how the superclass could be manifest? It might work if the spec also said that there were special |
because it's conceivable to implement Church Boolean efficiently, but not Church numbers? |
In "Grace's Inheritace" we write:
This of course implies that This line of reasoning makes me even more unhappy with our freshness constraint on inheritance — its insisting that the programmer ensure something that should that should be a invisible implementation detail. |
we might have written that, but perhaps we shouldn't have. There were designs where traits were actual objects - that's not the current design. The spec seems pretty clear.
I think removing the freshness constraint is incompatible with retaining pseudo-Javarian inheritance semantics. As Michael would say: objects, classes, pick one. The current design pretty much picks classes, and then the Black Equalities #134 let us pretend we're doing something closer to objects. Interpreted - ideally in a language with dynamic variables (or implicits) available - I don't think the current semantics come out as too bad - or rather, the freshness part of them isn't too bad. My dynamic semantics (#136) has 64/2000 lines dealing with the "creation" dynamic/implicit parameter --- but at least half are just wiring the thing though. Either with generic parameters or a more general mechanism for dynamic flags in the interpreter could streamline this I think so fewer than 20 lines would actually manage the freshness tracking. |
The point that I was making is not that the freshness constraint is hard to implement, but that it wires into the semantics an implementation issue that should be invisible. Whether an object that has only abstract identity (what @kjx calls a value object in #154) is cached or freshly generated ought to be a secret of its implementation.
I may be mis-remembering, but I think that's not quite right. We invented the freshness constraint to avoid having I see two advantages of doing it this way:
|
Well in that case we should take them out and shoot them. !!TRUMP!! Actually I was saying the opposite: I think with dynamic variable support in the interpreter / semantics, the freshness rules don't look too bad at all. Doesn't mean there aren't things we take out and shoot though (I HAS LIST as I suspect we all do).
so that that point, aren't we just reinventing freshness semantics? to put it another way, we find the class of he object from which we're inheriting, and then instantiate that class. This seems a bit much to be able to inherit from
as well as
well sure, but inheritance is an implementation relationship - superclasses don't have any private secrets kept from subclasses. Perhaps this argument works better the other way around: |
thinking this over (when I should have been doing something else) it seems the catch with the copy is that given Grace object initialisation is side-effectful, programs can (and likely will) observe the copy. Notably if extend the copy semantics straightforward to the inherit-from-method-tail-returning-object-constructor then initiation code in that objet construct will be run twice; once to actually run the object contstructor, in the context of the object described by that constructor, and then once again in the context of the copy - the actual final object. It's worse than that - I fear every parental-part-object is a copy, and that copy initialisations that part. so doesn't that go to O(N^2) in the very worst case |
This issue is way too abstract for me. The primitive objects are mirrors, exceptions, graceObject, done, numbers, strings and booleans. This list will probably grow as we find the need, but should be as small as possible. They are in a module called intrinsic, which is special, because it is not written in Grace and it's implementation is known to the compiler or interpreter. When user case needs to access these things, it can import intrinsic. In particular, the standardGrace dialect will import intrinsic, and re-export things like I agree that there is some engineering necessary to make this work, without intrinsic incorporating the whole world. Is that what this issue is about? If so, I think that this can best be explored in the context of a particular implementation. I don't see that it needs to be part of the language definition. The content of intrinsic should be in the language definition. |
The spec defines things like Number, String, etc but doesn't say how they come from or how you can extend their code, etc. In particular, the spec leaves open the question as to whether you can inherit from primitive types.
What are some design options:
I like the NS model, but talking to @apblack, realised it is asymmetric - it moves the magic behaviour out of the NS objects, but leaves the magic 'data' there. This certainly suits e.g. tagged VMs where integers or floats are represented by some subset of bitpatterns that are invalid as pointers.
The cleanest model I can think of is to go the 'full William Cook'. The VM provides an (interlinked) set of ADTs - not objects. Individual instances of these ADTs offer no Grace methods, not even equality. To Grace code, they are opaque 'magic cookies' that can be stored in Grace object fields, passed as arguments etc. Perhaps there are branded types that can distinguish the ADTs to which they belong but nothing more. Behaviour for these ADTs is provided by a vm object (perhaps a set of singleton VM objects one per ADT) that have methods that take and return those magic cookies. Then all the Grace-level behaviour for primitives is written as vanilla Grace code that manipulates the magic cookies. There's no question e.g. about inheriting from magic cookies; you just can't. In this design, the integer class would be 100% Grace and would look like this:
This gives a clear interface to the VM ADTs, a cleaner data model (objects are either all primitive, or all Grace, but nothing in between), and separates out the language specification (interface of Grace Integer or Number classes) and the VM interface of the
vmIntegerADT
module object.You'd probably want to do stuff with branded types, both on GraceInteger and VmIntegers. The
cookie
def could be handled specially in most VMs (perhaps the branded type is enough; perhaps you'd need an annotation too).I fear there is a subtle problem that - because the cookie must be purely exposed to Grace code - it may offer a route to an encapsulation breach if e.g. Grace code can even just do type tests - I guess that problem can be addressed by putting the brands and types into the vmModule too - so you really can only manipulate them if somehow you can get to code in those VM modules. In a typically Grace system, only the module implementing the Grace-level number class would import that module.
In terms of e.g. adding ownership or provenance or taint to Grace numbers or other primitives, this design resolves it nicely: those features could all be done in Grace code and the interface to the underlying primitives wouldn't need to be modified. I fear you'd need brands (or ownership) at the Grace level to make everything secure though (because the core interface for non-receiver method arguments is just
type { cookie -> VmInteger }
which will admit a multitude of sins.Thinking for a minute about implementation though, this model need not necessarily be slower or more memory intensive than one based on primitive parts and inheritance. This is because in
non-boxed
VMs, e.g. running Grace on top of JavaScript, or Java/Truffle/Graal - there's no way to inherit from primitives: you'll have to have a implementation field in the representation of the Grace object that holds the JS or Java VM integer (or string or whatever) anyway. Given branded types, the cookie field could be implemented as precisely a primitive field. Reading or writing that field would have to box and or unbox the primitives to make the Grace opaque cookies, but everything at that level would be functional, and if you can just inline the ADT code into the Grace code, the overhead should go away.The text was updated successfully, but these errors were encountered: