Два способа карри в Скале; какой вариант использования для каждого?

У меня есть обсуждение нескольких списков параметров в Руководстве по стилю 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 ответа

Решение

Методы с несколькими списками параметров

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

Методы с несколькими разделами параметров могут использоваться для облегчения локального вывода типа, используя параметры в первом разделе, чтобы вывести аргументы типа, которые обеспечат ожидаемый тип для аргумента в следующем разделе. 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, для фона.

Карри функции

Каррированные функции (или просто функции, которые возвращают функции) легче применять к 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!
}

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

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

Я думаю, что это помогает понять различия, если я добавлю это с 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) на что.

Другие вопросы по тегам