Lua: Когда можно использовать синтаксис с двоеточием?

Хотя я понимаю основную разницу между . и :, я не совсем понял, когда Lua позволяет использовать синтаксис двоеточия . Например, что-то вроде этого работает:

s = "test"
-- type(s) is string.
-- so I can write a colon function for that type
function string:myFunc() 
  return #self 
end

-- and colon function calls are possible
s:myFunc()

Однако тот же шаблон, похоже, не работает для других типов. Например, когда у меня есть table вместо string:

t = {}
-- type(t) is table.
-- so I can write a colon function for that type
function table:myFunc() 
  return #self 
end

-- Surprisingly, a colon function call is not not possible!
t:myFunc() -- error: attempt to call method 'myFunc' (a nil value)
-- But the verbose dot call works
table.myFunc(t)

Переходим к другому типу:

x = 1
-- type(x) is number.
-- So I was expecting that I can write a colon function 
-- for that type as well. However, in this case even this
-- fails: 
function number:myFunc() 
  return self 
end
-- error: attempt to index global 'number' (a nil value)

В настоящее время я пытаюсь понять это. Правильно ли сделать вывод, что

  • некоторые типы, такие как string, допускают как определение-функции-двоеточия , так и вызовы-функции-двоеточия.
  • другие типы, такие как table, допускают только определение функции-двоеточия, но не вызовы функции-двоеточия.
  • однако другие типы, такие как number, не допускают ни того, ни другого.

В чем именно причина этих различий? Есть ли список всех типов, показывающий, какой тип синтаксиса двоеточия они поддерживают? Возможно, есть обходной путь для случая number, позволяющий писать, например. x:abs()?


lua
person bluenote10    schedule 10.10.2015    source источник


Ответы (3)


Первый пример для string работает, потому что все строки имеют одну и ту же метатаблицу, и она хранится в таблице с именем string.

Из string:

Библиотека строк предоставляет все свои функции внутри таблицы string. Он также устанавливает метатаблицу для строк, где поле __index указывает на таблицу string. Следовательно, вы можете использовать строковые функции в объектно-ориентированном стиле. Например, string.byte(s,i) можно записать как s:byte(i).


Второй пример на table не работает, потому что каждая таблица имеет свою собственную метатаблицу, а таблица с именем table — это просто набор всех функций библиотеки таблиц.


Такие типы, как числа, по умолчанию не поддерживают метатаблицы.

person Yu Hao    schedule 10.10.2015
comment
Значит, string — единственный тип, который это позволяет? - person bluenote10; 10.10.2015
comment
Синтаксис двоеточия просто синтаксический сахар. Таблицы, например, поддерживают синтаксис двоеточия, однако не существует единой общей метатаблицы для всех таблиц. - person Yu Hao; 10.10.2015
comment
@ bluenote10, дескрипторы файлов - еще один пример. - person lhf; 10.10.2015

Как новичок в Lua, мне потребовалось некоторое время, чтобы понять ответ @Yu Hao, поэтому я попытаюсь добавить некоторые детали для других новичков. Пожалуйста, поправьте меня, если что-то не так.

Насколько я вижу, вызов типа x:someFunc() работает, если [*]:

  • x имеет метатаблицу
  • а в метатаблице есть поле __index
  • который указывает на таблицу, содержащую функцию someFunc.

Как указал Ю Хао, строки автоматически получают метатаблицу, указывающую на таблицу string, например:

th> s = 'test'

th> getmetatable(s)
{
  __mod : function: 0x40c3cd30
  __index : 
    {
      upper : function: builtin#82
      rep : function: builtin#79
      split : function: 0x40ffe888
      gfind : function: builtin#87
      find : function: builtin#84
      reverse : function: builtin#80
      lower : function: builtin#81
      len : function: 0x40af0b30
      tosymbol : function: 0x40ffe8a8
      myFunc : function: 0x41d82be0 -- note: this comes from our custom function string:myFunc()
      dump : function: builtin#83
      byte : function: builtin#76
      char : function: builtin#77
      gmatch : function: builtin#87
      match : function: builtin#85
      sub : function: builtin#78
      gsub : function: builtin#88
      format : function: builtin#89
    }
}

Так что в этом случае s:myFunc() работает автоматически. Чтобы использовать синтаксис двоеточия для table, мы можем вручную установить его метатаблицу:

