Семантическая разница была довольно хорошо объяснена в ответе, на который ссылается Plasty Grove .
Однако с точки зрения функциональности особой разницы нет. Давайте посмотрим на несколько примеров, чтобы убедиться в этом. Во-первых, нормальная функция:
scala> def modN(n: Int, x: Int): Boolean = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>
Итак, мы получаем частично примененное, <function1>
которое принимает Int
, потому что мы уже дали ему первое целое число. Все идет нормально. Теперь к каррированию:
scala> def modNCurried(n: Int)(x: Int): Boolean = ((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): 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>
Эти ответы показывают, насколько это может быть очень полезно.