Шаблон для каррирования функций в D? - PullRequest
2 голосов
/ 28 сентября 2019

Можно ли написать шаблон или аналогичный шаблон, который можно использовать для автоматического выполнения функций карри в D?Выписывание всех вложенных делегатов вручную убивает меня.

По сути, для функции f, например, с 3 аргументами, которые обычно можно вызвать как f(a,b,c), я хочу, чтобы она вызывалась как f(a)(b)(c),

Я знаю о std.functional.partial , но это не то, что я хочу.Я хочу перевести сторону определения функции, а не сторону вызова.

Я также знаю, что это далеко не лучшая практика, но я генерирую код, так что терпите меня.

1 Ответ

4 голосов
/ 28 сентября 2019

Ну, что-то в этом духе должно сработать:

template autocurry(alias what) {
        import std.traits;
        static if(Parameters!what.length)
                auto autocurry(Parameters!what[0] arg) {
                        alias Remainder = Parameters!what[1 .. $];
                        auto dg = delegate(Remainder args) {
                                return what(arg, args);
                        };

                        static if(Remainder.length > 1)
                                return &autocurry!dg;
                        else
                                return dg;
                }
        else
                alias autocurry = what;
}

int foo(int a, string b, float c) {
        import std.stdio; writeln(a, " ", b, " ", c);
        return 42;
}

string test() {
        import std.stdio; writeln("called test");
        return "no args";
}

void main() {
        import std.stdio;
        alias lol = autocurry!foo;
        writeln(lol(30)("lol")(5.3));

        auto partial = lol(20);
        partial("wtf")(10.5);

        alias t = autocurry!test;
        writeln(t());
}

Идея довольно проста: сгенерировать вспомогательную функцию на основе оставшихся аргументов - если они есть, вернуть адреспомощник в качестве делегата, в противном случае просто верните делегат, который вызывает собранные аргументы.Небольшая рекурсивность обрабатывает 1+ аргументов arg, а статическое if снаружи обрабатывает 0 аргументов arg, просто возвращая исходную функцию.

Особенности языка, которые нужно отметить:

  • одноименные шаблоны.Когда шаблон имеет элемент с тем же именем, что и шаблон (в данном случае, autocurry), на него автоматически ссылаются при использовании.

  • расширение кортежа.Когда я вызываю what(arg, args), args, являющийся встроенным кортежем, автоматически расширяется для создания полного списка аргументов.

  • различные auto возвращает здесь (явноеauto autocurry и неявное ключевое слово delegate без указания типа возвращаемого значения) просто переслать любой другой случайный тип, возвращаемый телом.

  • В основной функции я сделал alias lol = autocurry!foo;(Я часто использую lol в качестве имени-заполнителя, lol).Вы также можете перегрузить его на верхнем уровне:

      int foo(int a, string b, float c) {
              import std.stdio; writeln(a, " ", b, " ", c);
              return 42;
      }

      alias foo = autocurry!foo; // overloads the auto-curried foo with the original foo

И теперь вы можете использовать его напрямую, наряду с оригиналом:

void main() {
        foo(30)("lol")(5.3); // overload resolves to curried version
        foo(40, "cool", 103.4); // overload resolves to original automatically
}

Если вы предпочитаетеновое имя или перегрузка на ваше усмотрение, либо может работать.

Обратите внимание, что каждый аргумент обязан выделить некоторую память для хранения ее для следующего делегата.ГХ будет нести ответственность за его очистку.

...