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

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”).

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

It depends on what you mean by “the same thing”. :smiley:

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. :wink:

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

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.

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.