Currying (vor allem in der Linguistik auch Schönfinkeln) ist die Umwandlung einer Funktion mit mehreren Argumenten in eine Sequenz von Funktionen mit jeweils einem Argument. Obwohl das Verfahren von Moses Schönfinkel[1] erfunden und von Gottlob Frege[2] vorausgedacht wurde, wird es oft nach Haskell Brooks Curry benannt, der das Verfahren letztlich umfangreich theoretisch ausgearbeitet hat.[3]

Verfahren

Bearbeiten

Es sei eine Funktion gegeben, die n Argumente erfordert. Wird diese auf ein Argument angewendet, so konsumiert sie nur genau dieses und liefert als Funktionswert eine weitere Funktion, die noch   Argumente verlangt. Die zurückgegebene Funktion wird anschließend auf alle weiteren Argumente angewendet.

In Typen ausgedrückt, handelt es sich um die Umrechnung einer Funktion   zu einer modifizierten Funktion  .[4]

Eine alternative Schreibweise ist  , wobei   als ein Operator verstanden wird, der  -stellige Abbildungen (für  ) modifiziert zu einer einstelligen Abbildung, deren Bildwerte einstellige Abbildungen sind usw., wobei der Reihe nach alle Argumente der ursprünglichen Abbildung durchlaufen werden; formal:

 .

Das Konzept des Currying ist verwandt, aber (für  ) verschieden, von dem der partiellen Anwendung wie etwa:

 ,
 .

Im einstelligen Fall ( ) hat Currying keine Auswirkung:

 ,

im zweistelligen Fall ( ) besteht der Zusammenhang

 , d. h. für alle  :
 ,

im dreistelligen Fall ( ) gilt für  :

 , und mit zusätzlich  :
 , d. h.  ,

allgemein:

 ,
 .[5]

Im Allgemeinen kommt es häufig vor, dass eine mehrstellige Abbildung nicht für alle Wertekombinationen aus den Trägermengen   definiert ist, also nur auf einer Teilmenge des kartesischen Produkts  , d. h. auf (dem Graph einer) Relation.[6] Man kann diese Situation behandeln, in dem man entweder grundsätzlich partielle Abbildungen zulässt und die obigen Definitionen formal übernimmt. Hält man dagegen am Konzept totaler Abbildungen fest, werden die Definitionsbereiche der beteiligten Abbildungen von der Wahl bereits festgelegter Parameter abhängig, wie das Beispiel zweistelliger Abbildungen zeigt:

 ;

der Definitionsbereich dieser Abbildung ist von   abhängig:

 ,

also die Urbildfaser von   bezüglich des als Relation aufgefassten Definitionsbereichs  .[7]

Da für n=1 gilt  , kann für   dasselbe Symbol verwendet werden wie für  . Man nennt dies Überladung (siehe auch Signatur (Modelltheorie) §Anmerkungen).[8]

Beispiele

Bearbeiten

Lambda-Notation

Bearbeiten

Ein Beispiel in Lambda-Notation soll das Verfahren verdeutlichen, wobei die Funktion konkret folgendermaßen definiert sei:

 

Die Funktion verlangt also 3 Argumente und gibt diese zurück. Die Definition ist äquivalent zu:

 

Bei der Anwendung der Funktion auf die Argumente a, b und c geschieht Folgendes:

 

 

 

 

Nach erstmaliger Anwendung der Funktion auf a, b und c wird x im Funktionskörper durch das erste Argument a ersetzt. Das Resultat ist eine Funktion, die noch die Argumente y und z verlangt. Diese wird sofort auf b und c angewendet.

Überladung

Bearbeiten

Angenommen, wir haben eine zweistellige Multiplikationsfunktion  , die zwei ganze Zahlen multipliziert, d. h. einem Paar ganzer Zahlen ihr Produkt zuordnet:

  mit   [9]

Per Definition wird diese Funktion auf zwei natürliche Zahlen angewendet und eine natürliche Zahl zurückgegeben, beispielsweise  .

Wird nun zunächst das erste Argument besetzt (etwa mit  ), das zweite noch freigelassen, entsteht eine einstellige ‚höhere Funktion’, die selbst ein (weiteres) Argument aufnimmt und das Produkt mit diesem festen Parameter (der Nummer  ) zurückgibt:

   

