Можно ли одновременно иметь карри и вариадную функцию?

13

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

Вот несколько псевдокодов:

sum = if @args.empty then 0 else @args.head + sum @args.tail

который якобы суммирует все свои аргументы. Затем, если sumсам трактуется число, то результат 0. например,

sum + 1

равен 1, при условии, что +может работать только на числа. Однако, даже если sum == 0это правда, sumвсе равно сохранит свое значение и функциональное свойство независимо от того, сколько аргументов задано (например, «частично применено» и «вариадно» одновременно), например, если я объявлю

g = sum 1 2 3

затем gравен 6, однако, мы можем еще применить g. Например, g 4 5 == 15это правда. В этом случае мы не можем заменить объект gлитералом 6, потому что, хотя они и дают одно и то же значение, когда рассматриваются как целое число, они содержат разные коды внутри.

Если этот дизайн используется на реальном языке программирования, это вызовет какую-то путаницу или двусмысленность?

Майкл Цанг
источник
1
Строго говоря, использование карри в качестве основы языка означает, что все функции унарны - не только нет никаких функций с переменными числами, но даже нет двоичных! Тем не менее, программы на этом языке будут выглядеть так, как будто они принимают несколько аргументов, и это касается как функций с переменным числом, так и обычных функций.
Килиан Фот
Тогда мой вопрос упрощается так: «Может ли объект быть одновременно функцией и нефункциональным значением?» В приведенном выше примере, sumэто 0без аргумента и рекурсивно вызывает себя с аргументом.
Майкл Цанг
разве это не работа reduce?
чокнутый урод
1
Посмотрите на функции , которые вы используете на args: empty, head, и tail. Это все функции списков, предполагающие, что, возможно, проще и понятнее было бы использовать список, в котором будут использоваться различные варианты. (Итак, sum [1, 2, 3]вместо sum 1 2 3)
Майкл Шоу

Ответы:

6

Как можно реализовать varargs? Нам нужен какой-то механизм для оповещения об окончании списка аргументов. Это может быть

  • специальное значение терминатора, или
  • длина списка vararg, переданного в качестве дополнительного параметра.

Оба эти механизма могут использоваться в контексте каррирования для реализации varargs, но правильная типизация становится серьезной проблемой. Давайте предположим, что мы имеем дело с функцией sum: ...int -> int, за исключением того, что эта функция использует каррирование (поэтому у нас на самом деле есть тип более похожий sum: int -> ... -> int -> int, за исключением того, что мы не знаем количество аргументов).

Случай: значение терминатора: Позвольте endбыть специальным терминатором, и Tбудет тип sum. Теперь мы знаем , что применительно к endфункции возвращает: sum: end -> intи, применяемые к междунар мы получаем другую сумму-как функция: sum: int -> T. Поэтому Tявляется объединение этих типов: T = (end -> int) | (int -> T). Подстановкой T, мы получаем различные возможные типы , такие как end -> int, int -> end -> int, int -> int -> end -> intи т.д. Тем не менее, большинство систем типа не учитывают таких типов.

Случай: явная длина: первый аргумент функции vararg - это число varargs. Итак sum 0 : int, sum 1 : int -> intи sum 3 : int -> int -> int -> intт. Д. Это поддерживается в некоторых системах типов и является примером зависимой типизации . На самом деле, число аргументов будет параметром типа, а не обычным параметром - не имеет смысла зависеть от арности функции, зависящей от значения времени выполнения, s = ((sum (floor (rand 3))) 1) 2очевидно, что она не типизирована: это дает либо s = ((sum 0) 1) 2 = (0 1) 2, либо s = ((sum 1) 1) 2 = 1 2, либо s = ((sum 2) 1) 2 = 3.

На практике ни один из этих методов не следует использовать, поскольку они подвержены ошибкам и не имеют (значимого) типа в системах общих типов. Вместо этого, просто передать список значений в качестве одного параметра Я : sum: [int] -> int.

