Was ist das Besondere am Curry oder an der teilweisen Anwendung?

9

Ich habe jeden Tag Artikel über funktionale Programmierung gelesen und versucht, einige Praktiken so weit wie möglich anzuwenden. Aber ich verstehe nicht, was beim Curry oder bei der teilweisen Anwendung einzigartig ist.

Nehmen Sie diesen Groovy-Code als Beispiel:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

Ich verstehe nicht, was der Unterschied zwischen tripler1und ist tripler2. Sind sie nicht beide gleich? Das 'Currying' wird in reinen oder teilweise funktionalen Sprachen wie Groovy, Scala, Haskell usw. unterstützt. Aber ich kann das Gleiche tun (Links-Curry, Rechts-Curry, N-Curry oder Teilanwendung), indem ich einfach eine andere benannte oder anonyme Sprache erstelle Funktion oder Schließung, die die Parameter in den tripler2meisten Sprachen (sogar C) an die ursprüngliche Funktion (wie ) weiterleitet .)

Vermisse ich hier etwas? Es gibt Stellen, an denen ich Curry und Teilanwendung in meiner Grails-Anwendung verwenden kann, aber ich zögere, dies zu tun, weil ich mich frage: "Wie ist das anders?"

Bitte erleuchte mich.

EDIT: Wollen Sie damit sagen, dass eine teilweise Anwendung / Currying einfach effizienter ist als das Erstellen / Aufrufen einer anderen Funktion, die Standardparameter an die ursprüngliche Funktion weiterleitet?

Vigneshwaran
quelle
1
Kann jemand bitte die Tags "Curry" oder "Currying" erstellen?
Vigneshwaran
Wie curryst du in C?
Giorgio
dies ist wahrscheinlich wirklich mehr über partielle Anwendung programmers.stackexchange.com/questions/152868/...
jk.
1
@Vigneshwaran: AFAIK Sie müssen keine weitere Funktion in einer Sprache erstellen, die das Currying unterstützt. In Haskell f x y = x + ybedeutet dies beispielsweise, dass feine Funktion einen int-Parameter akzeptiert. Das Ergebnis von f x( fangewendet auf x) ist eine Funktion, die einen int-Parameter akzeptiert. Das Ergebnis f x y(oder (f x) y, dh f xangewendet auf y) ist ein Ausdruck, der keine Eingabeparameter akzeptiert und durch Reduzieren ausgewertet wird x + y.
Giorgio
1
Sie können die gleichen Dinge erreichen, aber der Aufwand, den Sie mit C ausführen, ist viel schmerzhafter und nicht so effizient wie in einer Sprache wie Haskell, in der dies das Standardverhalten ist
Daniel Gratzer,

Antworten:

8

Beim Currying geht es darum, eine Funktion zu drehen / darzustellen, die n Eingaben in n Funktionen umwandelt, die jeweils 1 Eingabe annehmen. Bei einer Teilanwendung geht es darum, einige der Eingaben für eine Funktion zu korrigieren.

Die Motivation für eine teilweise Anwendung besteht hauptsächlich darin, dass es einfacher ist, Funktionsbibliotheken höherer Ordnung zu schreiben. Zum Beispiel nehmen die Algorithmen in C ++ STL alle weitgehend Prädikate oder unäre Funktionen an. Mit bind1st kann der Bibliotheksbenutzer nicht unäre Funktionen mit einem gebundenen Wert einbinden . Der Bibliotheksschreiber muss daher nicht überladene Funktionen für alle Algorithmen bereitstellen, die unäre Funktionen zur Bereitstellung von Binärversionen verwenden

Currying selbst ist nützlich, da es Ihnen eine teilweise Anwendung bietet, wo immer Sie es kostenlos möchten, dh Sie benötigen keine Funktion mehr bind1st, die teilweise angewendet werden soll.

jk.
quelle
ist curryingetwas spezifisch für groovig oder sprachübergreifend anwendbar?
Amphibient
@foampile ist etwas, das sprachübergreifend anwendbar ist, aber ironischerweise macht grooviges Curry es nicht wirklich programmers.stackexchange.com/questions/152868/…
jk.
@jk. Wollen Sie damit sagen, dass Currying / Teilanwendung effizienter ist als das Erstellen und Aufrufen einer anderen Funktion?
Vigneshwaran
2
@Vigneshwaran - es ist nicht unbedingt performanter, aber es ist definitiv effizienter in Bezug auf die Zeit des Programmierers. Beachten Sie auch, dass Currying zwar von vielen funktionalen Sprachen unterstützt wird, in OO- oder prozeduralen Sprachen jedoch im Allgemeinen nicht unterstützt wird. (Oder zumindest nicht durch die Sprache selbst.)
Stephen C
6