und für ein beliebiges festes erstes Argument  :

    .

Currying bedeutet nun bei einer n-stelligen Funktion, diesen Vorgang solange durchzuführen, bis nur eine noch einstellige höhere Funktion übrigbleibt. Für n=2 ist daher:

  [10]

Da die Bezeichnung   als einstellige Funktion noch unbesetzt ist, kann diese überladen werden,[11] d. h. im einstelligen Fall wird   als   verstanden:

 .

Geometrisches Beispiel

Bearbeiten
 
f(x, y) = x^2 + x y + y^2

Man kann sich die Situation für eine Funktion mit zwei Argumenten   wie folgt vorstellen: das Fixieren einer Argumentvariable entspricht einer Einschränkung der zweidimensionalen Definitionsmenge auf eine eindimensionale Teilmenge, z. B.  , die resultierende Funktion entspricht der Schnittkurve des Graphen von   mit der Ebene aller Punkte  . Alle Punkte   des Graphen können somit auch durch eine zweistufige Auswahl erreicht werden: zunächst durch die Festlegung der Schnittebene   und dann durch die Auswertung der Schnittkurve   an der Stelle  .

Anwendung

Bearbeiten

Currying wird überwiegend in Programmiersprachen und Kalkülen verwendet, in denen Funktionen nur ein einzelnes Argument erhalten dürfen. Dazu gehören beispielsweise ML, Unlambda und der Lambda-Kalkül sowie das nach Curry benannte Haskell. Viele dieser Sprachen bieten dem Programmierer allerdings syntaktische Möglichkeiten, dies zu verschleiern. Ein Beispiel hierfür ist die Äquivalenz der Funktionsdefinition im oben gezeigten Beispiel.

Das nachfolgende Beispiel zeigt Currying in JavaScript. Zunächst wird eine Funktion uncurriedAdd definiert, die als Ergebnis die Summe der beiden Argumente hat. Andererseits wird eine Funktion curriedAdd definiert, die eine als Closure definierte Funktion zurückgibt, welche partiell angewendet werden kann.

function uncurriedAdd(x, y) {
    return x + y;
}

function curriedAdd(x) {
    return function(y) {
        return x + y;
    };
}

console.log(uncurriedAdd(3, 5)); // 8
console.log(curriedAdd(3)(5)); // 8

const addThree = curriedAdd(3);
console.log(addThree(5)); // 8
console.log(addThree(12)); // 15

Durch Currying wird die Funktion partiell angewendet, wobei die Funktionsargumente nacheinander übergeben werden und zwischenzeitlich in einer neuen Funktion gehalten werden, die beliebig weiterverwendet werden kann.

Die Funktionen können in JavaScript mit anonymen Funktionen auch kürzer definiert werden:

const uncurriedAdd = (x, y) => x + y;
const curriedAdd = x => y => x + y;
import java.util.function.*; 

class Main {
    static IntBinaryOperator uncurriedAdd = (x, y) -> x + y;
    static IntFunction<IntUnaryOperator> curriedAdd = x -> y -> x + y;

    public static void main(String[] args) {
        System.out.println(uncurriedAdd.applyAsInt(3, 5)); // 8
        System.out.println(curriedAdd.apply(3).applyAsInt(5)); // 8

        var addThree = curriedAdd.apply(3);
        System.out.println(addThree.applyAsInt(5)); // 8
        System.out.println(addThree.applyAsInt(12)); // 15
    }
}
val uncurriedAdd = { x: Int, y: Int -> x + y }
val curriedAdd = { x: Int -> { y: Int -> x + y } }

println(uncurriedAdd(3, 5)) // 8
println(curriedAdd(3)(5)) // 8

val addThree = curriedAdd(3)
println(addThree(5)) // 8
println(addThree(12)) // 15

C++ führte den Lambda-Kalkül mit anonymen Funktionen in die Sprache ein. Mit dem Schlüsselwort auto wird durch Typinferenz der Datentyp implizit abgeleitet, ohne den Datentypen explizit deklarieren zu müssen. Dadurch ergibt sich eine kürzere Schreibweise für die Currifizierung:

