Deciding if helper should *contain* or extend another helper

Here is some pseudocode:
// egghelper.script
to initialize
	add properties of this object to me
end initialize

properties
	remoteHelper:(ServerID of ConnectionInfo()) & ":" & 9973,
end properties

to handle foo
	put (my remoteHelper)
end foo
// utility.script
properties
	helpers:(egghelper),
end properties

to handle bar
	foo()
end bar

A brief history: At one time we had a helper called "utility". Now we have a new and improved helper called "egghelper" that has completely refactored what "utility" did, plus adds a lot more. Rather than go through all of the old code and replace "utility.bar" with "egghelper.foo", I thought it would be OK to refactor "utility" so that it transparently implements the new "egghelper" implementation.

We used to be able to make the following call:
run utility.bar

After the above refactoring, when utility.bar calls egghelper.foo, egghelper's "my remoteHelper" is empty.

How should I implement utility so that it is helped by egghelper and can properly call all of egghelper's handlers?

As the subject indicates, should utility just directly use the egghelper helper, or should utility have a helper property that *extends* egghelper?

ie:
this...
// utility.script
properties
	helpers:(egghelper),
end properties

to handle bar
	foo()
end bar

...or this...
// utility.script
to handle bar
	run egghelper.foo
end bar

The latter looks undesirable and is not very OO.
However, I can't seem to get the former to work correctly.

Thanks!

Pv

