Перейти к публикации
Форум - ComputerCraft
D-side

Привязка аргументов

Рекомендованные сообщения

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

 

Подобные штуки есть в куче языков. В стандартную библиотеку C++0x для этого (в том числе) ввели std::function, в Haxe и Haskell это просто часть языка. Многие считают такой приём чертой функционального стиля программирования, который я ещё не освоил, но стараюсь.

 

Работает это очень просто и код очень короткий. Берём функцию, указываем ей несколько первых аргументов - получаем функцию, которой надо указать только оставшиеся (если таковые есть):

function bind(f, ...)
 local higherArg = arg
 return function(...)
   return f( unpack(higherArg), unpack(arg) )
 end
end

Т. е. эта штука возвращает функцию, которая возвращает результат указанной вами функции, аргументами к которой выступает то, что вы указали при привязке (вызове bind), а затем то, что вы указали при вызове возвращённой из привязки функцией. Ещё ничего не закипело от обилия функций? Привожу пример.

Можете посмотреть сразу в интерпретаторе Lua. Это не слишком практичный пример, поэтому вот кое-что поинтереснее.

 

Допустим, вы хотите получить таблицу, содержащую путь к файлу и ряд команд, которые с ним можно выполнить без указания лишних данных. Примерно как у обычного объекта. Да, можно собрать класс "файл", но это не единственный способ, и у него свои недостатки. С биндом подобная штука записывается очень коротко:

filename = "file"
file = {
 filename = filename,
 run = bind(shell.run, filename), -- Можно смешивать функции из разных API
 delete = bind(fs.delete, filename) -- вообще без проблем
}
-- И эти операции можно вызывать без аргументов, забыв про API:
file.run() -- Запускаем файл
file.delete() -- Стираем файл

Конечно, в случае больших задач "нормальный" класс (с вызовами методов через двоеточие) предпочтительнее, поскольку даёт больше контроля над возвращаемыми значениями и обработкой возможных ошибок. Но если это не требуется, можно сэкономить время с помощью привязки. Есть и другие способы применения, на которые вы, может быть, наткнётесь сами.

Чаще всего это способ сэкономить на объёме кода, уложив повторяющийся аргумент. Компактнее, чем это делает вынесение в переменную, хотя потребляет несколько больше оперативной памяти, ведь такие "методы" будут создаваться каждый раз на каждую привязку. Осторожней с этим.

 

Это часть более крупной библиотеки, которую я пишу вот тут, на гитхабе. Потом перейду к каркасику для приложений, состоящих из менюшек (что покрывает на удивление много случаев).

 

Получить эту штуку на ComputerCraft'овский комп можно вот так:

openp/github get D-side/luaCC/master/bind.lua bind

Такие дела. Развлекайтесь! Надеюсь, скоро принесу ещё каких-нибудь плюшек.

  • Like 3

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

filename = "file"
file = {
 filename = filename,
 run = bind(shell.run, filename), -- Можно смешивать функции из разных API
 delete = bind(fs.delete, filename) -- вообще без проблем
}
-- И эти операции можно вызывать без аргументов, забыв про API:
file.run() -- Запускаем файл
file.delete() -- Стираем файл

 

Зачем в приведенном примере поле filename = filename? Ведь по сути будет использоваться только внешняя переменная filename.

Что касается уменьшения объема кода, согласен. Но увеличит ли этот механизм наглядность? Например, встретив запись file.delete(), сразу ли вы определите какой именно файл удаляется?

Или будет написано

filename = "file"
file.delete()
Это, по моему, ни чуть не короче чем fs.delete("file").

А, вообще, Lua имеет уникальный синтаксис, позволяющий творить подобные вещи. За что его и люблю.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Зачем в приведенном примере поле filename = filename? Ведь по сути будет использоваться только внешняя переменная filename.

В синтаксисе определения таблиц слева от = не переменная, а строковый ключ, написание которого подчиняется тем же правилам, что и переменные - чтобы его можно было вызывать через точку. Это короткая запись чуть более длинной:
{["filename"] = filename, <...>}

 

Но увеличит ли этот механизм наглядность? Например, встретив запись file.delete(), сразу ли вы определите какой именно файл удаляется?

Это зависит от программиста и определяется при создании объекта. Смысл можно заложить в само название переменной, куда он записан. Скажем, logfile.delete() - да, я однозначно пойму, что это, т. к. название переменной осмысленное.

 

Это, по моему, ни чуть не короче чем fs.delete("file").

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

 

Справедливости ради стоит приложить аналогичный код на классе File, который я пока не написал, т. к. сегодня (буквально часа два назад) поставил свежий Linux Mint и накатил среду разработки в Lua.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

В синтаксисе определения таблиц слева от = не переменная, а строковый ключ, написание которого подчиняется тем же правилам, что и переменные - чтобы его можно было вызывать через точку. Это короткая запись чуть более длинной:

{["filename"] = filename, <...>}

Это понятно. Просто, в приведенном примере поле file.filename нигде не используется. Используется же внешняя переменная filename (например, run = bind(shell.run, filename)). Отсюда и вопрос, зачем поле file.filename?

С остальным согласен.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Это понятно. Просто, в приведенном примере поле file.filename нигде не используется. Используется же внешняя переменная filename (например, run = bind(shell.run, filename)). Отсюда и вопрос, зачем поле file.filename?

С остальным согласен.

Там, где объект будет использоваться, та внешняя переменная может уже быть недоступна. Скажем, это поле можно использовать для вывода файла на экран при наличии только объекта на этом файле.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас

×