简体   繁体   中英

How do languages whose variables don't have sigils deal with dynamic dispatch/invocation?

Dynamic languages allow dispatching with and invoking on values from variables whose values are only known at run-time. Contrasting examples in Perl:

  1. class names

    • constant

       Foo::Bar->some_method Foo::Bar::->some_method 'Foo::Bar'->some_method 

    These are all identical, except the first one is an edge case. If there's a subroutine defined in scope with that name, the dispatch happens on its return value, which leads to difficult to understand bugs. The quoted versions are always safe.

    • dynamic

       my $class_name = 'Foo::Bar'; $class_name->some_method 
  2. method names

    • constant

       Some::Class->foo_bar 
    • dynamic

       my $method_name = 'foo_bar'; Some::Class->$method_name 
  3. function names

    • constant

       foo_bar; (\\&foo_bar)->() 
    • dynamic

       my $function_name = 'foo_bar'; (\\&$function_name)->() 

I wonder, how do languages whose variable names have no sigils (normally, or at all) deal with these problems, specifically how did their language designers disambiguate the following?

  1. resolving class name FooBar.some_method where class FooBar might be name literal or a variable whose value is a class name
  2. dispatching to SomeClass.foo_bar where method foo_bar might be name literal or a variable whose value is a method name
  3. invoking foo_bar where the function might be a name literal or a variable whose value is a function

I'm primarily interested in the three languages mentioned in this question's tags, but if you know a different dynamic language with no sigils, you can answer too.

In python and js (my ruby is a bit rusty) it's not possible to use a string in the name context. If you attempt to do so, that would be interpreted as an operation on the string itself:

class_name = 'Foo'
object = class_name()

> TypeError: 'str' object is not callable

The string should first be resolved by looking up in the context/scope dictionary:

class Foo:
   ....

object = globals()['Foo']()

or

function Foo() ....

object = new window['Foo'];

Python provides both global and local dicts, js only the global one, so there's no way, apart from ubiquitous "eval", to obtain a local value from its name.

The same applies to methods: you look up a method using getattr (python) or the indirect reference operator [...] (js):

method_name = 'foo'
method = getattr(some_object, method_name)
method()

Javascript code is slightly more complicated, because, unlike python, the returned method pointer is unbound:

method_name = 'foo'
method = some_object[method_name].bind(some_object)
method()

Without bind you won't get the correct this in a method:

 var obj = { xyz: 42, method: function() { document.write(this.xyz) } } var method_name = 'method'; var unbound_method = obj[method_name]; unbound_method() // prints undefined var bound_method = obj[method_name].bind(obj); bound_method() // prints 42 