Да, объект может отображаться как функция и значение, например, в системе типов с принуждениями. Позвольте sumбыть SumObj, который имеет два принуждения:

  • coerce: SumObj -> int -> SumObjпозволяет sumиспользовать в качестве функции, и
  • coerce: SumObj -> int позволяет нам извлечь результат.

Технически, это вариант описанного выше значения терминатора T = SumObj, coerceкоторый является и не является оберткой для данного типа. Во многих объектно-ориентированных языках это легко реализовать с перегрузкой операторов, например, C ++:

#include <iostream>
using namespace std;

class sum {
  int value;
public:
  explicit sum() : sum(0) {}
  explicit sum(int x) : value(x) {}
  sum operator()(int x) const { return sum(value + x); }  // function call overload
  operator int() const { return value; } // integer cast overload
};

int main() {
  int zero = sum();
  cout << "zero sum as int: " << zero << '\n';
  int someSum = sum(1)(2)(4);
  cout << "some sum as int: " << someSum << '\n';
}
Амон
источник
Отличный ответ! Недостатком упаковки varargs в списке является то, что вы теряете частичное применение карри. Я играл с Python-версией вашего подхода терминатора, используя аргумент-ключевое слово ..., force=False)для принудительного применения начальной функции.
ThomasH
Вы можете сделать свою собственную функцию более высокого порядка, которая частично применяет функцию, которая принимает список, например curryList : ([a] -> b) -> [a] -> [a] -> b, curryList f xs ys = f (xs ++ ys).
Джек
2

Возможно, вы захотите взглянуть на эту реализацию printf в Haskell вместе с описанием того, как она работает . На последней странице есть ссылка на статью Олега Киселева о таких действиях, которую также стоит прочитать. На самом деле, если вы разрабатываете функциональный язык, сайт Олега, вероятно, должен быть обязательным для чтения.

На мой взгляд, эти подходы немного взломать, но они показывают, что это возможно. Однако, если ваш язык имеет полностью зависимую типизацию, это намного проще. Функция с переменным числом для суммирования целочисленных аргументов может выглядеть примерно так:

type SumType = (t : union{Int,Null}) -> {SumType, if t is Int|
                                         Int,     if t is Null}
sum :: SumType
sum (v : Int) = v + sum
sum (v : Null) = 0

Абстракция для определения рекурсивного типа без необходимости давать ему явное имя может упростить написание таких функций.

Редактировать: конечно, я просто прочитал вопрос еще раз, и вы сказали, что язык с динамической типизацией, в этот момент очевидно, что механика типов на самом деле не актуальна, и поэтому ответ @ amon, вероятно, содержит все, что вам нужно. О, хорошо, я оставлю это здесь на случай, если кто-нибудь столкнется с этим, задаваясь вопросом, как это сделать на статическом языке ...

Жюль
источник
0

Вот версия для каррирования функций с переменными числами в Python3, которая использует подход «терминатора» @amon, используя дополнительные аргументы Python:

def curry_vargs(g):
    actual_args = []
    def f(a, force=False):
        nonlocal actual_args
        actual_args.append(a)
        if force:
            res = g(*actual_args)
            actual_args = []
            return res
        else:
            return f
    return f

def g(*args): return sum(args)
f = curry_vargs(g)
f(1)(2)(3)(4,True) # => 10

Возвращенная функция fсобирает аргументы, переданные ей в последовательных вызовах, в массиве, который связан с внешней областью. Только когда forceаргумент равен true, исходная функция вызывается со всеми собранными до сих пор аргументами.

Предостережения этой реализации заключаются в том, что вам всегда нужно передавать первый аргумент, fчтобы вы не могли создать «thunk», функцию, где все аргументы связаны и могут вызываться только с пустым списком аргументов (но я думаю, что это соответствует типичная реализация карри).

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

Я не знаю, может ли ваш упрощенный вопрос «может ли объект быть одновременно функцией и не функциональным значением?», Может быть реализован в Python, так как ссылка на функцию без скобок оценивает внутренний объект функции , Я не знаю, можно ли это согнуть, чтобы вернуть произвольное значение.

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

ThomasH
источник