Семантична різниця досить добре пояснена у відповіді, на яку посилається Пластиковий гай .
Що стосується функціональних можливостей, то, здається, різниці немає. Давайте розглянемо кілька прикладів, щоб підтвердити це. По-перше, нормальна функція:
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
має два параметри, перша функція в ланцюжку, що працює, має два параметри.
Підводячи підсумок, частково застосовані функції насправді не відрізняються від функцій, що функціонують з використанням каррі. Це легко перевірити, враховуючи те, що ви можете перетворити будь-яку функцію на функцію curry:
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$1) => modN(5, x$1))
Беручи до уваги, що ця інформація доступна для функцій, написаних із використанням нотації списку декількох параметрів:
scala> modNCurried(5) _
res3: Int => Boolean = <function1>
Ці відповіді показують, як це може бути дуже корисним.