ich habe kaum Ahnung von Haskell, mit echt funktionalen Sprachen habe ich mich noch nicht wirklich herumgeschlagen.
Dann freu ich mich, dass es mir schon mal gelungen ist, dich für eine Haskell-Diskussion zu begeistern.
Ich sehe dies: Das Haskell f1 ist etwas ganz anderes als das Perl- (oder JavaScript-) f1. In Haskell wird mit f1 ein Alias in Prefix-Form für die existierende Infix-Funktion "Multipliziere" gebildet, weiter nichts.
Genau richtig.
f3 und f5 werden durch das automatische Currying von Haskell aus f1 abgeleitet; aber dafür hättest Du f1 gar nicht gebraucht, man kann auch Infix-Funktionen direkt zu Curry verarbeiten (falls ich das Haskell-Wiki richtig lese...)
Auch richtig, man hätte f3
und f4
auch so definieren können:
f3 = (3 *)
f5 = (5 *)
Das habe ich an der Stelle nicht gemacht, weil ich versucht habe das originale Beispiel möglichst zu erhalten. Hätte ich überall vereinfacht, wo es ging, dann wäre nur main = print "33 35"
übrig geblieben.
Mit Currying hat das an der Stelle aber nichts zu tun, ich zeige dir gleich an deinem JavaScript-Beispiel auch warum. In Haskell sind alle Funktionen ausnahmslos einstellig, jede Funktion akzeptiert genau einen Parameter. Mehrstellige Funktionen werden über Funktionen ausgedrückt, die Funktionen zurück geben. Currying in Haskell bedeutet, aus einer Funktion, die ein Tupel als einzigen Parameter hat, eine Funktionskette zu machen, die die Tupel-Komponenten nacheinander entgegennimmt. In JavaScript dagegen gibt es echte mehrstellige Funktionen, aber keinen Tupel-Typen. Currying in JavaScript meint deshalb aus einer mehrstelligen Funktion eine Kette von einstelligen Funktionen zu machen. Ein feiner aber wichtiger Unterschied.
Ein besseres JavaScript-Äquivalent zu deiner eleganten Haskell-Curryspeise sähe wohl so aus:
function curry2(f, x) { return y => f(x,y); } let muli = (x,y) => x*y; let f3 = curry2(muli, 3); let f5 = curry2(muli, 5);
Das ist ein hervorragendes Beispiel, und jetzt kommt die versprochene Auflösung, warum mein Haskell Programm nichts mit Currying zu tun hat. Als Vorbereitung lass mich die curry2
-Funktion noch etwas anders aufschreiben, nur damit ich f1
wieder ins Spiel bringen kann, weil ich es später brauchen werde.
let curry2 => f => x => y => f(x,y);
let muli = (x,y) => x*y;
let f1 = curry2(muli);
let f3 = f1(3);
let f5 = f2(5);
Das Haskell-Pendant dazu sieht so aus:
curry2 f x y = f (x,y)
muli (x,y) = x * y
f1 = curry2 muli
f2 = f1 3
f3 = f1 5
Die muli
Funktion ist ähnlich zur Standard-Muliplikation, allerdings bekommt sie ihre Operanden nich nacheinander übergeben, sondern zusammengefasst in einem Tupel. Das klingt fast wie Currying, nur andersrum. Die Operation, kann man ebenso herausfaktorisieren, wie du es eben mit der Curry-Funktion gemacht hast. Dieses Andersrum-Curry trägt den passenden Namen uncurry
.
curry2 f x y = f (x,y)
uncurry2 f (x,y) = f x y
muli = uncurry2 (*)
f1 = curry2 muli
Wenn man jetzt die rechte Seite von muli
in die Definition von f1
einsetzt, dann kommt da folgendes raus:
f1 = curry2 (uncurry2 (*))
curry2
ist die Umkehrfunktion von uncurry2
, deswegen kann man sich das auch direkt sparen, und landet wieder bei
f1 = (*)
badum tsss
Möglicherweise lässt sich das auch in Perl formulieren; ich weiß nicht, ob in Perl subs als Parameter übergeben werden können.
Der Vergleich ist aber auf jeden Fall unfair; natürlich ist Currying in einer funktionalen Sprache viel eleganter als in Sprachen, die dafür keinen fertigen Mechanismus mitbringen.
Ich hoffe ich konnte demonstrieren, dass Currying hier in Haskell keinen Vorteil bringt, weil es nicht benutzt wird. Das Handicap von JavaScript ist in diesem Fall, dass der Multiplikations-Operator keine richtige Funktion ist, sondern ein Sprachkonstrukt.