Scala Currying gegen teilweise angewendete Funktionen

82

Mir ist klar, dass es hier einige Fragen dazu gibt, was Currying und teilweise angewandte Funktionen sind, aber ich frage, wie sie sich unterscheiden. Als einfaches Beispiel finden Sie hier eine Curry-Funktion zum Finden gerader Zahlen:

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)

Sie könnten also Folgendes schreiben, um dies zu verwenden:

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))

was zurückgibt : List(2,4,6,8). Aber ich habe festgestellt, dass ich das Gleiche auf diese Weise tun kann:

def modN(n: Int, x: Int) = ((x % n) == 0)

val p = modN(2, _: Int)
println(filter(nums, p))

was auch zurückgibt : List(2,4,6,8).

Meine Frage ist also, was ist der Hauptunterschied zwischen den beiden und wann würden Sie einen über den anderen verwenden? Ist dies ein zu einfaches Beispiel, um zu zeigen, warum eines über das andere verwendet wird?

Eric
quelle
Bei teilweise angewandt, der die Kosten eine curried und nicht-curried Version im Speicher darstellen könnten anders sein, so könnte die Laufzeit - Performance zu beeinflussen. (Das heißt, wenn der Optimierer nicht klug genug ist, um in beiden Fällen die optimale Darstellung zu wählen.) Ich bin mit Scala nicht vertraut genug, um die genauen Unterschiede zu sagen.
1
Schauen Sie sich diese an: stackoverflow.com/questions/8063325/…
Utaal
Ich fand diese Erklärung sehr, sehr nützlich, er erklärt über Teilfunktionen, teilweise angewendete Funktionen und Currying, alles in einem Beitrag: stackoverflow.com/a/8650639/1287554
Plasty Grove
Hervorragender Link, @PlastyGrove. Danke dir!
Eric
Und danke @Utaal für deinen Link. Jede Antwort von Martin Odersky selbst wird sehr geschätzt. Ich denke, diese Konzepte beginnen jetzt zu klicken.
Eric

Antworten:

88

Der semantische Unterschied wurde in der Antwort von Plasty Grove ziemlich gut erklärt .

In Bezug auf die Funktionalität scheint es jedoch keinen großen Unterschied zu geben. Schauen wir uns einige Beispiele an, um dies zu überprüfen. Erstens eine normale Funktion:

scala> def modN(n: Int, x: Int): Boolean = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>

Wir bekommen also eine teilweise angewendete <function1>, die eine nimmt Int, weil wir ihr bereits die erste ganze Zahl gegeben haben. So weit, ist es gut. Nun zum Curry:

scala> def modNCurried(n: Int)(x: Int): Boolean = ((x % n) == 0)

Mit dieser Notation würden Sie naiv erwarten, dass Folgendes funktioniert:

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)

Die Notation mit mehreren Parameterlisten scheint also nicht sofort eine Curry-Funktion zu erstellen (vermutlich, um unnötigen Overhead zu vermeiden), sondern wartet darauf, dass Sie explizit angeben, dass Sie sie als Curry wünschen (die Notation hat auch einige andere Vorteile ):

scala> modNCurried(5) _
res24: Int => Boolean = <function1>

Welches ist genau das gleiche, was wir vorher bekommen haben, also kein Unterschied hier, außer der Notation. Ein anderes Beispiel:

scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>

Dies zeigt, wie das teilweise Anwenden einer "normalen" Funktion zu einer Funktion führt, die alle Parameter akzeptiert, während das teilweise Anwenden einer Funktion mit mehreren Parameterlisten eine Funktionskette erzeugt, eine pro Parameterliste , die alle eine neue Funktion zurückgibt:

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.

Wie Sie sehen können, foohat die erste Funktion in der Curry-Kette zwei Parameter , da die erste Parameterliste zwei Parameter enthält.


Zusammenfassend lässt sich sagen, dass teilweise angewendete Funktionen sich in Bezug auf die Funktionalität nicht wirklich von Curry-Funktionen unterscheiden. Dies lässt sich leicht überprüfen, da Sie jede Funktion in eine Curry-Funktion umwandeln können:

scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>

Post Scriptum

