Home
 

Funktionsdefinitionen

nextnextupuppreviouspreviouscontentscontents
Next:OperatorenUp:Grundlegende SprachelementePrevious:Typen von Funktionen

Funktionsdefinitionen

sind natürlich die Seele vom Geschäft und auf verschiedene Weise möglich, wobei man immer auf bereits vorhandenes Material aufbaut;

formell besteht eine Funktionsdefinition aus dem Definitionszeichen ``='',
einer linken Seite, die den Namen der Funktion und (wenn zur Definition nötig) formalen Parametern besteht,
einer rechten Seite, die (evtl. unter Benutzung der formalen Parameter) ein Ausdruck von entsprechendem Typ ist.

Beispiele:
 inc :: Int -> Int      - Typdeklaration

inc x = x + 1 - Funktionsdefinition
--- - Ausdruck, der benutzt:
- formalen Parameter x,
- vordefinierte Funktionen x und 1


k_aus_n :: Int -> Int -> Int
k_aus_n k n = fac n `div` (fac k * fac (n-k))


negative :: Num(a) => a -> Bool
negative x = x < 0


is_zero :: Num(a) => a -> Bool
is_zero x = x==0

Keine formalen Parameter benötigt man, wenn die neue Funktion eigentlich eine alte ist ...

 circle_area :: Float -> Float

circle_area = area


coarse_diff :: (Float -> Float) -> (Float -> Float)
coarse_diff = multi_diff 10.0 - partielle Anwendung, vgl. vorher