#include <iostream>

int uncurriedAdd(int x, int y) {
    return x + y;
}

auto curriedAdd(int x) {
    return [x](int y) {
        return x + y;
    };
}

int main() {
    using namespace std;
    cout << uncurriedAdd(3, 5) << endl; // 8
    cout << curriedAdd(3)(5) << endl; // 8

    auto addThree = curriedAdd(3);
    cout << addThree(5) << endl; // 8
    cout << addThree(12) << endl; // 15
}

Man beachte, dass die Erwähnung des Typs int im Argument des Lambda-Ausdrucks auch durch auto ersetzt werden kann, wodurch die Implementierung verallgemeinert wird. Die Parameter der benannten Funktion curried_add kann jedoch nur durch Templates für verschiedene Datentypen verallgemeinert werden.

Da es in der Programmiersprache C keine anonymen Funktionen gibt, wird eine benannte Funktion add separat definiert, die von curriedAdd zurückgegeben wird. Der Wert von x wird in der globalen Variablen z gespeichert, da die Funktion add nicht auf die Continuation der Funktion curriedAdd zugreifen kann.

#include <stdio.h>

int uncurriedAdd(int x, int y) {
    return x + y;
}

int z;

int add(int y) {
    return y + z;
}

typedef int function(int);

function* curriedAdd(int x) {
    z = x;
    return add;
}

int main() {
    printf("%d\n", uncurriedAdd(3, 5)); // 8
    printf("%d\n", curriedAdd(3)(5)); // 8

    function* addThree = curriedAdd(3);
    printf("%d\n", addThree(5)); // 8
    printf("%d\n", addThree(12)); // 15
}
using System;

class Program {
    static Func<int, int, int> uncurriedAdd = (x, y) => x + y;
    static Func<int, Func<int, int>> curriedAdd = x => y => x + y;

    public static void Main() {
        Console.WriteLine(uncurriedAdd(3, 5)); // 8
        Console.WriteLine(curriedAdd(3)(5)); // 8

        var addThree = curriedAdd(3);
        Console.WriteLine(addThree(5)); // 8
        Console.WriteLine(addThree(12)); // 15
    }
}
let uncurriedAdd (x, y) = x + y
let curriedAdd x y = x + y

printfn "%i" (uncurriedAdd (3, 5)) // 8
printfn "%i" ((curriedAdd 3) 5) // 8
printfn "%i" (curriedAdd 3 5) // 8

let addThree = curriedAdd 3
printfn "%i" (addThree 5) // 8
printfn "%i" (addThree 12) // 15

In der Programmiersprache Haskell kann eine Funktion genau einen oder keinen Parameter haben. Wenn man eine Funktion mit mehreren Argumenten aufrufen möchte, müssen die Argumente zuerst in einem neuen Objekt zusammengesetzt werden, sodass die Funktion nur ein Argument erhält. Dafür können die Parameter in runden Klammern mit Kommata getrennt aufgelistet werden, um ein Tupel zu definieren.

uncurriedAdd (x, y) = x + y

Eine Alternative dazu ist das Currying. In der expliziten Schreibweise definiert man für jeden Wert im Tupel jeweils eine Funktion mit nur einem Argument. Solange noch nicht alle Argumente übergeben wurden, wird eine Funktion zurückgegeben, die ein Teilergebnis liefert.

curriedAdd x = \y -> x + y

Da die Schreibweise mit mehreren Lambda-Funktionen umständlich sein kann, gibt es eine syntaktisch gezuckerte Notation, die semantisch äquivalent ist. Schreibt man die Argumente ohne runde Klammern hintereinander, erhält man automatisch eine currifizierte Funktion.

curriedAdd x y = x + y

Die currifizierte Funktion kann sowohl explizit, als auch implizit partiell angewendet werden.

main = do
    print (uncurriedAdd (3, 5)) -- 8
    print ((curriedAdd 3) 5) -- 8
    print (curriedAdd 3 5) -- 8

    let addThree = curriedAdd 3
    print (addThree 5) -- 8
    print (addThree 12) -- 15

