Welche Vorteile hat Curry?

8

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?

    
user997112 13.09.2012, 19:32
quelle

7 Antworten

12

(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 :

%Vor%

Wenn du es gewohnt bist, punktefrei zu arbeiten, wirst du feststellen, dass x hier überflüssig ist. Logischerweise könnte dieser Code auf

reduziert werden %Vor%

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 implizit passiert . Für mich ist 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:

%Vor%

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.

    
phg 13.09.2012, 20:30
quelle
7

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:

%Vor%

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.

>     
Dax Fohl 13.09.2012 19:53
quelle
4

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] .

    
Abhinav Sarkar 13.09.2012 19:43
quelle
3

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 :

%Vor%

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) :

%Vor%

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:

%Vor%

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.

    
Luis Casillas 13.09.2012 21:17
quelle
3

Die "no-currying" Form der partiellen Anwendung funktioniert so:

  • Wir haben eine Funktion f : (A ✕ B) → C
  • Wir möchten es teilweise auf einige a : A anwenden.
  • Um dies zu tun, bauen wir eine Schließung von a und f auf (wir bewerten f vorerst gar nicht)
  • Dann erhalten wir einige Zeit später das zweite Argument b : B
  • Nun, da wir beide das Argument A und B haben, können wir f in seiner ursprünglichen Form auswerten ...
  • Also erinnern wir uns an 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:

  • Wir haben eine Funktion f : A → B → C
  • Wir möchten es teilweise auf einige a : A anwenden - was wir einfach tun können : f a
  • Dann erhalten wir einige Zeit später das zweite Argument b : B
  • Wir wenden das bereits ausgewertete 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 λ .

    
leftaroundabout 13.09.2012 23:44
quelle
2

Es ist etwas zweifelhaft zu fragen, was die Vorteile des Currying sind, ohne den Kontext zu spezifizieren, in dem Sie die Frage stellen:

  • In einigen Fällen, wie bei funktionalen Sprachen, wird das Currying lediglich als etwas angesehen, das eine lokale Änderung aufweist, bei der Sie Dinge durch explizite Tupel-Domains ersetzen können. Dies bedeutet jedoch nicht, dass das Curry in diesen Sprachen nutzlos ist. In gewissem Sinne lässt dich das Programmieren mit Curry-Funktionen "fühlen", als ob du in einem funktionaleren Stil programmierst, weil du typischerweise mit Situationen konfrontiert bist, in denen du mit Funktionen höherer Ordnung zu tun hast. Sicherlich werden Sie in der Regel alle Argumente einer Funktion "ausfüllen", aber in den Fällen, in denen Sie die Funktion in ihrer teilweise angewandten Form verwenden möchten, ist dies in Curry-Form etwas einfacher. Normalerweise sagen wir unseren Programmieranfängern, dass sie diese beim Erlernen einer funktionalen Sprache verwenden sollen, nur weil sie sich besser anfühlt und sie daran erinnert, dass sie mehr programmieren als nur C. Dinge wie 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 ...
  • In einigen Fällen möchten Sie über mehr als funktionale Programme nachdenken. Sie können currying / uncurrying als Möglichkeit vorstellen, die Eliminierungs- und Einleitungsregeln für und in konstruktiver Logik anzugeben, die eine Verbindung zu einer eleganteren Motivation für den Grund bieten es existiert.
  • In einigen Fällen, z. B. in Coq, kann die Verwendung von Curry-Funktionen im Vergleich zu Tupel-Funktionen zu unterschiedlichen Induktionsschemata führen, die je nach Anwendung einfacher oder schwieriger zu handhaben sind.
Kristopher Micinski 13.09.2012 20:31
quelle
1

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.

    
MathematicalOrchid 14.09.2012 16:15
quelle