ReLast - Klassik


Funktionale Programmierung


Funktionale Programmiersprachen verwenden das Konstrukt der aus der Mathematik bekannten Funktion (f: A -> B mit Funktionsname f, Definitionsbereich A, Wertebereich B).

Beispiel;
K(r) = Pi * r^2 mit dem Definitions- und Wertebereich der reellen Zahlen K: IR -> IR und der Verwendung der Konstante Pi sowie einer Multiplikations- und Quadratfunktion.

Es gilt referentielle Transparenz. Eine Variable hat also in ihrem Gültigkeitsbereich immer den gleichen Wert. Dies gilt für imperative Programmiersprachen nicht, siehe i = i + 1 oder bei Funktionen die mit globalen Variablen nicht nur rechnen sondern diese dabei auch verändern sogar f =/= f.

Als Substituierbarkeit wird in funktionalen Programmiersprachen der Umstand bezeichnet das eine Variable durch ihre Definition ersetzt werden kann, ohne die Bedeutung des Ausdrucks zu verändern.

http://try.ocamlpro.com/

Grundlagen in SML (SML/NJ)

Datentypen und Operationen

Datentypen

Ganze Zahlen: int, Beispiel: 12345.

Reelle Zahlen: real, Beispiel: 1.4 , 2E1 , 2E-1, nEm bezeichnet n*10^m.

Zeichen: char, Beispiel: #"a".

Zeichenketten: string, Beispiel: "a", "zeichen", "kette".

Wahrheitswerte: bool, Beispiel: true, false.

