Чи можливе одночасно функціонування каррі та варіди?


13

Я замислююся над тим, щоб зробити доступні функції currying і variadic у динамічно набраній мові програмування, але мені цікаво, чи це можливо чи ні.

Ось псевдокод:

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
Власне кажучи, використання currying в якості основи мови означає, що всі функції є одинарними - не тільки немає варіативних функцій, немає навіть бінарних! Однак програми на цій мові все одно будуть виглядати так, ніби вони взяли кілька аргументів, і це стосується варіативних функцій так само, як і для звичайних.
Кіліан Фот

Тоді моє запитання спрощується до "чи може об'єкт одночасно бути функцією та нефункціональним значенням?" У наведеному вище прикладі, sumце 0без аргументу і рекурсивно викликає себе з аргументом.
Майкл Цанг

хіба це не робота reduce?
храповик урод

1
Подивіться на функції , які ви використовуєте на args: empty, head, і tail. Це всі функції списку, які дозволяють припустити, що, можливо, простіше і простіше зробити, було б використовувати список, де були б різні варіанти. (Отже, sum [1, 2, 3]замість sum 1 2 3)
Майкл Шоу

Відповіді:


6

Як можна реалізувати varargs? Нам потрібен якийсь механізм, щоб сигналізувати про закінчення списку аргументів. Це може бути або

  • спеціальне значення термінатора, або
  • довжина списку vararg, передана як додатковий параметр.

Обидва ці механізми можуть бути використані в контексті currying для реалізації varargs, але правильне введення тексту стає головною проблемою. Припустимо, що ми маємо справу з функцією sum: ...int -> int, за винятком того, що ця функція використовує currying (тому ми насправді маємо тип більше подібний 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 - це кількість vararg. Так 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буде a 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';
}

Дивовижна відповідь! Недоліком упаковки вараг у списку є те, що ви втрачаєте часткове застосування каррі. Я грав з версією 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аргумент є істинним, викликається оригінальна функція з усіма зібраними досі аргументами.

Підказки цієї реалізації полягають у тому, що вам завжди потрібно передавати перший аргумент, щоб fви не могли створити "стовбур", функцію, де всі аргументи пов'язані і можна викликати лише зі порожнім списком аргументів (але я думаю, це відповідає типова реалізація каррі).

Ще одне застереження полягає в тому, що після передачі помилкового аргументу (наприклад, неправильного типу) вам доведеться повторно використати початкову функцію. Немає іншого способу скидання внутрішнього масиву, це робиться лише після успішного виконання curred функції.

Я не знаю, чи може ваше спрощене запитання "чи може об'єкт бути функцією і одночасно нефункціональним значенням?", Може бути реалізовано в Python, як посилання на функцію без дужок оцінюється на внутрішній об'єкт функції . Я не знаю, чи можна це зігнути, щоб повернути довільне значення.

Напевно, це буде легко в Lisp, оскільки символи Lisp можуть одночасно мати значення та значення функції; значення функції просто вибирається, коли символ з’являється у функціональному положенні (як перший елемент у списку).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.