it-swarm.com.ru

Scala карри против частично примененных функций

Я понимаю, что здесь есть несколько вопросов оwhatкарри и частично применяемых функциях, но я спрашиваю о том, чем они отличаются. В качестве простого примера приведу карри для поиска четных чисел:

def filter(xs: List[Int], p: Int => Boolean): List[Int] =
   if (xs.isEmpty) xs
   else if (p(xs.head)) xs.head :: filter(xs.tail, p)
   else filter(xs.tail, p)

def modN(n: Int)(x: Int) = ((x % n) == 0)

Таким образом, вы можете написать следующее, чтобы использовать это:

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))

который возвращает: List(2,4,6,8). Но я обнаружил, что могу сделать то же самое таким образом:

def modN(n: Int, x: Int) = ((x % n) == 0)

val p = modN(2, _: Int)
println(filter(nums, p))

который также возвращает: List(2,4,6,8)

Итак, мой вопрос: в чем главное различие между ними, и когда бы вы использовали один над другим? Это слишком упрощенный пример, чтобы показать, почему один будет использоваться поверх другого?

81
Eric

Семантическая разница была довольно хорошо объяснена в ответ, связанный с Plasty Grove .

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

scala> def modN(n: Int, x: Int) = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>

Таким образом, мы получаем частично примененный <function1>, который принимает Int, потому что мы уже дали ему первое целое число. Все идет нормально. Теперь к карри

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

С этой нотацией вы наивно ожидаете, что сработает следующее:

scala> modNCurried(5)
<console>:9: error: missing arguments for method modN;
follow this method with `_' if you want to treat it as a partially applied function
          modNCurried(5)

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

scala> modNCurried(5) _
res24: Int => Boolean = <function1>

Это то же самое, что мы получили раньше, так что здесь нет никакой разницы, кроме обозначений. Другой пример:

scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>

Это демонстрирует, как частичное применение «нормальной» функции приводит к функции, которая принимает все параметры, тогда как частичное применение функции с несколькими списками параметров создает цепочку функций, по одному на список параметров, которые все возвращают новую функцию:

scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y
scala> foo _
res42: (Int, Int) => Int => (Int => Int) = <function2>

scala> res42(5)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2.
Unspecified value parameter v2.

Как вы можете видеть, поскольку первый список параметров foo имеет два параметра, первая функция в цепочке карри имеет два параметра. 


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

scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>

Пост скриптум

Примечание: Причиной того, что ваш пример println(filter(nums, modN(2)) работает без подчеркивания после modN(2), по-видимому, является то, что компилятор Scala просто считает это подчеркивание для удобства программиста.


Добавление: Как правильно заметил @asflierl, Scala, похоже, не в состоянии определить тип при частичном применении «обычных» функций:

scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

Принимая во внимание, что эта информация доступна для функций, написанных с использованием нескольких списков параметров:

scala> modNCurried(5) _
res3: Int => Boolean = <function1>

Это ответы показывает, как это может быть очень полезно.

86
fresskoma

Каррирование связано с кортежами: превращение функции, которая принимает аргумент Tuple, в функцию, которая принимает n отдельных аргументов, и наоборот . Запоминание этого является ключом к различению карри от частичного применения, даже в языках, которые не поддерживают чисто карри.

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a Tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

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

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

В языках, которые по умолчанию карри (например, Haskell), разница очевидна - вам нужно что-то сделать, чтобы передать аргументы в кортеже. Но большинство других языков, включая Scala, по умолчанию не используются: все аргументы передаются как кортежи, поэтому curry/uncurry гораздо менее полезен и менее очевиден. И люди даже в конечном итоге думают, что частичное применение и каррирование - это одно и то же - просто потому, что они не могут легко представлять функции карри!

19
Don Stewart

Многофакторная функция:

def modN(n: Int, x: Int) = ((x % n) == 0)

Карри (или функция карри):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

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

2
lcn

Просто чтобы уточнить последний пункт 

Дополнение: Как правильно заметил @asflierl, Scala не выглядит уметь выводить тип при частичном применении «нормального» функции:

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

scala> modN(_,_)
res38: (Int, Int) => Boolean = <function2>

scala> modN(1,_)
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1))
       modN(1,_)
              ^
0
Sud