Tupel: (1-1,true,0.0,1>2) wird ausgewertet zu int * bool * float * bool = (0,true,0.0,false). Der Typ ist das kartesische Produkt der enthaltenen Typen int * bool * float * bool. Die einzelnen Werte werden Komponenten genannt. Abfrage über Index-Aufruf, die zählweise beginnt mit 1 (#2 (0,true,0.0,false) wird ausgewertet zu true). Einstellige Tupel gibt es in der Form als Typen nicht, da der Typ dem Typen des Wertes entspricht. Den speziellen Wert () : unit kann man als Nulltupel (in Anlehnung an das leere kartesische Produkt) betrachten.

Record: {Name="Hans",Age=34}. Hier werden die Komponenten über Namen angesprochen (#Name {Name="Hans",Age=34} wird ausgewertet zu Hans). Der Typ definiert sich aus den Komponentennamen und den entspechenden Typen, für Vergleichbarkeit müssen diese beiden also übereinstimmen. Tupel sind im Grunde eine Kurzschreibweise für Records, ein Record bei dem die Komponenten über Zahlen angesprochen werden: {1=1-1,2=true,3=0.0,4=false} = (0,true,0.0,false) (siehe Tupel) oder die beiden folgenden Ausdrücke {1=9,2=true,3=3.0,4=()} = (9,true,3.0,{}) werden zu dem gleichen Ergebnis (9,true,3.0,()) : int * bool * real * unit ausgewertet.

Listen: [1] : int list oder ["eine","zeichen","liste"] : string list. Verschachtelte Listen [[1],[2]] : int list list. Die leere Liste wird definiert über [] oder nil. Erlaubt ist also auch [] oder [nil] (wird ausgewertet zu [[]]) oder [[1],nil], aber nicht [1,nil,2], da nil eine leere Liste repräsentiert und kein Element.

Umwandlungen

real: int -> real

floor: real -> int

ord: char -> int

chr: int -> char

str: char -> string

Operationen

Negation: (~). Unärer Operator für int -> int oder real -> real.

Absolut: (abs). Unärer Operator für int -> int oder real -> real.

Modulo: mod. Binärer Operator für int * int -> int.

Division: /. Binärer Operator für real * real -> real oder div für int * int -> int (Ganzzahlig abgerundete Division).

Multiplikation: (*). Binärer Operator für int * int -> int oder real * real -> real (Überladen).

Addition: (+) Binärer Operator für int * int -> int oder real * real -> real (Überladen).

Subtraktion: (-). Binärer Operator für int * int -> int oder real * real -> real (Überladen).

Konkatenation: (^). Binärer Operator für string * string -> string.

Vergleichsoperationen: (=, <>, <, >, <=, >=, not, andalso, orelse).

Logische Operationen: Negation (not), Konjunktion (andalso), Disjunktion (orelse)

Fallunterscheidung: if cond then exp_a else exp_b, (if 4=4 then "gleich" else "ungleich")

size: string -> int

substring: string * int * int -> string

hd: list -> unit (liefert das erste Element einer Liste)

tl: list -> list (liefert die Restliste nach dem ersten Element. Ergebnis ist immer list, auch wenn es nur noch ein Element ist)

:: (cons) fügt ein Element am Anfang der Liste ein: "L"::["i","s","t","e"], wobei Strings zu Listen konvertiert werden können: "t"::nil = ["t"] : string list da nil als leere Liste interpretiert wird, wird somit ein String-Element am Anfang einer leeren Liste eingefügt.

@ (append) konkateniert Listen: ["L"]@["i","s"]@["t","e"]. Auch hier gilt: nil als leere Liste ist möglich ["3"," ","L","i","s"]@nil@["t","e","n"," ","!"]

rev: list -> list. Kehrt die Reihenfolge einer Liste um.

explode: string -> char list. Wandelt einen string in eine Liste von char's um: explode "Rene" = [#"R",#"e",#"n",#"e"] : char list.

implode: char list -> string. Wandelt eine Liste von char's in einen string um: implode [#"R",#"e",#"n",#"e"] = "Rene" : string.

Variablen und Definitionen

Variablen werden gebunden mit val varname = expression, dabei wird die Expression zu einem Value ausgewertet, und dieser Wert dann an die Variable gebunden. Dies ist auch im pattern matching möglich: val (vorname, nachname) = ("Rene","Lauenstein") wird ausgewertet zu den beiden Variablen vorname und nachname.

Statisches Binden

Für Funktionen gilt die Umgebung die zum Zeitpunkt des bindens gültig ist.

val y = true;
> val y = true : bool

fun f x = if y then x else x + 1;
> val f = fn : int -> int

val y = false;
> val y = false : bool

f 4;
> val it = 4 : int

Lokale Definitionen

Um die statische Bindung zu umgehen können lokale Umgebungen sowohl für Ausdrücke als auch für neue Bindungen definiert werden. Die in der lokalen Umgebung definierten Bindungen sind nur dort gültig. Nach der lokalen Umgebung haben wieder die vorher vorhandenen Bindungen Gültigkeit.

Als Mittel stehen die let-Klausel und die local-Klausel zur Verfügung. Die let-Klausel definiert lokale Bedingungen (auch Abkürzungen von Ausdrücken sind möglich) für die Auswertung eines Ausdrucks. Die local-Klausel definiert lokale Bedingungen für die Bindung neuer Variablen die nach der Auswertung in der Umgebung gültig sind.

let-Klausel

Mit der let-Klausel lassen sich Abkürzungen für einen speziellen Ausdruck erzeugen let declaration in expression end;, let val var=exp in exp end;. In dem enthaltenen Ausdruck können dann die abgekürzten Ausdrücke verwendet werden. Dabei bleiben alle Bindungen lokal. Die let-Klausel wird zu einem einzigen Ausdruck ausgewertet.

An folgendem Beispiel kann man sehen das dabei innerhalb der lokalen Umgebung der Wert des vorher gebundenen x übernommen wird. In der lokalen Umgebung wird x neu gebunden als Abkürzung für die size-Operation, nach der lokalen Umgebung gilt aber wieder die alte Bindung.

val x = "unberuehrt";
> val x = "unberuehrt" : string

let val x = size x 
  in
    x * 9
end;
> val it = 90 : int

"x blieb " ^ x;
> val it = "x blieb unberuehrt" : string
local-Klausel

Mit der local-Klausel lassen sich lokale Bindungen erzeugen um damit unter den dadurch definierten Bedingungen neue Bindungen zu erzeugen let declaration in declaration end;. Dabei haben die erzeugten Bindungen auch nach der Klausel Gültigkeit wie man an folgendem Beispiel an der nach wie vor vorhandenen Bindung von z und x sehen kann, wogegen die nur für die Bindung gebundene Variable y nicht mehr definiert ist.

local val x = 15
      val y = 12
  in
      val z = x div 3
      val x = 2 * y
  end;
> val z = 5 : int
> val x = 24 : int

z;
> val z = 5 : int

x;
> val x = 24 : int

y;
> stdIn:4.1 Error: unbound variable or constructor: y
Beispiele:
let val x=3
  local val x=4
  in
    val y=x
  end
in
  x
end
> val it = 3 : int

Die erste Bindung von x=3 bleibt von der zweiten lokalen Bindung unberührt. Die Bindung von y würde nach der local-Klausel noch existieren, da selbige aber Teil der lokalen Bindung für den Ausdruck der let-Klausel ist, gibt es nach der Gesamtauswertung auch kein y mehr. Da am Ende einfach nur x als Ausdruck ausgewertet wird, kommt es zu dem it=3. Da y im Ausdruck nicht beachtet wird, hat es keinerlei Bedeutung. Kann auch reduziert werden auf let val x = 3 in x end;.

local val x=2
  val y=let local val x=x*x
            in
              val x=x*x
            end
        in
          x*x
        end
in
  val x=y div x div x
end
> val x = 64 : int

x^2 -> x^4 -> x^8 ergo 2^8 führt zu y=256, zwei mal teilen durch das originale x=2 führt zu -> 128 -> 64 ergo x=64

Funktionen

Definition: fn r => 3.1415 * r * r; > val it = fn : real -> real. Bindung: val circleArea = fn r => 3.1415 * r * r; > val circleArea = fn : real -> real

Kurzform der Funktionsbindung mit fun f x = x+1.

Aufruf mit f 1; oder val x=1; f x;.

Mehrere Parameter mit Tupeln: fun pie (r,w) = 3.1415 * r * r * w / 360.0; > val pie = fn : real * real -> real

Typrestriktion: fun sqr (x:int) = x * x; > val sqr = fn : int -> int


Copyright © 2024

Datenschutz | Impressum