Zudem gibt es in Haskell die Möglichkeit eine Funktion im Nachhinein zwischen currifizierter und nicht currifizierter Definition umzuwandeln. Dafür werden die Funktionen curry und uncurry verwendet.

main = do
    let uncurried = uncurry curriedAdd
    print (uncurried (3, 5)) -- 8
    
    let curried = curry uncurriedAdd
    print ((curried 3) 5) -- 8
    print (curried 3 5) -- 8

    let addThree = curried 3
    print (addThree 5) -- 8
    print (addThree 12) -- 15
def uncurriedAdd(x, y):
    return x + y

def curriedAdd(x):
    return lambda y: x + y

print(uncurriedAdd(3, 5)) # 8
print(curriedAdd(3)(5)) # 8

addThree = curriedAdd(3)
print(addThree(5)) # 8
print(addThree(12)) # 15
def uncurriedAdd(x, y)
    return x + y
end

def curriedAdd(x)
    return lambda { |y| x + y }
end

puts uncurriedAdd(3, 5) # 8
puts curriedAdd(3).call(5) # 8

addThree = curriedAdd(3)
puts addThree.call(5) # 8
puts addThree.call(12) # 15

Es gibt in der Programmiersprache Ruby zusätzlich die Möglichkeit, ein Funktionsobjekt erst beim Funktionsaufruf zu currifizieren.

uncurriedAdd = lambda { |x, y| x + y }
puts uncurriedAdd.call(3, 5) # 8
puts uncurriedAdd.curry[3][5] # 8

addThree = uncurriedAdd.curry[3]
puts addThree.call(5) # 8
puts addThree.call(12) # 15
(define uncurriedAdd (lambda (x y)
    (+ x y)))

(define curriedAdd (lambda (x)
    (lambda (y)
        (+ x y))))

(display (uncurriedAdd 3 5)) (newline) ; 8
(display ((curriedAdd 3) 5)) (newline) ; 8

(define addThree (curriedAdd 3))

(display (addThree 5)) (newline) ; 8
(display (addThree 12)) (newline) ; 15
proc uncurriedAdd {x y} {
    expr {$x + $y}
}

proc curriedAdd x {
    list apply [list y [format {expr {%d + $y}} $x]]
}

puts stdout [uncurriedAdd 3 5]; # 8
puts stdout [{*}[curriedAdd 3] 5]; # 8

set addThree [curriedAdd 3]
puts stdout [{*}$addThree 5]; # 8
puts stdout [{*}$addThree 12]; # 15

Einzelnachweise und Anmerkungen

Bearbeiten
  1. Moses Schönfinkel: Über die Bausteine der mathematischen Logik. In: Mathematische Annalen 92 (1928). S. 305–316, Digitalisat.
  2. Gottlob Frege: Grundgesetze der Arithmetik. Hermann Pohle, Jena 1893 (Band I) 1903 (Band II) korpora.org.
  3. Haskell Brooks Curry, Robert Feys, Roger Hindley, Jonathan P. Seldin: Combinatory Logic. North Holland, 2 Bände, 1958, 1972.
  4. Dabei bezeichnet   oder   die Menge der Abbildungen von   nach  .
  5. Im nullstelligen Fall (n=0) würde das Currying einer Abbildung   ohne Parameter und mit einem Wert   eine einstellige Abbildung mit konstantem Wert   zuordnen, also etwa
     .
  6. Ein Beispiel ist   mit   (wobei   die Diagonale   bezeichnet).
  7. Im obigen Beispiel ist dies  .
  8. Voraussetzung ist, dass das Symbol   nicht schon anderweitig mit Stelligkeit 1 überladen ist (siehe Beispiel unten, dies zwingt zwischen   und   zu unterscheiden).
  9. Zur Unterscheidung vom Platzhalter   wird hier   als Multiplikationszeichen verwendet.
  10. Dieser Funktion könnte man auch einen eigenen Namen vergeben:
     
    mit  .
  11. Man unterscheide zwischen   und der überladenen Funktion   mit   zur Stelligkeit   und Faktoren  . Zwar ist zweistellig  , aber einstellig ist  , während   eine einstellige Funktion ist.

Kategorie:Programmierung Kategorie:Theoretische Informatik