Comments

  • SenseTalkDougSenseTalkDoug ForumAdmin admin
    You've raised some interesting questions!

    Let me start by describing how SenseTalk treats these scripts from the point of view of using them as objects with properties. When the egghelper object is first referred to or used in any way, SenseTalk loads the egghelper.script file, and the egghelper object comes into existence. At this time (and only at this time) the properties declared in the script (in "properties ... end properties" declarations) are evaluated and become the initial properties of that object. The initialize handler is not called at this time (I'll explain later).

    Similarly, when utility is first used or referred to, the utility.script is read by SenseTalk, and utility's initial property values are set. In this case, the utility object gets its helpers property set, so egghelper is now a helper of utility (if egghelper had not been used previously, it would get loaded at this point, as described above).

    Now, let's try running something and see what happens. For those of you "following along at home", you may want to try this by entering the egghelper and utility scripts in a new suite, along with a third script to call them like this:
    run utility.bar
    
    Start this script in the debugger by holding down the Option key when you run it. Then you can step through the action a line at a time to get a feel for what's happening here.

    When you step into the "run utility.bar" command you should find yourself at the beginning of utility's bar handler. To arrive at this point, SenseTalk had to load the utility object, which in turn loaded the egghelper object. No handlers have been run yet in either object, and both objects have only the initial property values that were declared in their respective script files.

    If you step into the call to "foo()" you'll find that Eggplant is now executing egghelper's foo handler. The "foo" message was sent to the utility object, but since it doesn't have a foo handler, its helper (egghelper) was called on to help out and its foo handler was run on behalf of utility.

    We've now arrived at the critical point for understanding SenseTalk objects and helpers. The one command in this handler reads "put (my remoteHelper)". If you execute this command, you'll see that it displays... nothing!

    Now egghelper has a remoteHelper property which was evaluated and assigned a value as soon as the egghelper object's script file was read. But egghelper's foo handler is being run on behalf of the utility object, in egghelper's role as utility's helper. In this situation, the words "my" and "me" refer to the object being helped, so "my remoteHelper" refers to the remoteHelper property of the utility object. Since it doesn't have that property, its value is empty.

    SenseTalk provides a way to access properties of the helper object itself, by using "this object" instead of "me". So (if you're still in the foo handler in the debugger) you can type "put this object's remoteHelper" in the Do box to see egghelper's remoteHelper.

    I didn't directly answer your question, but hopefully this explanation has helped you to figure out what you want to do in your situation. The key things to remember are:
    1. Each SenseTalk object has its own properties.
    2. Helpers provide an object with additional behavior (handlers) but not properties.
    3. When a handler is being used as a helper, "me" and "my" refer to the object that is being helped. Use "this object" if you need to refer to the helper object itself.

    Oh, I also said I would explain why egghelper's initialize handler wasn't called. An initialize message is sent when a "new" expression is used to create an object. For example:
    put a new egghelper into newegg
    
    If you run this command, a new object is created with egghelper as its helper. An initialize message is then sent automatically to the new object (newegg). Since newegg doesn't have an initialize handler (it doesn't have any handlers of its own in this example), the initialize message is handled by its helper, egghelper.

    Hopefully you can see now exactly what this line in the initialize handler will do:
    add properties of this object to me
    
    Since egghandler's initialize handler is being called as a helper of newegg, this command adds all of the properties of egghandler ("this object") to newegg ("me").
  • pvpv Member
    Thank you for your quick reply.

    If I do *not* use the "new" operator, will "initialize" still get called?

    If I just said:
    set newegg to a new egghelper
    newegg.bar()
    

    Is that the same thing as just saying:
    egghelper.bar()
    

    I imagine it is, I just wanted to verify there is no subtle difference.

    Thanks!

    Pv
  • SenseTalkDougSenseTalkDoug ForumAdmin admin
    It depends on what you mean by "the same thing". :D

    There's a fairly significant difference here: in the first case you're creating an entirely new object and then calling its bar() and in the second case there's only one object.

    On the other hand, since the newegg object gets a copy of all of egghelpers properties when it's created (thanks to the initialize handler in egghelper), and uses egghelper's "bar" handler, then you're right that the result of that particular call will be the same as calling egghelper.bar(). If later you change the value of egghelper's remoteHelper property, though, that won't affect newegg's remoteHelper so the two objects will then give different results when you call bar.

    > If I do *not* use the "new" operator, will "initialize" still get called?

    No, "initialize" is only called automatically by "new" (actually, it's somewhat more complex than that, but that's the simple answer -- for the full details see the section "Making Objects Based on Other Objects" in the SenseTalk Reference manual). You can of course call "initialize" yourself at any time. ;)
  • pvpv Member
    A year and a half later I am revisiting this issue again, but specifically related to "properties" this time.

    A slight spin on the original code:
    // Helper1.script
    to initialize
       add properties of this object to me
    end initialize
    
    properties
      someProp:1,
    end properties
    
    to handle foo
      put (me.someProp)
    end foo
    
    // Helper2.script
    properties
      helpers:(Helper1),
    end properties
    
    // Helper3.script
    properties
      helpers:(Helper1),
      someProp:3,
    end properties
    

    So I run the following:
    put Helper1.foo()
    put Helper2.foo()
    put Helper3.foo()
    

    Per your reply, I would get the following output:
    1
    empty
    3
    

    Again, per your reply if I change foo to use "(this object).someProp" I would get the following output:
    1
    1
    1
    

    The problem is that I want:
    1
    1
    3
    

    Using "this object" seems to *always* uses the base object's property. I want to allow the sub-class the option of overriding that value, but I don't want to require it. Is this getting too close to true OOP, and is this not supported in SenseTalk? Or, is this where a more fancy makeNewObject function would be used?

    Thanks!

    Pv
  • SenseTalkDougSenseTalkDoug ForumAdmin admin
    One option that could work would be to add a someProp property to Helper2. So then Helper2 might look like this:
    properties
    	helpers:Helper1,
    	someProp: Helper1.someProp
    end properties
    
    This will give Helper2 a someProp property with an initial value that is a copy of Helper1's someProp. This may not be quite what you want, though. For one thing, the value of Helper1's someProp might change, and you'd like Helper2 to inherit the new value rather than being stuck with a static copy of the original value.

    For more dynamic inheritance of a property value, in a way that also works without needing to modify Helper2 or any of the other objects that Helper1 suports, I suggest something like this in Helper1:
    to handle foo 
    	if there is a property someProp of me then
    		return me.someProp
    	else
    		return this object.someProp
    	end if
    end foo
    
    If the only purpose of the foo handler is to return the value of a particular property, you may want to write this as a getProp handler instead:
    getProp someProp
    	if there is a property someProp of me then return me.someProp
    	return this object.someProp
    end getProp
    
    This gives you the advantage of being able to ask for the property directly by name rather than calling the function name:
    put Helper1.someProp
    put Helper2.someProp
    put Helper3.someProp
    
    Note that in both cases I used the "there is a property" operator to check for the existence of the property in the object that's being helped. This is a better choice than checking whether the property is empty, since empty might be a valid value for some properties. This way an object can have an empty value for someProp that will override the base value, or it can have no value at all (not have that property) and inherit the base value.
  • SenseTalkDougSenseTalkDoug ForumAdmin admin
    On a slightly different topic, the above discussion doesn't touch on the initialize handler in Helper1, because in the examples given it is never called. If a "new" expression such as "put a new Helper1 into var" is used, the initialize handler would be called.

    I mention it because of some behavioral changes in SenseTalk that you should be aware of. The first change, beginning with Eggplant 4.0, is that the "new" operator now copies properties of the prototype object into the newly created object. So a standard initialize handler like you have here is no longer needed. Unless other initialization is required, it is suggested that you delete such initialize handlers and let the new standard behavior take care of copying the properties for you.

    Beginning in Eggplant 4.1 removing the initialize handler is even more desirable, because the script of an object, which previously was a "hidden" property, will now be visible, so the code in your initialize handler will actually assign its script to the new object along with its other properties. The final result will probably be the same in most cases, but it is unnecessary and somewhat cumbersome for each object to have a copy of the script -- the point of having helpers is that objects can inherit behavior without needing their own script.

    You can get the old behavior by changing the command in the initialize handler to:
    add properties of (this object removing "script") to me
    
    But it's simpler just to remove that handler completely.
Sign In or Register to comment.