Im allgemeinen enthält die rechte Seite einer Definition eine

  • Fallunterscheidung

    = die einzelnen Definitionsfälle werden von "`Wächtern"' (guards, Boolesche Ausdrücke) gestützt;
    es wird der (im Sinne der Aufschreibungsreihenfolge) erste Fall genommen, dessen Wächter sich zu True errechnet.

    Beispiele:
     max2 :: Int -> Int -> Int
    
    max2 x y | x > y = x
    | x <= y = y

    dafür geht auch:

     max2 x y | x > y     = x
    
    | otherwise = y


    max3 :: Int -> Int -> Int -> Int
    max3 x y z | x>=y && x>=z = x
    | y>=x && y>=z = y
    | otherwise = z

    usw.

  • pattern matching

    Beim Aufruf einer Funktion werden aktuelle Parameter (=Ausdrücke, Werte) an die Stelle der formalen Parameter (bloße Bezeichner) gesetzt:

    max217(2*3)
     $\downarrow$$\downarrow$
     xy

    Man kann dieses übliche "`matchen"' (auf einander beziehen) von formalen und aktuellen Parametern auch noch verallgemeinern:

    Lasse zu, daß ein formaler Parameter ein Muster (pattern) ist, d.h. etwas Strukturiertes.

    Typische Beispiele bieten Listen-Datentyp:
    eine Liste ist entweder die leere Liste [], oder sie hat ein Listenelement x gefolgt von einer Restliste xs, d.h. hat den Aufbau x:xs;
    das nimmt man jetzt zur Definition;

    Beispiel:
     sum_the_list :: [Int] -> Int
    
    sum_the_list [] = 0
    sum_the_list (x:xs) = x + sum_the_list xs

    Beachte:

    • für jeden pattern-Fall eigene Gleichung;
    • bei Anfang wird erst passende Gleichung (d.h. passendes Muster) genommen; gibt es keine Übereinstimmung, resultiert ein Laufzeitfehler;
    • Witz an der Sache ist, daß in Verallgemeinerung der formal/aktuell-Beziehung ein Zugriff auf Teile (Konstituenten) des aktuellen Parameterwerts ermöglicht wird:
      im Beispiel werden Kopf und Schwanz der Liste an x und xs gebunden:
      [1,$\underbrace{2, 3}$]aktuelle Parameter
       $\downarrow$ $\downarrow$  
       x xs pattern-Teile

    Was ist für patterns erlaubt?

    • Variable (=formale Parameter) und Konstante (=numerische Literale, Char- , Bool-Elemente ),
    • Listen, von denen die Elemente wieder Patterns sind, also sowas wie [True, b1, b2],
    • mit ``:'' gebildete Listenausdrücke (siehe oben),
    • mit ``+'' gebildete arithmetische Ausdrücke der Form n+k, wobei k konstant und n-k>=0 ist,
    • ähnlich: Ausdrücke der Form k*n,
    • das wild-card-pattern ``_'' auf das alles paßt
    Beispiel:
     vorgaenger :: Int -> Int
    
    vorgaenger 0 = 0 - naja
    vorgaenger (n+1) = n

    dann folgt:

     > vorgaenger 77
    
    76
    > vorgaenger (2-3)
    Programm Error: vorgaenger(-1)

    denn: (-1) läßt sich nicht in der verlangten Weise als n+1 darstellen, und einen Fall für negative Werte gibt es nicht.

    Beispiel:
    für beliebige Listentypen:
     head (x:_)  = x           - Rest der Liste interessiert nicht
    
    - Keine Definition für leere Liste!!


    tail (_:xs) = xs - Kopf interessiert nicht,
    - nicht definiert für leere Liste!!

    Manchmal will man eine Funktion konstruieren, ohne sie einem Bezeichner zuzuweisen, d.h. die Funktion bleibt anonym.

    Beispiel: partielle Anwendung einer Funktion, also sowas wie volume 1 1.

    Weitere wichtige Möglichkeiten sind:

  • Lambda-Abstraktion
    gegeben: ein Ausdruck exp und ein Variablenbezeichner x

    dann: $\backslash$x -> exp ist diejenige Funktion, die einem Wert v den Wert exp $v \atop x$ ( d.h. v ersetzt x in exp) zuweist.

    Beispiel:

    gegeben: 2.0 * x2 - 1

    dann: $\backslash$x -> (2.0 * x^2 - 1.0)

    Das ist die Funktion mit Werten:

    x...-101...
    Wert...1-11...

    also graphisch:


    \begin{picture}(6,4) \par % Koordinatenachsen \put(0.5,2){\vector(1,0){5}} \put(... ...eich [-1, 1] \qbezier(3,1)(3.75,1)(4,3) \qbezier(3,1)(2.25,1)(2,3) \end{picture}

    Natürlich kann man einen Bezeichner einführen wie z.B.

     f = \x -> 2.0 * x^2 - 1.0
    

    aber es ist oft bequem, die unbenannte Funktion als Argument in "`higher order functions"' zu benutzen:

     > diff (\x -> 2.0 * x^2 - 1.0) 0
    
    0.00202656
    > diff (\x -> 2.0 * x^2 - 1.0) 1
    4.00209
    > diff (\x -> 2.0 * x^2 - 1.0) (-1)
    -3.99792

    usw.

  • Komposition von Funktionen

    Mathematik: g$\circ$f heißt: erst f, dann darauf g anwenden,

    also sowas wie "`2 Verarbeitungsschritte"' hintereinander:

    Ergebnis $\leftarrow$ \fbox{\rule[-1mm]{0mm}{4mm}g} $\leftarrow$ \fbox{\rule[-1mm]{0mm}{4mm}f} $\leftarrow$ Input

    Die Haskell-Schreibweise für dieselbe Sache ist g.f

    Wertebereich von f muß zu Argumentbereich von g passen!

    Beispiel:
    2. Ableitung:
     > (diff.diff) cos 0
    
    -1.07288

    denn: (-cos) $\leftarrow$ \fbox{diff} $\leftarrow$ (-sin) $\leftarrow$ \fbox{diff} $\leftarrow$ cos

    und - cos 0 = -1

    diff.diff cos 0 wird aufgefaßt als diff.((diff cos) 0) wegen höchster Priorität der Funktionsapplikation!

    Funktionsapplikation (bei higher order functions) und Funktionskomposition nicht verwechseln!

     (diff.diff) cos 0
    
    | |
    | |-- Applikation: diff hat cos als Argument transformiert cos
    |
    |-- Komposition: zweites diff wird nach dem ersten diff, d.h.
    auf Ergebnis vom ersten diff angewendet.

    Natürlich kann man dem Kind einen Namen geben, z.B.:

     > let diff2 = diff.diff in diff2 cos 0
    
    -1.07288
    Beispiel:
    Komposition von Funktion und inverser Funktion gibt Identität, z.B.:
     sqrt . (\x -> x^2) = id  in R
    
    | |
    | |-- Quadratfunktion
    |
    |-- vordefinierte Wurzelfunktion
     > (sqrt.(\x -> x^2)) 5
    
    5.0
    > (sqrt.(\x -> x^2)) (-5) - stimmt nur für x>0
    5.0
    > diff (sqrt.(\x -> x^2)) (-5)
    -0.999927

nextnextupuppreviouspreviouscontentscontents
Next:OperatorenUp:Grundlegende SprachelementePrevious:Typen von Funktionen
Ronald Blaschke
1998-04-19