Aber ich kann das Gleiche tun (linkes Curry, rechtes Curry, n-Curry oder Teilanwendung), indem ich einfach eine andere benannte oder anonyme Funktion oder einen Abschluss erstelle, der die Parameter in den meisten Sprachen an die ursprüngliche Funktion (wie tripler2) weiterleitet (wie tripler2). sogar C.)

Und der Optimierer wird sich das ansehen und umgehend zu etwas übergehen, das er verstehen kann. Currying ist ein netter kleiner Trick für den Endbenutzer, hat aber vom Standpunkt des Sprachdesigns aus viel bessere Vorteile. Es ist wirklich schön, alle Methoden als unär zu behandeln, A -> Bwo Bes eine andere Methode geben kann.

Es vereinfacht, welche Methoden Sie schreiben müssen, um Funktionen höherer Ordnung zu verarbeiten. Ihre statische Analyse und Optimierung in der Sprache hat nur einen Pfad, mit dem Sie arbeiten können, der sich auf bekannte Weise verhält. Die Parameterbindung fällt nur aus dem Entwurf heraus, anstatt dass Reifen dieses allgemeine Verhalten ausführen müssen.

Telastyn
quelle
6

Als @jk. Curry kann dazu beitragen, den Code allgemeiner zu gestalten.

Angenommen, Sie hatten diese drei Funktionen (in Haskell):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

Die Funktion fnimmt hier zwei Funktionen als Argumente an, übergibt sie 1an die erste Funktion und übergibt das Ergebnis des ersten Aufrufs an die zweite Funktion.

Wenn wir fusing qund rals Argumente aufrufen würden, wäre dies effektiv:

> r (q 1)

wo qwürde auf eine 1andere Funktion angewendet werden und diese zurückgeben (wie qes Curry ist); Diese zurückgegebene Funktion würde dann rals Argument übergeben, um ein Argument von zu erhalten 3. Das Ergebnis wäre ein Wert von 9.

Nehmen wir an, wir hatten zwei weitere Funktionen:

> let s a = 3 * a

> let t a = 4 + a

wir könnten diese fauch weitergeben und einen Wert von 7oder erhalten 15, abhängig davon, ob unsere Argumente s toder waren t s. Da diese Funktionen beide eher einen Wert als eine Funktion zurückgeben, würde in f s toder keine Teilanwendung stattfinden f t s.

Wenn wir fmit qund rim Hinterkopf geschrieben hätten, hätten wir möglicherweise ein Lambda (anonyme Funktion) anstelle einer Teilanwendung verwendet, z.

> let f' a b = b (\x -> a 1 x)

aber dies hätte die Allgemeinheit von eingeschränkt f'. fkann mit Argumenten qund roder sund aufgerufen werden t, f'kann aber nur mit qund r- aufgerufen werden f' s tund f' t sbeide führen zu einem Fehler.

MEHR

Wenn f'mit einem q'/ r'pair aufgerufen würde, bei dem q'mehr als zwei Argumente verwendet wurden, q'würde das immer noch teilweise angewendet f'.

Alternativ könnten Sie qaußen fstatt innen einwickeln , aber das würde Sie mit einem bösen verschachtelten Lambda zurücklassen:

f (\x -> (\y -> q x y)) r

Das ist im Wesentlichen das, was der Curry überhaupt qwar!