To put it more formally, the dot operator . (equivalent to php's :: and -> ) in python/js requires an identifier on the right, and allows for arbitrary expressions on the left. Since everything is an object, if the left-hand expression returns a string, the dot applies to that string, not to a variable whose name this string happens to be equal to.

(As a side note, it's technically feasible to allow expressions on the right of the dot as well, so that foo.('bar' + 'baz') would resolve to foo.barbaz . I'm not aware of languages that support this syntax, but it looks like a nice alternative to getattr and similar methods).

Similarly, the call operator () allows for complex expressions on the left, additionally this expression must resolve to a "callable" object. Therefore, "someString"() doesn't make sense, since strings are normally not callable (unless you hack them somehow so that they are).

That is, if you have a string the contains a variable name, you have to explicitly resolve it before use. There's no magic in the language that does that for you behind the scenes.

In Ruby, you can always use const_get and send :

class FooBar
    def some_method
        return 42
    end
end
class_name = 'FooBar'
puts Module.const_get(class_name).new.some_method

class SomeClass
    def foo_bar
        return 23
    end
end
method_name = 'foo_bar'
puts SomeClass.new.send(method_name)

def foo_bar
    return 123
end
function_name = 'foo_bar'
puts send(function_name)

In js, you need a global to do a name lookup:

xyz = 100; // notice: no "var" here – it's a global
var varname = 'xyz';
window[varname]; // 100

... Or you can always use eval , but it's most probably going to bite you:

var x = eval;
x("var y = 10"); // INDIRECT call to eval
window.y; // 10 (it worked)
eval("var y = 11"); // direct call
window.y; // still 10, direct call in strict mode gets a new context

When trying to get a value from an object, you need to know that JS doesn't respect eta conversion . Let's set up the context to explain.

var object = {
  x: 10,
  say: function () {
    console.log(this.x);
  }
}

var method = 'say';
// that version (the "eta abstraction" version):
object.say(); // "this" inside of say is correctly "object"
object[method](); // equivalent

// ... isn't equivalent to this (the "eta reduction" version):
var sayit = object[method];
sayit(); // "this" inside of say is incorrectly "window" (and thus the property x won't exist)

// You can use Function#bind to work around that, but it's only available in ES5-compatible browsers (and currently pretty slow on v8/chrome)
var sayit = object[method].bind(object);
sayit(); // "this" inside of say has been forced to "object", and will print 10 correctly

// You can also, of course, close over it:
var sayit = function () { object[method]() };
sayit(); // prints 10

Python does not allow you to treat objects the same as strings containing the names of variables referring to those objects. If obj is a variable whose value is an object, you can do FooBar.some_method() . If you have a string "FooBar" , you have to do something else entirely. Exactly what you do depends on where you expect to find that variable "FooBar" (ie, is it a global variable, a local variable, an attribute name, or what). You must look up the name in whatever namespace you think it should be in, and then perform your operation on the resulting object. For instance, if you want to interpret "FooBar" as a global variable, you can do globals()["FooBar"].some_method() .

The situation is the same for functions, since functions in Python are just objects like any other. If you have a string "foo_bar" that you think refers to a function named foo_bar in the global namespace, you can do globals()["foo_bar"]() to try to call it.

For methods, the situation is basically the same, except that for methods you always know what namespace you're trying to look up the method name in: it's the namespace of the object you're trying to call the method on. For this you use the getattr function. If you have a string "method_name" and want to call the method of that name on FooBar , you do getattr(FooBar, "method_name")() .

The getattr approach can also be used to look up global names in another module's namespace. If you think function_name refers to a function in another module's global namespace, you can do getattr(other_module, function_name) .

Here are some examples:

def some_function():
    print "I am a function!"

class SomeClass(object):
    def some_method(self):
        print "I am a method!"

function_name = "some_function"
class_name = "SomeClass"
method_name = "some_method"

some_function() # call the function
globals()[function_name]() # call the function
getattr(some_module, function_name)() # if the function was in another module

SomeClass() # make an instance of the class
globals()[class_name]() # make an instance of the class
getattr(some_module, class_name)() # if the class was in another module

instance = SomeClass()
instance.some_method() # call the method
getattr(instance, method_name)() # call the method

In short, there is no ambiguity, because Python does not allow you to use the same syntax to try to do things with objects and with strings referring to objects. Trying to do something like "obj".method() directly is unambiguous in Python: "obj" is a string, so it can only mean you are trying to call the method on the string itself. There is no attempt to implicitly "decode" the string to see if it happens to contain a variable name. Also, there is no conceptual difference between the operations for looking up a class, function, or method, because those are all first-class objects in Python. The procedure is always the same: first, get the namespace where you want to look up the name; then, look it up. Both steps have to be explicit.

It's also worth noting that using this sort of string-based lookup in globals() is usually considered hackish in Python. Use of getattr and the like is considered okay only when you're dealing with a highly dynamic data structure (eg, reading user data from some kind of self-documenting file that tells you what its own fields are called) or some kind of metaprogramming (eg, a plugin framework). Using something like globals()["some_class"]() for such simple cases would be considered very unpythonic.

In a nutshell: the designers of these other languages (and all the others that I know of, for that purpose) didn't create this ambiguity, that a string can be interpreted as an entity name, in the first place. Such a conversion is always explicit.

So, there's nothing to disambiguate from.

Java and other static languages offer tools for reflection through the use of runtime type information. You need to reintroduce the type information.

class SomeClass {
     int foo(int x) {
         return x * 2;
     }

     public static void reflectionExample() 
     throws ReflectiveOperationException {
         String someMethodName = "foo";

         SomeClass obj = new SomeClass();

         // You must select the method by both method name and the signature.
         // This is because Java supports overloading.
         Method method = SomeClass.class.getMethod(
             someMethodName, Integer.TYPE
         );

         // You must cast the object back to the right return type.
         // Java will automatically 'box' and 'unbox' the values.
         int result = (Integer) method.invoke(obj, 3);

         assert result == 6;
     }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM