Extending a method's definition (need to call a function, providing "this" context)


#1

How can I set the Dynamic_Object context (aka this) when calling a regular function (as if it were a member method of the object)?

auto o = Dynamic_Object()
o.value = 7
auto freeFunc = fun() { print("object's value is ${this.value}") }

Is there a way I can call freeFunc with this set to object o?
Of course, it works properly when set (and used) as a member method:

o.member = freeFunc   // or: o["member"] = freeFunc
o.member
// -> object's value is 7

What I want to do is extend the current definition of a method – reference the old behaviour (method) and add something new.

Here’s a simplified example, showing what I tried (and doesn’t work):

auto o = Dynamic_Object()
o.value = 7
o.update = fun(x) {
    print("original update(${x})");
    print("...with value ${this.value}")
}

// So far everything works fine:
o.update(1)
// -> original update(1)
// -> ...with value 7

// Now I want to extend the update() method, doing the previous behaviour
// and adding some extra behaviour...

// Make a copy of the old function... (need to use this notation so
// it doesn't actually call the method here!)
auto oldUpdate = o["update"]
// ...and overwrite the definition of the update method, calling the old one:
o["update"] = fun[oldUpdate](x) { print("redefined version:"); oldUpdate(x) }

// But now, I get an error (because of "this") when the new update calls the old:
o.update(2)
// -> redefined version:
// -> original update(2)
// -> *** Error: "'value' is not a function."

// So the issue seems to be: how can I call oldUpdate, giving it a
// proper context (i.e. with "this" properly set to o)?
oldUpdate(3)
// -> original update(3)
// -> *** Error: "'value' is not a function."

oldUpdate(o, 3)
// -> *** Error: "Function dispatch arity mismatch with function 'oldUpdate'"

// And this obviously can't work, because "oldUpdate" is not a member of o:
o.oldUpdate(3)
// -> *** Error: "Error with function dispatch for function 'oldUpdate'"
// ->     With parameters: (const int).()

Thanks for any ideas on how to proceed.


#2

The ability to be able to call a function object that is assigned as a parameter to a Dynamic_Object is already pretty fragile, in my opinion.

Implemented via foisting “this” up into the context of the attribute call that appears to be a function call, see here: https://github.com/ChaiScript/ChaiScript/blob/develop/include/chaiscript/dispatchkit/dispatchkit.hpp#L924-L963

So, I don’t know if there’s any (reasonable) way to carry that a level deeper.

However, an option for you might be to do:

o.oldUpdate = o["update"];

which then allows the calling context to work as expected if you later do:

o["update"] = fun(x) { print("redefined version:"); this.oldUpdate(x) }

#3

Yeah, that’s the best solution I came up with, too – rename the old member method and have the new definition call the old. It works fine. It’s not conducive to repeated “extension” – you’d have to keep adding new method names in the object, or use a stack of definitions or something – but in practical terms, normally you don’t need to redefine methods multiple times, so this should be fine.

For live coding, this approach should work well – when “extending” a method, you move the old version somewhere, define a new one with the original name (that calls the old one, if desired). Also, you can easily restore the original version if/when desired.

You can’t redefine “top-level” (free) functions in ChaiScript, but the fact that you can manipulate Dynamic_Objects in this way means it’s possible to do what I want, as long as everything is referenced to some top-level Dynamic_Object instance which can have its methods redefined in this way.

Thanks,
G.