th> function enableColonForTable(t)
..>   meta = {__index = table}
..>   setmetatable(t, meta)
..> end

th> t = {}

th> enableColonForTable(t)

th> t:insert(1) -- works now!

Другое наблюдение заключается в том, что на самом деле не имеет значения, указывает ли __index на таблицу с точно таким же именем, как у типа. Вместо meta = {__index = table} мы также могли бы сделать:

th> arbitraryScope = {}

th> function arbitraryScope:test() return "something" end

th> t = {}

th> setmetatable(t, {__index = arbitraryScope})
{}

th> t:test()
something   

Это также ключевое отличие от случая number. Хотя существуют таблицы с именами string и table, таблицы с именем number не существует. Вот почему даже определение, например. function number:abs() раньше не удавалось. Но мы все еще можем заставить его работать:

th> number = {}

th> function number:abs() return math.abs(self) end

th> x = -123

th> debug.setmetatable(x, {__index = number})
-123    

th> x:abs()
123 

Обратите внимание, что здесь нам пришлось использовать debug.setmetatable вместо setmetatable. Разница между ними заключается в том, что setmetatable устанавливает метатаблицу только для данного экземпляра, а debug.setmetatable устанавливает метатаблицу для всего типа. По-видимому, установка индивидуальной метатаблицы для чисел запрещена (и в любом случае не имеет особого смысла). Это означает, что (в отличие от таблиц) вновь созданные числа теперь имеют заданную метатаблицу по умолчанию, так что это работает:

th> y = -42

th> y:abs()
42  

[*] Обновление

Как указал Том Блоджет, x:someFunc() также работает, если x само по себе служит пространством имен, то есть это таблица с полем метода someFunc. Например, вы можете сделать table:insert(1). Но теперь пространство имен (таблица с именем table) передается как self, и вы бы добавили данные в пространство имен:

th> print(getmetatable(table)) -- note: "table" does not have a metatable
nil 

th> table:insert(1) -- yet a colon syntax call works

th> table
{
  prune : function: 0x4156bde0
  getn : function: 0x41eb0720
  maxn : function: builtin#90
  remove : function: 0x41eb08c8
  foreachi : function: 0x41eb05b8
  sort : function: builtin#93
  concat : function: builtin#92
  unpack : function: builtin#16
  splice : function: 0x4156bdc0
  foreach : function: 0x41eb0688
  1 : 1
  pack : function: builtin#94
  insert : function: builtin#91
}
person bluenote10    schedule 10.10.2015
comment
Хорошее переформулирование, но вы упускаете очевидное: поле таблицы можно вызвать как метод. См. справочную информацию в моем ответе. - person Tom Blodget; 11.10.2015

Дополнительный ответ:

Во-первых, обратите внимание, что функция — это значение (также известное как "первоклассный гражданин"). .

: — один из трех операторов индексации в Lua. Оператор индексации возвращает значение «поля» из объекта, который может быть проиндексирован, будь то функция или любой другой тип.

Операторы индексации в порядке общности:

  1. выражение [ выражение2 ]
  2. выражение . идентификатор
  3. выражение : идентификатор ( список-параметров )

Последние два являются просто «синтаксическим сахаром» и могут быть переписаны в виде любого над ним.

Вы бы использовали второй, если «выражение2» всегда будет той же строкой, которая является допустимым идентификатором Lua, и вы хотите жестко закодировать ее.

Вы должны использовать третий, если значение, возвращаемое индексированием «идентификатора» по «выражению», всегда будет функцией, которую вы хотите вызвать со значением, возвращаемым «выражением», в качестве неявного первого параметра. Такой вызов функции называется «вызовом метода».

Кроме того, обратите внимание, что язык/компилятор не заботится/знает, является ли значение поля функцией или нет (вы получите ошибку во время выполнения, если попытаетесь вызвать значение, которое не является функцией). И ему все равно/не знать, является ли функция методом или нет (функция, вероятно, не будет вести себя так, как вы предполагали, если вы не передадите ей соответствующие параметры).

Теперь тип значения выражения должен быть любым типом, который может быть проиндексирован. Обратите внимание, что выражения не имеют типов времени компиляции, поэтому, если значение выражения относится к типу, который не может быть проиндексирован, это также является ошибкой времени выполнения. Индексируемыми типами являются: таблица и любой объект с метаметодом __index. Другие ответы содержат подробную информацию об этом.

person Tom Blodget    schedule 10.10.2015