Частичное применение и закрытие

Меня спросили, какая связь между приложением частичной функции и закрытием. Я бы сказал, что нет никого, если я не упущу смысл. Скажем, я пишу на python, и у меня есть очень простая функция MySum, которая определяется следующим образом:

MySum = lambda x, y : x + y;

Теперь я фиксирую один параметр для получения функции с меньшей arity, которая возвращает то же значение, что и MySum, если я назову его с теми же параметрами (частичное приложение):

MyPartialSum = lambda x : MySum(x, 0);

Я мог бы сделать то же самое с C:

int MySum(int x, int y) { return x + y; }
int MyPartialSum(int x) { return MySum(x, 0); }

Итак, глупый вопрос: какая разница? Зачем мне закрывать частичные приложения? Эти коды эквивалентны, я не вижу, что связано с закрытием и частичным приложением.

Ответ 1

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

Другими словами, если у вас есть функция F(a, b), функция, которая применяет частичное приложение a, будет выглядеть как B(fn, a) где F(a, b) = B(F, a)(b).

В вашем примере вы просто создаете новые функции, а не применяете частичное приложение к существующему.

Вот пример в python:

def curry_first(fn, arg):
    def foo(*args):
       return fn(arg, *args)
    return foo

Это создает замыкание по предоставленной функции и аргументу. Возвращается новая функция, которая вызывает первую функцию с новой сигнатурой аргумента. Закрытие важно - оно позволяет fn получить доступ к arg. Теперь вы можете делать такие вещи:

add = lambda x, y : x + y;
add2 = curry_first(add, 2)
add2(4) # returns 6

Я обычно слышал, что это называлось currying.

Ответ 2

Приложение с частичной функцией - это исправление некоторых аргументов данной функции, чтобы получить другую функцию с меньшим количеством аргументов, например

sum = lambda x, y: x + y
inc = lambda x: sum(x, 1)

Обратите внимание, что "inc" - это "сумма", частично примененная, не захватывая ничего из контекста (как вы упомянули закрытие).

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

# sum = lambda x, y: x + y
def makePartialSumF(n):
    def partialSumF(x):
        return sum(x, n)
    return partialSumF

inc = makePartialSumF(1)
plusTwo = makePartialSumF(2)

Здесь factory makePartialSumF вызывается дважды. Каждый вызов приводит к функции partialSumF (фиксируя разные значения как n). Использование закрытия делает реализацию частичного приложения удобной. Таким образом, вы можете сказать, что частичное приложение может быть реализовано посредством закрытия. Конечно, закрытие может многое сделать! (В качестве стороны node у python нет правильного закрытия.)

Currying - это превращение функции из N аргументов в унарную функцию, которая возвращает унарную функцию..., например, мы имеем функцию, которая принимает три аргумента и возвращает значение:

sum = lambda x, y, z: x + y + z

Версия с картой

curriedSum = lambda x: lambda y: lambda z: x + y + z

Бьюсь об заклад, вы не напишете такой код на Python. ИМО мотивация Currying в основном представляет собой теоретический интерес. (Структура выражения вычислений с использованием только унарных функций: каждая функция унарна!). Практический побочный продукт состоит в том, что в языках, где функции находятся в карри, некоторые частичные приложения (когда вы исправляете "аргументы слева" ) так же тривиальны, как и предоставление аргументов to curried function. (Но не все частичные приложения как таковые. Пример: задано f (x, y, z) = x + 2 * y + 3 * z, когда вы привязываете y к константе, чтобы получить функцию двух переменных.) Итак, вы может сказать, что Currying - это техника, которая на практике и как побочный продукт может сделать много полезных частично функциональных приложений тривиальными, но это не точка Currying.

Ответ 3

Просто результат частичного приложения обычно реализуется как закрытие.

Ответ 4

Закрытие не является обязательной функциональностью на языке. Я экспериментирую с самодельным языком, lambdatalk, в котором лямбды не создают замыкания, но принимают частичное применение. Например, так можно определить набор [cons, car, cdr] в SCHEME:

(def cons (lambda (x y) (lambda (m) (m x y))))
(def car (lambda (z) (z (lambda (p q) p))))
(def cdr (lambda (z) (z (lambda (p q) q))))

(car (cons 12 34)) -> 12
(cdr (cons 12 34)) -> 34

и в lambdatalk:

{def cons {lambda {:x :y :m} {:m :x :y}}} 
{def car {lambda {:z} {:z {lambda {:x :y} :x}}}}
{def cdr {lambda {:z} {:z {lambda {:x :y} :y}}}}

{car {cons 12 34}} -> 12
{cdr {cons 12 34}} -> 34

В SCHEME внешняя лямбда сохраняет x и y в закрытии, чтобы внутренняя лямбда могла получить доступ к данному m. В lambdatalk лямбда сохраняет: x и: y и возвращает новую лямбду, ожидающую: m. Таким образом, даже если закрытие (и лексический охват) полезны для функциональности, нет необходимости. Без каких-либо свободных переменных из любой лексической области функции являются истинными черными ящиками без какого-либо побочного эффекта, в полной независимости, следуя истинной функциональной парадигме. Вы так не считаете?

Ответ 5

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

Если в будущем вы решите изменить логику MySum (скажем, например, верните x + y + 1), вам не придется беспокоиться о MyPartialSum, потому что он вызывает MySum

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