Zdaję sobie sprawę, że jest tu kilka pytań o to, czym są funkcje curry i częściowo stosowane, ale pytam, czym się różnią. Jako prosty przykład, oto funkcja curry do znajdowania liczb parzystych:
def filter(xs: List[Int], p: Int => Boolean): List[Int] =
if (xs.isEmpty) xs
else if (p(xs.head)) xs.head :: filter(xs.tail, p)
else filter(xs.tail, p)
def modN(n: Int)(x: Int) = ((x % n) == 0)
Możesz więc napisać co następuje:
val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))
który powraca: List(2,4,6,8)
. Ale odkryłem, że mogę zrobić to samo w ten sposób:
def modN(n: Int, x: Int) = ((x % n) == 0)
val p = modN(2, _: Int)
println(filter(nums, p))
co również zwraca: List(2,4,6,8)
.
Więc moje pytanie brzmi: jaka jest główna różnica między nimi i kiedy użyjesz jednego nad drugim? Czy to po prostu zbyt uproszczony przykład, aby pokazać, dlaczego jeden miałby być użyty nad drugim?
Odpowiedzi:
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 anInt
, 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.
źródło
Currying ma do czynienia z krotkami: zamienianiem funkcji, która przyjmuje argument krotki, w taką, która przyjmuje n oddzielnych argumentów i odwrotnie . Pamiętanie o tym jest kluczem do odróżnienia curry od częściowego zastosowania, nawet w językach, które nie obsługują w pełni curry.
Zastosowanie częściowe to możliwość zastosowania funkcji do niektórych argumentów, dając nową funkcję dla pozostałych argumentów .
Łatwo to zapamiętać, jeśli po prostu pomyślisz, że curry jest transformacją związaną z krotkami.
W językach, które są domyślnie curried (takich jak Haskell) różnica jest oczywista - musisz coś zrobić, aby przekazać argumenty w krotce. Ale większość innych języków, w tym Scala, jest domyślnie nieuruszana - wszystkie argumenty są przekazywane jako krotki, więc curry / uncurry jest znacznie mniej użyteczne i mniej oczywiste. A ludzie nawet myślą, że częściowa aplikacja i curry to to samo - tylko dlatego, że nie mogą łatwo przedstawić funkcji curry!
źródło
Funkcja wielu zmiennych:
def modN(n: Int, x: Int) = ((x % n) == 0)
Curry (lub funkcja curry):
def modNCurried(n: Int)(x: Int) = ((x % n) == 0)
Nie jest to więc funkcja stosowana częściowo, porównywalna z curry. To funkcja wielu zmiennych. To, co można porównać do funkcji częściowo zastosowanej, to wynik wywołania funkcji bieżącej, która jest funkcją z taką samą listą parametrów, jaką ma funkcja zastosowana częściowo.
źródło
Żeby wyjaśnić ostatni punkt
Scala może wywnioskować typy, jeśli wszystkie parametry są symbolami wieloznacznymi, ale nie wtedy, gdy niektóre z nich są określone, a niektóre nie.
scala> modN(_,_) res38: (Int, Int) => Boolean = <function2> scala> modN(1,_) <console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1)) modN(1,_) ^
źródło
Najlepsze wyjaśnienie, jakie udało mi się do tej pory znaleźć: https://dzone.com/articles/difference-between-currying-amp-partually-applied
źródło