Два способа карри в Скале;какой вариант использования для каждого? - PullRequest
81 голосов
/ 06 февраля 2011

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

def add(a:Int)(b:Int) = {a + b}
// Works
add(5)(6)
// Doesn't compile
val f = add(5)
// Works
val f = add(5)_
f(10) // yields 15

def add2(a:Int) = { b:Int => a + b }
// Works
add2(5)(6)
// Also works
val f = add2(5)
f(10) // Yields 15
// Doesn't compile
val f = add2(5)_

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

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

Ответы [ 3 ]

129 голосов
/ 07 февраля 2011

Несколько методов списка параметров

Для вывода типа

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

def foldLeft[B](z: B)(op: (B, A) => B): B

List("").foldLeft(0)(_ + _.length)

Если бы это было написано как:

def foldLeft[B](z: B, op: (B, A) => B): B

Нужно было бы предоставить более явные типы:

List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)

Для свободного API

Еще одно применение методов с несколькими параметрами - создание API, который выглядит как языковая конструкция. Вызывающий абонент может использовать скобки вместо скобок.

def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)

loop(2) {
   println("hello!")
}

Применение N списков аргументов к методу с разделами параметров M, где N _ или неявно с ожидаемым типом FunctionN[..]. Это функция безопасности, см. Примечания к изменениям для Scala 2.0, в Справочниках Scala, для фона.

Функции Curried

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

val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)

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

Ваш второй пример - гибрид: метод с одним параметром, который возвращает функцию.

Многоступенчатые вычисления

Где еще полезны функции карри? Вот шаблон, который появляется все время:

def v(t: Double, k: Double): Double = {
   // expensive computation based only on t
   val ft = f(t)

   g(ft, k)
}

v(1, 1); v(1, 2);

Как мы можем поделиться результатом f(t)? Распространенным решением является предоставление векторизованной версии v:

def v(t: Double, ks: Seq[Double]: Seq[Double] = {
   val ft = f(t)
   ks map {k => g(ft, k)}
}

Гадкий! Мы запутали несвязанные проблемы - вычислили g(f(t), k) и отобразили последовательность ks.

val v = { (t: Double) =>
   val ft = f(t)
   (k: Double) => g(ft, k)       
}
val t = 1
val ks = Seq(1, 2)
val vs = ks map (v(t))

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

def v(t:Double): Double => Double = {
   val ft = f(t)
   (k: Double) => g(ft, k)       
}

Но если мы попытаемся сделать то же самое с методом с несколькими разделами параметров, мы застрянем:

def v(t: Double)(k: Double): Double = {
                ^
                `-- Can't insert computation here!
}
16 голосов
/ 06 февраля 2011

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

Учитывая различные методы и функции (например, с точки зрения JVM), Scala довольно неплохо стирает грань между ними и в большинстве случаев выполняет «Правильные вещи», но там разница, и иногда вам просто нужно знать об этом.

5 голосов
/ 07 февраля 2011

Я думаю, что это помогает понять различия, если я добавлю, что с def add(a: Int)(b: Int): Int вы просто определяете метод с двумя параметрами, только эти два параметра сгруппированы в два списка параметров (см. Последствияоб этом в других комментариях).На самом деле, этот метод является просто int add(int a, int a) в том, что касается Java (не Scala!).Когда вы пишете add(5)_, это просто функциональный литерал, более короткая форма { b: Int => add(1)(b) }.С другой стороны, с помощью add2(a: Int) = { b: Int => a + b } вы определяете метод, который имеет только один параметр, а для Java это будет scala.Function add2(int a).Когда вы пишете add2(1) в Scala, это просто простой вызов метода (в отличие от литерала функции).

Также обратите внимание, что add имеет (потенциально) меньше накладных расходов, чем add2, если вы немедленно предоставитевсе параметры.Как add(5)(6) просто переводит в add(5, 6) на уровне JVM, объект Function не создается.С другой стороны, add2(5)(6) сначала создаст объект Function, включающий 5, а затем вызовет apply(6) для этого.

...