Hinweis: Der Grund dafür, dass Ihr Beispiel println(filter(nums, modN(2))ohne den Unterstrich danach funktioniert, modN(2)scheint darin zu liegen, dass der Scala-Compiler diesen Unterstrich einfach als Annehmlichkeit für den Programmierer annimmt.


Ergänzung: Wie @asflierl richtig hervorgehoben hat, scheint Scala nicht in der Lage zu sein, auf den Typ zu schließen, wenn teilweise "normale" Funktionen angewendet werden:

scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

Diese Informationen sind für Funktionen verfügbar, die mit der Notation mehrerer Parameterlisten geschrieben wurden:

scala> modNCurried(5) _
res3: Int => Boolean = <function1>

Diese Antwort zeigt, wie nützlich dies sein kann.

Fresskoma
quelle
Ein wichtiger Unterschied ist, dass die Typinferenz anders funktioniert: gist.github.com/4529020
Vielen Dank, ich habe eine Notiz zu Ihrem Kommentar hinzugefügt :)
Fresskoma
19

Currying hat mit Tupeln zu tun: Verwandeln einer Funktion, die ein Tupelargument verwendet, in eine Funktion, die n separate Argumente akzeptiert, und umgekehrt . Denken Sie daran, dass dies der Schlüssel zur Unterscheidung zwischen Curry und Teilanwendung ist, selbst in Sprachen, die Curry nicht sauber unterstützen.

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

Teilanwendung ist die Fähigkeit, eine Funktion auf einige Argumente anzuwenden, wodurch eine neue Funktion für die verbleibenden Argumente erhalten wird .

Es ist leicht zu merken, wenn Sie nur denken, dass Curry die Transformation ist, die mit Tupeln zu tun hat.

In Sprachen, die standardmäßig verwendet werden (wie Haskell), ist der Unterschied klar: Sie müssen tatsächlich etwas tun, um Argumente in einem Tupel zu übergeben. Die meisten anderen Sprachen, einschließlich Scala, werden standardmäßig ohne Eile verwendet. Alle Argumente werden als Tupel übergeben, sodass Curry / Uncurry weitaus weniger nützlich und weniger offensichtlich ist. Und am Ende denken die Leute sogar, dass teilweises Auftragen und Currying dasselbe sind - nur weil sie Curry-Funktionen nicht einfach darstellen können!

Don Stewart
quelle
Ich stimme vollkommen zu. In Scala-Begriffen ist "Currying" in der ursprünglichen Bedeutung des Wortes der "Transformationsprozess" einer Funktion mit einer Parameterliste in eine Funktion mit mehreren Parameterlisten. In Scala kann diese Transformation mit ".curried" ausgeführt werden. Leider scheint Scala die Bedeutung des Wortes etwas überladen zu haben, da es ursprünglich eher ".curry" statt ".curried" heißen würde.
Fatih Coşkun
2

Multivariable Funktion:

def modN(n: Int, x: Int) = ((x % n) == 0)

Currying (oder die Curry-Funktion):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

Es ist also keine teilweise angewendete Funktion, die mit Curry vergleichbar ist. Es ist die multivariable Funktion. Was mit einer teilweise angewendeten Funktion vergleichbar ist, ist das Aufrufergebnis einer Curry-Funktion, bei der es sich um eine Funktion mit derselben Parameterliste handelt, die die teilweise angewendete Funktion hat.

lcn
quelle
0

Nur um den letzten Punkt zu verdeutlichen

Ergänzung: Wie @asflierl richtig hervorgehoben hat, scheint Scala nicht in der Lage zu sein, auf den Typ zu schließen, wenn teilweise "normale" Funktionen angewendet werden:

Scala kann Typen ableiten, wenn alle Parameter Platzhalter sind, aber nicht, wenn einige von ihnen angegeben sind und einige nicht.

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,_)
              ^
Sud
quelle
0

Die beste Erklärung, die ich bisher finden konnte: https://dzone.com/articles/difference-between-currying-amp-partially-applied

Currying: Zerlegung von Funktionen mit mehreren Argumenten in eine Kette von Einzelargumentfunktionen. Beachten Sie, dass Scala die Übergabe einer Funktion als Argument an eine andere Funktion ermöglicht.

Teilanwendung der Funktion: Übergeben Sie an eine Funktion weniger Argumente als in ihrer Deklaration. Scala löst keine Ausnahme aus, wenn Sie der Funktion weniger Argumente zur Verfügung stellen. Sie wendet sie einfach an und gibt eine neue Funktion mit den restlichen Argumenten zurück, die übergeben werden müssen.

Danylo Zatorsky
quelle