Scala currying vs частично применяемые функции

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

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).

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

4 ответов


семантическое различие было довольно хорошо объяснено в ответ, связанный с 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)

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

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>

Post Scriptum

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


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

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

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

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

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


каррирование связано с кортежами:превращение функции, которая принимает аргумент кортежа в тот, который принимает 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, по умолчанию не спешат - все ARG передаются как кортежи, поэтому curry/uncurry гораздо менее полезен и менее очевиден. И люди даже начинают думать, что частичное применение и карри-это одно и то же ... потому что они не могут легко представлять функции Карри!


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

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

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

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

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


просто уточнить по последнему пункту

Addition: как правильно указал @asflierl, Scala не кажется чтобы иметь возможность вывести тип при частичном применении " normal" функции:

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

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

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