Скала против карринг, частичное применение функций
Я понимаю, что здесь есть несколько вопросов о что каррирование и частично прикладные функции есть, но я спрашиваю о том, как они отличаются. В качестве простого примера, вот функция карри для нахождения четных чисел:
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, по умолчанию не обработаны-все args передаются как кортежи, поэтому curry/uncurry гораздо менее полезен и менее очевиден. И люди даже в конечном итоге думают, что частичное применение и карри-это одно и то же ... просто потому что они не могут представлять Карри функции легко!
Многомерная функция:
def modN(n: Int, x: Int) = ((x % n) == 0)каррирование (или функция Карри):
def modNCurried(n: Int)(x: Int) = ((x % n) == 0)таким образом, это не частично прикладная функция, которая сравнима с каррингом. Это многомерная функция. Что сопоставимо с частично примененной функцией, так это результат вызова функции карри, которая является функцией с тем же списком параметров, что и частично примененная функция.
просто чтобы уточнить последний пункт
дополнение: как правильно указал @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,_) ^
Comments