Какая разница между currying и несколькими списками параметров?

Всюду, где я смотрю, я вижу, что термины с несколькими списками параметров и currying используются взаимозаменяемо. Я вижу это в десятках вопросов stackoverflow и даже на scala -lang.org. Эта страница, например, имеет название "Currying". И первое предложение? "Методы могут определять несколько списков параметров".

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

Я знаю, что в stackoverflow есть много очень похожих вопросов, но я не нашел того, что точно говорит о различии. Что мне нужно знать о нескольких списках параметров и currying, чтобы точно говорить о них?

Ответы

Ответ 1

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

Prelude> let add = (\(x, y) -> x + y) :: (Int, Int) -> Int
Prelude> add (1, 2)
3

Теперь мы можем выполнить эту функцию для получения функции с типом Int -> Int -> Int вместо (Int, Int) -> Int:

Prelude> let curriedAdd = curry add

Это позволяет частично применить функцию, например:

Prelude> let add3 = curriedAdd 3
Prelude> add3 1
4

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

Prelude> :t curry
curry :: ((a, b) -> c) -> a -> b -> c

Хорошо, теперь для Scala.

В Scala вы также можете иметь функции, которые принимают аргумент кортежа. Scala также имеет "функции", которые принимают более одного аргумента (Function2 и выше). Это (путано) разные виды животных.

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

Итак, что значит сказать, что мы что-то понимаем в этом контексте?

В буквальном смысле, currying - это то, что вы делаете с Function2 (и вверх):

scala> val add: Function2[Int, Int, Int] = (x: Int, y: Int) => x + y
add: (Int, Int) => Int = <function2>

scala> val curriedAdd = add.curried
curriedAdd: Int => (Int => Int) = <function1>

scala> val add3 = curriedAdd(3)
add3: Int => Int = <function1>

Это примерно то же, что мы видели в случае Haskell, за исключением того, что мы просматриваем функцию с несколькими аргументами, что не так хорошо существует в Haskell.

Теперь я уверен, что это единственный контекст, в котором слово curry действительно появляется в стандартной библиотеке Scala (не считая сопровождающего uncurried на Function сопутствующий объект), но учитывая огромный беспорядок, который Scala делает из идеи методов, функций и т.д. (не поймите меня неправильно - я люблю Scala, но это часть языка - это полная катастрофа), мне представляется разумным применить слово в следующем контексте:

def add(x: Int, y: Int) = x + y
def curriedAdd(x: Int)(y: Int) = add(x, y)

Здесь мы превратили метод, который принимает два параметра в метод с несколькими списками параметров, каждый из которых принимает только один параметр (эта последняя часть важна).

И на самом деле спецификация языка также использует этот термин в этом контексте, описывая следующее как "определение одной, кардной функции" :

def func(x: Int)
        (y: Int) = x + y

(Это, конечно, сбивает с толку как ад, так как это метод, а не функция.)

Итак, чтобы подвести итог: несколько списков параметров являются одним из способов реализации currying в Scala, но не все методы с несколькими списками параметров являются только для curries, где каждый список параметров имеет единственный параметр. И вся терминология довольно мягкая, так или иначе, так что не беспокойтесь о том, чтобы правильно это понять.