Neue Sprachen braucht die Welt (Teil 3)
Kommentare

Interfaces on-the-fly
Die Instantiierung von Objekten in Ausdrücken ist eine sehr mächtige Technik. Der vielleicht einfachste Anwendungsfall ist die Erzeugung von Interface-Instanzen. Microsofts LINQ

Interfaces on-the-fly

Die Instantiierung von Objekten in Ausdrücken ist eine sehr mächtige Technik. Der vielleicht einfachste Anwendungsfall ist die Erzeugung von Interface-Instanzen. Microsofts LINQ enthält etwa eine Reihe von Funktionen, die anstelle von Lambda-Ausdrücken Interfaces entgegennehmen – dies ist insbesondere dann sinnvoll, wenn mehr als eine Funktion an eine andere übergeben werden soll. In C# oder Visual Basic ist der Umgang damit allerdings nicht einfach, denn es muss zunächst eine separate Klasse erzeugt werden, die das Interface implementiert. In F# hingegen kann eine Instanz eines Interfaces, wie im Beispiel zu sehen, direkt erzeugt werden.

„Komposition anstelle von Vererbung“ – das ist für objektorientierte Programmierer beinahe ein geflügeltes Wort. Kurz gesagt, es ist oft eine bessere Idee, Klassen aus anderen Klassen zusammenzusetzen, als diese voneinander abzuleiten. In C# oder Visual Basic ist dies allerdings nicht immer einfach, da bei der Komposition andere Klassen entweder in Gänze oder gar nicht verwendet werden müssen. In F# hingegen ist es möglich, eine Variante einer Klasse als Bestandteil für eine neue Klasse zu benutzen. Dies führt zu größerer Flexibilität bei der Komposition und damit zu einfacherem Code.

Discriminated unions schließlich bieten eine extrem vereinfachte Syntax zur Erstellung komplexer Datencontainer. Aus der Sicht von F# selbst ist dies nicht unbedingt ein objektorientiertes Feature, tatsächlich gibt es ähnliche Konstrukte auch in funktionalen Programmiersprachen wie Haskell, die mit Objektorientierung gar nichts zu tun haben. Allerdings arbeitet die Umsetzung dieser Technik in F# so, dass Klassenhierarchien vom Compiler automatisch erzeugt werden, die auch von anderen .NET-Sprachen eingesetzt werden. Insofern sind discriminated unions in F# als vereinfachte Syntax zur Klassendeklaration zu sehen.

// Interface instantiation

// This function takes an IEat implementation and calls Eat
let CallEat(e: IEat) =
  e.Eat()

// Now call CallEat with an on-the-fly IEat instance
CallEat ({new IEat with Eat() = printfn "Ad-hoc eating"})

// Composition instead of inheritance
type Wolf() = 
  // Use a standard dog, but change the way it sleeps
  let dog = {
    new Dog() with
      member x.Sleep() = printfn "Wolf sleeping"
  }
  // Sleep like the modified dog we are
  member x.Sleep() = dog.Sleep()
  // Eat like a dog, although no IEat implementation
  member x.Eat() = (dog :> IEat).Eat()

let wolf = Wolf()
wolf.Sleep()
wolf.Eat()

// Discriminated unions

type Product = 
  | OwnProduct of string
  | RemoteReference of int
    
type Count = int
type StoreBooking = 
  | Incoming of Product * Count
  | Outgoing of Product * Count

// Instantiate a list of store bookings with nested products
let bookings = 
  [
    Incoming(OwnProduct("Rubber Hose"), 20);
    Incoming(RemoteReference(301), 100);
    Outgoing(RemoteReference(301), 40);
    Outgoing(OwnProduct("Boat"), 3)
  ]  

Listing 5

Funktionale Programmierung

F# ist unter den hier beschriebenen Sprachen diejenige, die sich in Richtung der funktionalen Programmierung spezialisiert hat. Sie stammt, was ihre Syntax betrifft, von den funktionalen Sprachen ML und OCaml ab, und in vielen Fällen kann in F# geschriebener Code tatsächlich auch von OCaml-Compilern verarbeitet werden. Das Konzept der funktionalen Programmierung ist für Programmierer, die imperative Sprachen gewöhnt sind, oft schwer greifbar, da Beispiele in Form kurzer Funktionen oft nicht sehr aussagekräftig sind – Vorteile, die sich etwa aus der Bevorzugung unveränderbarer Datenstrukturen oder der einfachen Verfügbarkeit von Mechanismen wie Currying, Composition und Partial Application ergeben, sind im Rahmen größerer Codebeispiele einfacher erkennbar.

F# zeichnet sich dadurch aus, dass die Sprache funktionale Ansätze einfach zugänglich macht und die mitgelieferten Librarys diese ebenfalls weitgehend verwenden. Partial Application kann in F# jederzeit angewendet werden:

// Eine Funktion mit zwei Parametern let add x y = x + y

// Partial Application zur Erzeugung einer neuen Funktion let add5 = add 5

add5 10 // ergibt 15

Listing 6 zeigt den Code, der zu diesem Zweck in C#, Visual Basic und Python geschrieben werden muss – wie so oft können moderne Programmiersprachen diese Ansätze umsetzen, lassen es aber an der Eleganz missen, die eine „echte“ funktionale Sprache bietet. Es sollte gesagt werden, dass es Librarys gibt, die diese Vorgängen für C#, Visual Basic und Python etwas vereinfachen, aber diese sind extern zur Sprache selbst.

// Currying und Partial Application in C#
static int Add(int x, int y) {
  return x + y;
}
...
Func> curriedAdd = x => y => Add(x, y);
var add5 = curriedAdd(5);
Console.WriteLine(add5(10));

// Currying und Partial Application in Visual Basic
Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
    Return x + y
End Function
...
Dim curriedAdd As Func(Of Integer, Func(Of Integer, Integer)) = _
    Function(x As Integer) Function(y As Integer) Add(x, y)
Dim add5 = curriedAdd(5)
Console.WriteLine(add5(10))

// Currying und Partial Application in Python
def add(x, y): return x + y

curriedAdd = lambda x: lambda y: add(x, y)
add5 = curriedAdd(5)
print add5(10)  

Listing 6

Eine Frage der Darstellung

Ein weiteres einfaches Beispiel ist die Implementation einer Funktion zur Berechnung einer Fakultät. In funktionalen Sprachen bedienen sich Programmierer hierfür gern eines rekursiven Algorithmus‘, der sehr elegant und lesbar ist:

let rec fact = function

| 0 -> 1

| x -> x * fact (x - 1)

Funktionen in solcher Form gleichen einer Liste von mathematischen Gleichungen oder Definitionen, anhand derer sich auch komplexe Algorithmen sehr einfach und übersichtlich darstellen. Ein typischer imperativer Ansatz in C# sieht hingegen so aus:

static int Fact(int x) {

int result = 1;

if (x > 1)

for (int i = 2; i

result *= i;

return result;

}

Es geht an dieser Stelle zu weit, Bedeutung und Vorteile der funktionalen Programmierung im Detail zu erläutern. Die Beispiele haben jedoch zwei Fälle aufgezeigt, in denen F# besondere Unterstützung bietet, die in den anderen Sprachen nicht vergleichbar existiert. F# bietet das Beste aus den beiden Welten der objektorientierten und der funktionalen Programmierung, und keine der anderen Sprachen kann in diesen Bereichen mithalten.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -