2

动态语言允许使用仅在运行时才知道的变量的值进行调度和调用。Perl 中的对比示例:

  1. 类名

    • 持续的

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

    这些都是相同的,除了第一个是边缘情况。如果在作用域中定义了具有该名称的子例程,则分派发生在其返回值上,这会导致难以理解的错误。引用的版本总是安全的。

    • 动态的

      my $class_name = 'Foo::Bar';
      $class_name->some_method
      
  2. 方法名称

    • 持续的

      Some::Class->foo_bar
      
    • 动态的

      my $method_name = 'foo_bar';
      Some::Class->$method_name
      
  3. 函数名

    • 持续的

      foo_bar;
      (\&foo_bar)->()
      
    • 动态的

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

我想知道,变量名没有符号(通常或根本没有)的语言如何处理这些问题,特别是他们的语言设计者如何消除以下歧义?

  1. 解析类名FooBar.some_method,其中类FooBar可能是名称文字或值为类名的变量
  2. 调度到SomeClass.foo_barwhere 方法foo_bar可能是名称文字或值为方法名称的变量
  3. 调用foo_bar函数可能是名称文字或值为函数的变量

我主要对这个问题的标签中提到的三种语言感兴趣,但如果你知道另一种没有符号的动态语言,你也可以回答。

4

5 回答 5

5

在 python 和 js 中(我的 ruby​​ 有点生疏),不可能在名称上下文中使用字符串。如果您尝试这样做,那将被解释为对字符串本身的操作:

class_name = 'Foo'
object = class_name()

> TypeError: 'str' object is not callable

该字符串应首先通过在上下文/范围字典中查找来解析:

class Foo:
   ....

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

或者

function Foo() ....

object = new window['Foo'];

Python 提供了全局和局部字典,js 只提供全局字典,所以除了无处不在的“eval”之外,没有办法从其名称中获取局部值。

getattr这同样适用于方法:您使用(python) 或间接引用运算符[...](js)查找方法:

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

Javascript 代码稍微复杂一些,因为与 python 不同,返回的方法指针是未绑定的:

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

没有bind您将无法获得正确this的方法:

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

更正式地说,python/js 中的点运算符.(相当于 php 的::and ->)在右侧需要一个标识符,并允许在左侧使用任意表达式。由于一切都是一个对象,如果左边的表达式返回一个字符串,那么点适用于该字符串,而不适用于名称恰好等于该字符串的变量。

(作为旁注,在点右侧允许表达式在技术上也是可行的,因此foo.('bar' + 'baz')可以解析为foo.barbaz。我不知道支持这种语法的语言,但它看起来是一个不错的替代getattr方法和类似方法)。

同样,调用运算符()允许左侧的复杂表达式,此外,此表达式必须解析为“可调用”对象。因此,"someString"()没有意义,因为字符串通常是不可调用的(除非你以某种方式破解它们以便它们是)。

也就是说,如果您有一个包含变量名的字符串,则必须在使用前显式解析它。在幕后为你做这件事的语言没有魔法。

于 2014-10-16T10:49:52.987 回答
4

Python 不允许您将对象视为与包含引用这些对象的变量名称的字符串相同。如果obj是一个变量,它的值是一个对象,你可以做FooBar.some_method(). 如果你有一个 string "FooBar",你必须完全做其他事情。您所做的具体操作取决于您希望在何处找到该变量"FooBar"(即,它是全局变量、局部变量、属性名称还是什么)。您必须在您认为它应该位于的任何名称空间中查找名称,然后对生成的对象执行操作。例如,如果你想解释"FooBar"为一个全局变量,你可以做globals()["FooBar"].some_method().

函数的情况也是如此,因为 Python 中的函数和其他函数一样只是对象。如果你有一个字符串"foo_bar",你认为它引用了一个foo_bar在全局命名空间中命名的函数,你可以globals()["foo_bar"]()尝试调用它。

对于方法,情况基本相同,只是对于方法,您始终知道要在哪个名称空间中查找方法名称:它是您尝试调用该方法的对象的名称空间。为此,您使用该getattr功能。如果你有一个字符串"method_name"并且想调用那个名字的方法FooBar,你可以这样做getattr(FooBar, "method_name")()

getattr方法还可用于在另一个模块的命名空间中查找全局名称。如果你认为function_name引用另一个模块的全局命名空间中的函数,你可以这样做getattr(other_module, function_name)

这里有些例子:

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

简而言之,没有歧义,因为 Python 不允许您使用相同的语法来尝试处理对象和引用对象的字符串。尝试直接做类似的事情"obj".method()在 Python 中是明确的:"obj"是一个字符串,所以它只能意味着你试图在字符串本身上调用方法。没有尝试隐式“解码”字符串以查看它是否恰好包含变量名。此外,查找类、函数或方法的操作在概念上没有区别,因为它们都是 Python 中的一等对象。过程始终相同:首先,获取要查找名称的名称空间;然后,查一下。这两个步骤都必须是明确的。

还值得注意的是,globals()在 Python 中使用这种基于字符串的查找通常被认为是 hackish。getattr仅当您处理高度动态的数据结构(例如,从某种自我记录的文件中读取用户数据,告诉您其自己的字段被称为什么)或某种元编程(例如,插件框架)。在这种简单的情况下使用类似的东西globals()["some_class"]()会被认为是非常不合时宜的。

于 2014-10-22T19:45:44.980 回答
4

在 Ruby 中,您始终可以使用const_getand 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)

在 js 中,您需要一个全局变量来进行名称查找:

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

...或者你可以一直使用eval,但它很可能会咬你:

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

在尝试从对象中获取值时,您需要知道 JS 不尊重eta 转换。让我们设置上下文来解释。

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
于 2014-10-16T11:53:33.067 回答
1

简而言之:这些其他语言的设计者(以及我所知道的所有其他语言)并没有造成这种歧义,即字符串可以被解释为实体名称。这种转换总是显式的。

所以,没有什么可以消除歧义的。

于 2014-10-27T16:02:01.067 回答
0

Java 和其他静态语言通过使用运行时类型信息提供反射工具。您需要重新引入类型信息。

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;
     }
}
于 2014-10-23T15:21:32.373 回答