Różnica semantyczna została dość dobrze wyjaśniona w odpowiedzi, do której odnosi się Plasty Grove .
Pod względem funkcjonalności nie wydaje się jednak dużej różnicy. Spójrzmy na kilka przykładów, aby to sprawdzić. Po pierwsze, normalna funkcja:
scala> def modN(n: Int, x: Int): Boolean = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>
Otrzymujemy więc częściowo zastosowaną, <function1>
która przyjmuje an Int
, ponieważ podaliśmy już pierwszą liczbę całkowitą. Jak na razie dobrze. Teraz do curry:
scala> def modNCurried(n: Int)(x: Int): Boolean = ((x % n) == 0)
Przy takim zapisie można by naiwnie oczekiwać, że zadziała:
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)
Więc notacja listy wielu parametrów tak naprawdę nie tworzy funkcji curried od razu (zakładając, że ma to na celu uniknięcie niepotrzebnego narzutu), ale czeka, aż wyraźnie stwierdzisz, że chcesz, aby była ona curried (notacja ma również inne zalety ):
scala> modNCurried(5) _
res24: Int => Boolean = <function1>
To jest dokładnie to samo, co wcześniej, więc nie ma tu żadnej różnicy, z wyjątkiem notacji. Inny przykład:
scala> modN _
res35: (Int, Int) => Boolean = <function2>
scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>
To pokazuje, jak częściowe zastosowanie „normalnej” funkcji daje w wyniku funkcję, która przyjmuje wszystkie parametry, podczas gdy częściowe zastosowanie funkcji z wieloma listami parametrów tworzy łańcuch funkcji, jednej na listę parametrów, które wszystkie zwracają nową funkcję:
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.
Jak widać, ponieważ pierwsza lista parametrów programu foo
ma dwa parametry, pierwsza funkcja w łańcuchu curry ma dwa parametry.
Podsumowując, częściowo zastosowane funkcje nie różnią się tak naprawdę od funkcji zależnych od funkcjonalności. Można to łatwo zweryfikować, biorąc pod uwagę, że możesz przekonwertować dowolną funkcję na curried:
scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1
scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>
Post Scriptum
Uwaga: powód, dla którego Twój przykład println(filter(nums, modN(2))
działa bez podkreślenia pomodN(2)
wydaje się być to, że kompilator Scala po prostu zakłada, że podkreślenie to jest wygodą dla programisty.
Dodanie: Jak słusznie zauważył @asflierl, Scala nie wydaje się być w stanie wywnioskować typu przy częściowym stosowaniu „normalnych” funkcji:
scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))
Podczas gdy te informacje są dostępne dla funkcji napisanych przy użyciu notacji listy wielu parametrów:
scala> modNCurried(5) _
res3: Int => Boolean = <function1>
Ta odpowiedź pokazuje, jak może to być bardzo przydatne.