WarCraft 3: 10. Lambda выражения

WurstScript

Определение

Wurst поддерживает особую конструкцию, известную как "Лямбда выражение" или "Анонимная функция". Следующий пример ее продемонстрирует
() -> print("Hello, World!")
Здесь вы должны обратить внимание на символ ->, который разделяет конструкцию на 2 части. Слева — список формальных параметров, которые функция принимает (в данном случае, ничего не принимает), справа — тело функции, ее реализация. Реализация анонимной функции предусматривает использование только одной инструкции, так как анонимные функции обычно очень малы и используются в одной строке. Давайте посмотрим, как мы могли бы использовать представленную выше конструкцию на практике
init
    CreateTimer()
        ..startPeriodic(2.0, ()->print("Hello, World!"))
В результате выполнения этого кода создается таймер, который будет вызывать нашу анонимную функцию каждые 2 секунды и выводить сообщение "Hello, World!" на экран. В классическом случае, нам бы пришлось написать для таймера внешнюю функцию
function TimerHello()
    print("Hello, World!")

init
    CreateTimer()
        ..startPeriodic(2.0, function TimerHello)
Как видите, анонимные функции, это способ писать более компактный код.
Представленный выше пример чрезвычайно бесполезен, какой толк от ежесекундного приветствия? Посмотрим, как мы можем использовать анонимные функции на более практичном примере
function TrgCondition() returns bool
    return GetSpellAbilityID == 'A000'

init
    CreateTrigger()
        ..addCondition(Condition( function TrgCondition ))
Рутинная ситуация — создается триггер с проверкой условия в одну единственную инструкцию. Лямбда-выражение позволит сократить этот код
init
    CreateTrigger()
        ..addCondition(Condition( ()->GetSpellAbilityID == 'A000' ))
Данный пример так же демонстрирует возможность возврата значения из анонимной функции, если выражение в теле функции подразумевает некоторый результат.
При необходимости, лямбда-выражение может быть записано в несколько строк, для этого используются ключевые слова begin-end
CreateTimer()
    ..start(5.0, ()->begin
    ...
    print("Hello, World!")
end)
В подобном случае, выражение так же может возвращать значение, однако, оператор return должен иметь место как самая последняя инструкция.

Функциональный интерфейс

Функциональный интерфейс — интерфейс имеющий единственный метод.
// Функциональный интерфейс
interface Predicate<T>
    function isTrueFor(T t) returns bool

// Простая реализация
class IsEven implements Predicate<int>
    function isTrueFor(int x) returns bool
        return x mod 2 == 0

// Теперь мы можем использовать это следующим образом
let x = 3
Predicate<int> pred = new IsEven()
if pred.isTrueFor(x)
    print("x is even")
else
    print("x is odd")
destroy pred
В данном случае, нам пришлось определить класс IsEven, для реализации интерфейса Predicate.
Используя лямбда-выражение, мы можем выполнить реализацию этого интерфейса на месте
let x = 3

Predicate<int> pred = (int x) -> x mod 2 == 0
if pred.isTrueFor(x)
    print("x is even")
else
    print("x is odd")
destroy pred

Захват переменных

Прим. Пер.: Изначально я должен был ввести такое понятие как "Замыкание", но учитывая, что подобное в Wurst лишь условность, решил отказаться от этого термина.
Одна из действительно крутых функций лямбда-выражений — захват локальных переменных. Это означает, что анонимная функция имеет доступ к значениям локальных переменных функции, внутри которой она была объявлена
var min = 10
var max = 50
// Удаление всех значений в списке, между значениями min и max
myList.removeWhen((int x) ->  x < min or x > max)
Важно помнить, значения переменных min и max только копируются, соответственно, какие-либо изменения внутри анонимной функции не окажут влияния на оригинал. То же верно и в обратном случае — какие-либо изменения оригинальных переменных, после объявления анонимной функции, не окажут влияния на их копии
var s = "Hello!"
// Подразумевается, интерфейс CallbackFunc был объявлен ранее где-то в коде
CallbackFunc f = () -> begin
    print(s)
    s = s + "!"
end
s = "Bye!"
f.run()  // Выведет "Hello!"
f.run()  // Выведет "Hello!!"
print(s) // Выведет "Bye!"

За кулисами

Компилятор создает новый класс для каждого лямбда-выражения в вашем коде. Этот класс реализует интерфейс в контексте, внутри которого лямбда-выражение использовано. Сгенерированный класс будет иметь атрибуты для записи значения всех захваченных локальных переменных. При всяком объявлении лямбда-выражения, создается новый объект класса, которому устанавливаются значения атрибутов.
Выше представленный пример "Hello!", может быть приведен к следующему эквивалентному коду
// Объявление этого интерфейса ранее было скрыто
interface CallbackFunc
    function run()

// Компилятор создаст класс, реализующий интерфейс выше
class Closure implements CallbackFunc
    // Атрибуты для каждой захваченной переменной
    string s

    function run()
        // Тело лямбда-варажения == тело этой функции
        print(s)
        s = s + "!"

var s = "Hello!"
CallbackFunc f = new Closure()
// Захват переменных
f.s = s
s = "Bye!"
f.run()  // Выведет "Hello!"
f.run()  // Выведет "Hello!!"
print(s) // Выведет "Bye!"

Тип функции

Лямбда-выражение имеет специальный тип, формирующийся из типов формальных параметров и возвращаемого типа. Этот тип именуется типом функции
() -> 1
    // Тип: () -> integer

(real r) -> 2*r
    // Тип: (real) -> real

(int x, string s) -> s + I2S(x)
    // Тип: (int,string) -> string
Хотя тип функции и является частью системы типов, Wurst не имеет возможности определить и записать тип функции. То есть, не существует переменной типа (int,string) -> string. Из-за этого лямбда-выражения могут быть использованы только в том месте, где известен конкретный интерфейсе или класс
Predicate<int> pred = (int x) -> x mod 2 == 0
Они не могут быть присвоены переменной, чей тип прогнозируется
// Не будет скомпилировано с ошибкой "Could not get super class for closure"
let pred = (int x) -> x mod 2 == 0

Lambda-выражение как тип code

Лямбда-выражения могут быть использованы там, где ожидается тип code. В таком случае, оно будет преобразовано в обычную Jass-функцию. Предпосылками для этого служат отсутствие формальных параметров функции и отсутствие захвата каких-либо переменных. Взглянем еще раз на пример с которого начиналась эта глава
CreateTimer()
    ..startPeriodic(2.0, ()->print("Hello, World!"))
Здесь, вместо создания нового класса, лямбда-выражение будет преобразовано в обычную функцию и передано как значение типа code. Соответственно, мы не создаем новый объект, который были бы вынуждены в последствии уничтожить.
Из вышесказанного вытекает — мы не можем производить захват переменных там, где анонимная функция используется в качестве типа code
let t = getTimer()
let x = 3
t.start(3.0, () -> doSomething(x)) // Ошибка: Невозможно захватить локальную переменную 'x'

Просмотров: 119

Комментарии пока отсутcтвуют