Ich glaube nicht, dass ich das Curry verstehen kann, da ich keinen massiven Nutzen sehen kann, den es bieten könnte. Vielleicht könnte mir jemand anhand eines Beispiels aufklären, warum es so nützlich ist. Hat es tatsächlich Vorteile und Anwendungen oder ist es nur ein überschätztes Konzept?
(Es gibt einen kleinen Unterschied zwischen currying und teilweise Anwendung , obwohl sie eng verwandt sind, da sie oft zusammen gemischt werden, werde ich mit beiden umgehen Begriffe.)
Der Ort, an dem ich die Vorteile zuerst erkannte, war, als ich in Scheiben geschnittene Bediener sah:
%Vor% IMO, das ist total einfach zu lesen. Nun, wenn der Typ (+)
(Int,Int) -> Int
* ist, was die uncurried-Version ist, würde es (konterintuitiv) zu einem Fehler führen - aber curryied, es funktioniert wie erwartet und hat den Typ [Int] -> [Int]
.
Sie haben C # lambdas in einem Kommentar erwähnt. In C # hätten Sie incElems
wie folgt schreiben können, mit einer Funktion plus
:
Wenn du es gewohnt bist, punktefrei zu arbeiten, wirst du feststellen, dass x
hier überflüssig ist. Logischerweise könnte dieser Code auf
was wegen der fehlenden automatischen partiellen Anwendung mit C # lambdas schrecklich ist. Und das ist der entscheidende Punkt, um zu entscheiden, wo Currieren tatsächlich nützlich ist: meistens, wenn es map (+1)
am einfachsten zu lesen, dann kommt .Select(x => plus(1,x))
, und die Version mit curry
sollte wahrscheinlich vermieden werden, wenn es keinen wirklich guten Grund gibt.
Nun, wenn sie lesbar sind, summieren sich die Vorteile zu kürzerem, besser lesbarem und weniger überladenem Code - es sei denn, es gibt einen gewissen Missbrauch von point-free-Stilen (ich liebe% co_de) %, aber es ist ... speziell)
Außerdem würde die Lambda-Analyse ohne Verwendung von Curry-Funktionen unmöglich werden, da sie nur einwertige (aber dafür höherwertige) Funktionen hat.
* Natürlich ist es tatsächlich in (.).(.)
, aber es ist im Moment besser lesbar.
Update: wie currying tatsächlich funktioniert.
Sehen Sie sich den Typ von Num
in C # an:
Sie müssen ein Tuple von Werten geben - nicht in C #, sondern mathematisch gesprochen; Sie können den zweiten Wert nicht einfach weglassen. In Haskell ausgedrückt, das ist
%Vor%welches wie
verwendet werden könnte %Vor%Das sind viel zu viele Zeichen zum tippen. Angenommen, Sie möchten dies in Zukunft öfter tun. Hier ist ein kleiner Helfer:
%Vor%was
ergibt %Vor%Lassen Sie uns dies auf einen konkreten Wert anwenden.
%Vor% Hier können Sie plus
bei der Arbeit sehen. Es verwandelt eine Standard-Haskell-Style-Funktion-Anwendung ( curry
) in einen Aufruf einer "Tupled" -Funktion - oder transformiert auf einer höheren Ebene die "Tupled" - in die "Unplugled" -Version.
Zum Glück müssen Sie sich in den meisten Fällen nicht darum kümmern, da es eine automatische teilweise Anwendung gibt.
Es ist nicht die beste Sache seit geschnittenem Brot, aber wenn Sie sowieso Lambdas verwenden, ist es einfacher, Funktionen höherer Ordnung zu verwenden, ohne Lambda-Syntax zu verwenden. Vergleichen Sie:
%Vor% Diese Art von Konstrukten kommt oft genug vor, wenn Sie funktionale Programmierung verwenden, dass es eine nette Abkürzung ist und Sie von einer etwas höheren Ebene aus über das Problem nachdenken können - Sie ordnen sich dem " max 4
" zu "Funktion, nicht irgendeine Zufallsfunktion, die zufällig als (\i -> max 4 i)
definiert ist. Es lässt dich leichter in höhere Ebenen der Indirektion zu denken:
Das heißt, es ist kein Allheilmittel; manchmal sind die Parameter Ihrer Funktion die falsche Reihenfolge für das, was Sie mit Curry machen wollen, also müssen Sie ohnehin auf ein Lambda zurückgreifen. Sobald Sie sich jedoch an diesen Stil gewöhnt haben, beginnen Sie zu lernen, wie Sie Ihre Funktionen so gestalten, dass sie gut damit arbeiten, und sobald diese Neuronen sich in Ihrem Gehirn verbinden, können früher komplizierte Konstrukte im Vergleich offensichtlich erscheinen.
>Ein Vorteil von currying ist, dass es die teilweise Anwendung von Funktionen ermöglicht, ohne dass eine spezielle Syntax / ein spezieller Operator erforderlich ist. Ein einfaches Beispiel:
%Vor% Die Funktion map
kann wegen des Currys als Typ (a -> b) -> ([a] -> [b])
betrachtet werden. Wenn also length
als erstes Argument angewendet wird, ergibt sich die Funktion mapLength
vom Typ [[a]] -> [Int]
.
Curry hat die in anderen Antworten erwähnten Bequemlichkeitsmerkmale, aber es dient auch oft dazu, das Nachdenken über die Sprache zu vereinfachen oder einen Code viel einfacher zu implementieren, als es sonst sein könnte. Zum Beispiel bedeutet currying, dass jede Funktion einen Typ hat, der mit a ->b
kompatibel ist. Wenn Sie Code schreiben, dessen Typ a -> b
enthält, kann dieser Code mit jeder Funktion arbeiten, egal wie viele Argumente er benötigt.
Das bekannteste Beispiel ist die Klasse Applicative
:
Und ein Beispiel verwenden:
%Vor% In diesem Kontext passen pure
und <*>
jede Funktion vom Typ a -> b
an, um mit Listen vom Typ [a]
zu arbeiten. Aufgrund der teilweisen Anwendung können Sie also Funktionen des Typs a -> b -> c
so anpassen, dass sie mit [a]
und [b]
oder a -> b -> c -> d
mit [a]
, [b]
und [c]
usw. arbeiten / p>
Der Grund dafür ist, dass a -> b -> c
dasselbe ist wie a -> (b -> c)
:
Ein anderer, anderer Einsatz von Curry ist, dass Haskell Ihnen ermöglicht, Typkonstruktoren teilweise anzuwenden. ZB wenn Sie diesen Typ haben:
%Vor% ... es macht tatsächlich Sinn, Foo a
in vielen Kontexten zu schreiben, zum Beispiel:
I.e., Foo
ist ein Konstruktor mit zwei Parametern vom Typ * -> * -> *
; Foo a
, die partielle Anwendung von Foo
auf nur einen Typ, ist ein Typkonstruktor mit der Art * -> *
. Functor
ist eine Typklasse, die nur für Typ-Konstruktoren vom Typ * -> *
instanziiert werden kann. Da Foo a
von dieser Art ist, können Sie eine Functor
-Instanz dafür erstellen.
Die "no-currying" Form der partiellen Anwendung funktioniert so:
f : (A ✕ B) → C
a : A
anwenden.
a
und f
auf (wir bewerten f
vorerst gar nicht) b : B
A
und B
haben, können wir f
in seiner ursprünglichen Form auswerten ... a
von der Schließung und bewerten f(a,b)
. Ein bisschen kompliziert, nicht wahr?
Wenn f
an erster Stelle Curry ist, ist es einfacher:
f : A → B → C
a : A
anwenden - was wir einfach tun können : f a
b : B
f a
auf b
an. So weit, so schön, aber wichtiger als einfach zu sein, gibt uns das auch zusätzliche Möglichkeiten, unsere Funktion zu implementieren: Wir können vielleicht einige Berechnungen durchführen, sobald das Argument a
empfangen wird, und diese Berechnungen haben gewonnen. t muss später ausgeführt werden, auch wenn die Funktion mit mehreren verschiedenen b
Argumenten ausgewertet wird!
Um ein Beispiel zu geben, betrachten Sie dies Audiofilter , ein unendlicher Impulsantwortfilter . Es funktioniert folgendermaßen: Für jedes Audio-Sample geben Sie eine "Akkumulator-Funktion" ( f
) mit einem Zustandsparameter (in diesem Fall eine einfache Zahl, 0 am Anfang) und dem Audio-Sample ein. Die Funktion zaubert dann etwas und spuckt den neuen internen Zustand 1 und den Ausgangs-Abtastwert aus.
Hier ist nun das entscheidende Bit - welche Art von Magie die Funktion bewirkt, hängt vom Koeffizienten 2 λ
ab, der nicht ganz konstant ist: Es kommt darauf an, welche Grenzfrequenz wir gerne hätten der Filter zu haben (dies regelt, "wie der Filter klingen wird") und auf welcher Abtastrate wir verarbeiten. Leider ist die Berechnung von λ
etwas komplizierter ( lp1stCoeff $ 2*pi * (νᵥ ~*% δs)
als der Rest der Magie, wir möchten das nicht für jedes einzelne Sample wiederholen, das ist sehr ärgerlich, denn νᵥ
und δs
sind fast konstant: sie ändern sich sehr selten, sicherlich nicht bei jedem Audio-Beispiel.
Aber Curry rettet den Tag! Wir berechnen einfach λ
, sobald wir die notwendigen Parameter haben. Dann müssen wir bei jedem der vielen folgenden Audio-Samples nur die verbleibende, sehr einfache Magie ausführen: yⱼ = yⱼ₁ + λ ⋅ (xⱼ - yⱼ₁)
. Wir sind also effizient und behalten immer noch eine schöne, rein referentielle transparente, rein funktionale Schnittstelle.
1 Beachten Sie, dass diese Art des State-Passing im Allgemeinen besser mit der State
oder ST
Monade durchgeführt werden kann, was in diesem Beispiel nicht besonders nützlich ist.
2 Ja, das ist ein Lambda-Symbol. Ich hoffe, ich verwechsle niemanden - glücklicherweise ist in Haskell klar, dass Lambda-Funktionen mit \
geschrieben werden, nicht mit λ
.
Es ist etwas zweifelhaft zu fragen, was die Vorteile des Currying sind, ohne den Kontext zu spezifizieren, in dem Sie die Frage stellen:
curry
und uncurry
helfen auch bei bestimmten Annehmlichkeiten Funktionale Programmiersprachen, ich kann mir Pfeile in Haskell als ein konkretes Beispiel dafür vorstellen, wo Sie curry
und uncurry
ein bisschen verwenden würden, um Dinge auf verschiedene Teile eines Pfeils anzuwenden, etc ... Früher dachte ich, dass Currysing einfach Syntaxzucker ist, der ein bisschen Tipparbeit spart. Zum Beispiel, anstatt
zu schreiben %Vor%Ich kann nur schreiben
%Vor%Letzteres ist sofort besser lesbar und weniger zu booten.
Also, wenn es nur eine bequeme Abkürzung ist, warum all das Aufhebens?
Nun, es stellt sich heraus, dass, weil Funktionstypen curiert sind, Sie Code schreiben können, der in der Anzahl der Argumente einer Funktion polymorph ist.
Mit dem QuickCheck
-Framework können Sie beispielsweise Funktionen testen, indem Sie ihnen zufällig generierte Testdaten zuführen. Es funktioniert bei jeder Funktion, deren Eingabetyp automatisch generiert werden kann. Aber wegen der Curry-Erfahrung konnten die Autoren es so einrichten, dass es mit einer beliebigen Anzahl von Argumenten funktioniert. Wären Funktionen nicht curried, gäbe es für jede Anzahl von Argumenten eine andere Testfunktion - und das wäre nur mühsam.
Tags und Links haskell functional-programming currying