paul
quelle
Du hast meine Augen geöffnet. Durch Ihre Antwort wurde mir klar, wie sich Curry- / teilweise angewendete Funktionen von der Erstellung einer neuen Funktion unterscheiden, die Argumente an die ursprüngliche Funktion übergibt. 1. Das Weitergeben von Curry- / Paed-Funktionen (wie f (q.curry (2)) ist ordentlicher als das unnötige Erstellen separater Funktionen für nur eine vorübergehende Verwendung (in funktionalen Sprachen wie groovig)
Vigneshwaran
2. In meiner Frage sagte ich: "Ich kann dasselbe in C tun." Ja, aber in nicht funktionierenden Sprachen, in denen Sie Funktionen nicht als Daten weitergeben können, hat das Erstellen einer separaten Funktion, die Parameter an das Original weiterleitet, nicht alle Vorteile von Curry / Pa
Vigneshwaran
Mir ist aufgefallen, dass Groovy die von Haskell unterstützte Generalisierung nicht unterstützt. Ich musste schreiben def f = { a, b -> b a.curry(1) }, um f q, rarbeiten zu können und def f = { a, b -> b a(1) }oder def f = { a, b -> b a.curry(1)() }um f s, tzu arbeiten. Sie müssen alle Parameter übergeben oder explizit sagen, dass Sie Curry spielen. :(
Vigneshwaran
2
@ Vigneshwaran: Ja, man kann mit Sicherheit sagen, dass Haskell und Curry sehr gut zusammenpassen . ;] Beachten Sie, dass in Haskell Funktionen standardmäßig (in der richtigen Definition) ausgeführt werden und Leerzeichen die Funktionsanwendung anzeigen, f x ydh was viele Sprachen schreiben würden f(x)(y), nicht f(x, y). Vielleicht würde Ihr Code in Groovy funktionieren, wenn Sie qso schreiben , dass er voraussichtlich so aufgerufen wird q(1)(2)?
CA McCann
1
@ Vigneshwaran Ich bin froh, dass ich helfen konnte! Ich habe das Gefühl, dass Sie ausdrücklich sagen müssen, dass Sie eine Teilanwendung durchführen. In Clojure muss ich tun (partial f a b ...)- da ich an Haskell gewöhnt bin, vermisse ich das richtige Curry sehr, wenn ich in anderen Sprachen programmiere (obwohl ich kürzlich in F # gearbeitet habe, was es zum Glück unterstützt).
Paul
3

Es gibt zwei wichtige Punkte bei der Teilanwendung. Die erste ist syntaktisch / bequem - einige Definitionen werden einfacher und kürzer zu lesen und zu schreiben, wie @jk erwähnt. (Schauen Sie sich die Pointfree-Programmierung an, um mehr darüber zu erfahren, wie großartig das ist!)

Die zweite, wie @telastyn erwähnt, handelt von einem Funktionsmodell und ist nicht nur praktisch. In der Haskell-Version, aus der ich meine Beispiele erhalte, weil ich mit anderen Sprachen mit teilweiser Anwendung nicht vertraut bin, verwenden alle Funktionen ein einziges Argument. Ja, funktioniert sogar wie:

(:) :: a -> [a] -> [a]

nimm ein einziges Argument; Aufgrund der Assoziativität des Funktionstypkonstruktors ->entspricht das Obige:

(:) :: a -> ([a] -> [a])

Dies ist eine Funktion, die eine Funktion übernimmt aund zurückgibt [a] -> [a].

Dies ermöglicht es uns, Funktionen zu schreiben wie:

($) :: (a -> b) -> a -> b

Dies kann jede Funktion auf ein Argument des entsprechenden Typs anwenden . Sogar verrückte wie:

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

Okay, das war ein erfundenes Beispiel. Eine nützlichere ist jedoch die Typklasse Applicative , die diese Methode enthält:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Wie Sie sehen können, ist der Typ identisch, ähnlich wie $wenn Sie das Applicative fBit entfernen , und tatsächlich beschreibt diese Klasse die Funktionsanwendung in einem Kontext. Also anstelle der normalen Funktionsanwendung:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

Wir können Funktionen in einem anwendbaren Kontext anwenden. Zum Beispiel im Kontext Vielleicht, in dem etwas vorhanden sein oder fehlen kann:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

Der wirklich coole Teil ist nun, dass die Applicative-Typklasse nichts über Funktionen von mehr als einem Argument erwähnt - dennoch kann sie damit umgehen, sogar Funktionen von 6 Argumenten wie f:

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

Soweit ich weiß, wäre die anwendbare Typklasse in ihrer allgemeinen Form ohne eine Vorstellung von einer teilweisen Anwendung nicht möglich. (Um jegliche Programmier Experten da draußen - bitte korrigieren Sie mich , wenn ich falsch!) Natürlich, wenn Sie die Sprache teilweise Anwendung fehlt, man könnte es in in irgendeiner Form bauen, aber ... es ist einfach nicht das gleiche, es ist ? :) :)


quelle
1
Applicativeohne Curry oder teilweise Anwendung würde verwenden fzip :: (f a, f b) -> f (a, b). In einer Sprache mit Funktionen höherer Ordnung können Sie Currying und teilweise Anwendung in den Kontext des Funktors heben und sind gleichbedeutend mit (<*>). Ohne Funktionen höherer Ordnung haben Sie keine, fmapso dass das Ganze nutzlos wäre.
CA McCann
@CAMcCann danke für das Feedback! Ich wusste, dass ich mit dieser Antwort überfordert war. Ist das, was ich gesagt habe, falsch?
1
Es ist sicherlich im Geiste richtig. Das Aufteilen von Haaren über die Definitionen von "allgemeiner Form", "möglich" und eine "Konzeption der Teilanwendung" ändert nichts an der einfachen Tatsache, dass der charmante f <$> x <*> yidiomatische Stil leicht funktioniert, da Currying und Teilanwendung leicht funktionieren. Mit anderen Worten, was angenehm ist, ist wichtiger als das, was hier möglich ist.
CA McCann
Jedes Mal, wenn ich Codebeispiele für funktionale Programmierung sehe, bin ich mehr davon überzeugt, dass es ein aufwändiger Witz ist und dass er nicht existiert.
Kieveli
1
@ Kieveli es ist bedauerlich, dass du so denkst. Es gibt viele gute Tutorials , die Sie mit einem guten Verständnis der Grundlagen zum Laufen bringen.