Re: there is a subtle problem doing: mySlot := obj getSlot(slotname)
Jeremy Tregunna <[email protected]>
| Newsgroups | gmane.comp.lang.io |
|---|---|
| Message-ID | <[email protected]> |
On 2011-11-08, at 8:35 AM, Mildred Ki'Lya wrote: > > > On 8 November 2011 15:11, Jeremy Tregunna <[email protected]> wrote: > >> This is a common gotcha, and a source of many pains if you are writing library code. You can never be sure if a user is passing in an activatable object, so you have to account for that. >> >> Is it so much a problem in library code, can't we assume that we don't accept method objects? > > > Well, you can assume that, but then you litter your code with checks which still require a getSlot to verify the argument isn't a Block type. I.e., you think just restricting the objects users can pass around and making your implementation more complicated in doing so, requiring more time even to execute. > > I don't really see how a simpler assumption can still result in complicated code. Why would you need to do a check with getSlot()? Alright, so you've got a method foo which takes an argument: foo := method(a, /* do something */ ) If you want to restrict that non-block items (no blocks or methods), then you have to add a check: foo := method(a, obj := getSlot("a") if(getSlot("obj") isKindOf(Block), Exception raise("Uhh, no blocks, kthx.")) /* do something */ ) The reason the second getSlot() is required is because you don't know if the object stored in 'obj' is activatable or not. > Can't we use a proxy object that don't have the activation bit to use methods just like any normal object. > Now that I think about it, can't we use a block as a proxy object? Sure, so your suggestion is similar to mine though not as obvious: Increase the load on the allocator and the garbage collector. This results in a substantial overhead on the runtime in terms of time and space. It'll make Io slower. I think a better solution might be some kind of read barrier, which checks if the object being referenced was set in the locals via a formal parameter at least (this way is easy to do). If this holds true, we copy the object, and disable the activatable bit. The beauty with this method is it's 100% compatible and is entirely transparent with the existing system. Users of code don't need to change anything on their end, superfluous getSlot()s aren't going to affect you at all, and if you forget to use them, it still won't matter (unless you depend on the object activating). Just a one-off I hadn't thought of before. That said, that read barrier is likely to be expensive itself. I don't want to make Io slower! > In Io, behaviour is a special kind of state, and that's a core requirement. As such, methods/blocks are valid values. If you use the method() convenience constructor, the activatable bit is set by default. If you use the block() constructor, it's not set by default. That is to say: > > foo := method(1) > bar := block(1) > > list(foo, bar) > > Will return a list whose first value is 1, but whose second value is a Block object. You must explicitly pass call() to the Block object in order to invoke it. Generally, if you are accepting a function, you should pass around blocks. That said, nothing negates users from passing methods. > > Now, > > foo := block(method(1)) > bar := block(1) > list(foo, bar) > > you get a list with two blocks, the first wrapping the method. So you're using blocks as thunks here; I'd recommend using "inlineMethod" instead. It's an activatable message, twice as fast as a block since we don't have to create a locals object on each invocation. The contents of the inlineMethod are evaluated in the context you pass it, and no way around that. It's like the method were... inlined into that context. Not used nearly enough in my opinion. :) > Functionally, it's similar to: > > foo := method(1) > bar := block(1) > list(getSlot("foo") setIsActivable(false), bar) > > > But then, we can imagine a thinner layer around the method: > > protectActivableObject := method(obj, > # probably won't work > if(getSlot("obj") isActivable) then( > proxy := Object clone > proxy do( > targetObject := getSlot("obj") > forward := method( > targetObject doMessage(call message) > ) > ) > proxy setIsActivatable(false) > return(proxy) > ) else( > return(obj) > ) > ) > > And we can imagine that when called, a method wraps all arguments this way > > Or, if when we send a message, we know an object is activable, then we can wrap it manually at the call site directly. That is a huge overhead on the system, wrapping all arguments in blocks. Block activation isn't exactly expensive, but when you start doing it all over the place, it adds up quickly. Using an inlineMethod would be marginally better, in terms of speed and pressure on the heap, but still would add up to significant overhead with the extra messages being used. There's a simple solution which is cheap staring us in the face, I know it. Just haven't found it yet. Regards, Jeremy Tregunna