簡體   English   中英

變量沒有符號的語言如何處理動態調度/調用?

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

動態語言允許從值僅在運行時獲知的變量調度和調用值。 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可能是name literal或其值為類名的變量
  2. 調度到SomeClass.foo_bar ,其中方法foo_bar可能是name literal或其值為方法名稱的變量
  3. 調用foo_bar ,其中函數可能是名稱文字或值為函數的變量

我主要對這個問題標簽中提到的三種語言感興趣,但是如果你知道一種不同的動態語言,你也可以回答。

在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提供全局和本地dicts,js只提供全局dicts,因此除了無處不在的“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 

更正式地說,點運算符. (相當於php的::-> )在python / js中需要右側的標識符,並允許左側的任意表達式。 由於一切都是對象,如果左側表達式返回一個字符串,則該點應用於該字符串,而不是該字符串恰好等於的變量。

(作為旁注,在點的右側允許表達式在技術上是可行的,因此foo.('bar' + 'baz')將解析為foo.barbaz 。我不知道支持的語言這個語法,但它看起來像getattr和類似方法的一個很好的替代品)。

類似地,調用operator ()允許左側的復雜表達式,此外該表達式必須解析為“可調用”對象。 因此, "someString"()沒有意義,因為字符串通常不可調用(除非你以某種方式破解它們以便它們)。

也就是說,如果您有一個包含變量名稱的字符串,則必須在使用前明確解析它。 在幕后為你做的那種語言沒有神奇之處。

在Ruby中,您始終可以使用const_getsend

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

Python不允許您將對象與包含引用這些對象的變量名稱的字符串相同。 如果obj是一個值為對象的變量,則可以執行FooBar.some_method() 如果你有一個字符串"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"]()的東西將被認為是非常不合理的。

簡而言之:這些其他語言的設計者(以及我所知道的所有其他語言)都沒有產生這種模糊性,首先可以將字符串解釋為實體名稱。 這種轉換總是明確的。

所以,沒有什么可以消除歧義。

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;
     }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM