RapydScript: __call__ special method not working

Context

class tester:
  def __init__(self):
    print("init")

  def go(self):
    print("go")

# specal method:
  def __call__(self, arg):
    print("call:", arg)

test = tester()
test.go()
test.__call__(1)
test(2)                   # <<<< invoke the special method; use object as functor

Expected and Desired Behavior (ordinary python)

init
go
call: 1
call: 2

This works just fine in python2.7, python3.4, ipython2.7, and ipthon3.

Observed Behavior (RS)

init
go
call: 1
[stdin]:152
test(2);
^
TypeError: object is not a function
    at [stdin]:152:1
    at Object.exports.runInThisContext (vm.js:74:17)
    at Object.<anonymous> ([stdin]-wrapper:6:22)
    at Module._compile (module.js:460:26)
    at evalScript (node.js:431:25)
    at Socket.<anonymous> (node.js:164:11)
    at Socket.emit (events.js:129:20)
    at _stream_readable.js:908:16
    at process._tickCallback (node.js:355:11)

Workaround

Just invoke test.__call__(...) instead of test(...)

Discussion

There are ways of making functors in javascript, using closures … but I reckon it is both easier (at compile time) and more efficient (at run time) if the compiler just catches the pythonic functor expression and maps it to the __call__ special method.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 20 (11 by maintainers)

Most upvoted comments

To summarize, the problems with using function objects to implement __call__ are:

  1. Changing object prototype dynamically is not available in all JS runtimes. This could potentially be worked around by manually copying properties, but IMO, that is just a slow, ugly mess.

  2. The list of properties and type of the object will be changed by simply adding a __call__ method to the object. This is highly unexpected. See code below:

o = Object.create(null)
f = (function () {})
typeof o == 'object'
typeof f == 'function'
typeof o.apply == 'undefined'
typeof f.apply == 'function'
  1. No evidence beyond vague hand-waving was presented to counter the claim on MDN that dynamically changing the prototype will slow down attribute access on the resulting object

  2. I am willing to ignore this – __call__ simply wont work for runtimes without this facility.

  3. This is a problem. In particular the type() of the resulting object changing is highly unexpected. Any code based on type inspection that expects an object will break.

  4. This is a problem, unless someone can present some actual evidence that it is not.

IMO the call operator () suffers from the same problem as all other operators in RS – namely that it cannot be overloaded. Any solution for this problem should be a general solution for the operator overloading problem as a whole. I am still inclined towards simply enabling all operator overloading on a per-module basis. This allows the user to choose which is more important to him – performance or python compatibility, on a fairly granular level.

The issue with that is the different behavior of indexOf() when used with non-primitive types. I am inclining towards ignoring that as the cost of doing business. The RS provided index() method can be made to work (it will check if the passed in argument has an __eq__ method, and if it does it will loop through the array checking with __eq__ and otherwise fallback to indexOf). This should cover 99% of the use cases. It is still not fully compatible with python since in theory, a custom class could has an __eq__ method that returns true for primitive types, but that should be pretty rare.

There is a similar issue with the python-like dict and set types. Currently in my fork I have an implementation for them that use the JS Map and Set types where available. The problem with that is that they do not use __hash__ and __eq__. This can be worked around by implementing a hashmap in pure JS that will use those methods when defined, otherwise falling back to toString().