Funktionale Programmierung

Frank Thomas und Benjamin Trenker

Agenda

  1. Was ist funktionale Programmierung?
  2. Fehlerbehandlung ohne Exceptions
  3. Effekte mit Funktionen

Was ist funktionale Programmierung?

Was ist eine Funktion?

Eine Funktion A => B bildet jeden Wert vom Typ A auf genau einen Wert vom Typ B ab.


Und mehr nicht.

Funktionale Programmierung = keine Seiteneffekte

  • Variablen / Objektzustände ändern
  • Exceptions
  • I/O, z.B. GUI, Datenbank, Dateien

Formal: Referenzielle Transparenz

An expression e is referentially transparent if, for all programs p, all occurrences of e in p can be replaced by the result of evaluating e without affecting the meaning of p. A function f is pure if the expression f(x) is referentially transparent for all referentially transparent x

Praktisch bedeutet das:

  • der Rückgabewert einer Funktion hängt einzig von ihren Parameterwerten ab
  • somit liefert jeder Aufruf einer Funktion mit den gleichen Parameterwerten zu jeder Zeit das gleiche Ergebnis
  • Voraussetzung: die Parameter sind ebenfalls referenziell Transparent
  • einfaches Bsp. "plus"-Funktion: 1 + 2 ergibt immer 3

Bsp. 1

              x = new StringBuilder("Hello")
              r1 = x.append(" World")
              r2 = x.append(" World")
               
Ist StringBuilder#append referentiell transparent?

Bsp. 1

              r1 == "Hello World"
              r2 == "Hello World World"
               
=> nicht referenziell transparent

Bsp. 2

                  def hello(name: String) = new StringBuilder("Hello ")
                    .append(name)
                    .append("!")
                    .toString
                          
Ist "hello(...)" referenziell transparent?

Bsp. 2

=> ja, da von außen beobachtet kein Effekt auftritt
=> der Parameter vom Typ String ist ebenfalls referenziell transparent

Und die Außenwelt?

  • referenziell transparente Funktionen bilden die Businesslogik ab
  • Effekte werden in einen äußeren Layer geschoben
  • Effekte werden explizit gemacht und treten nicht implizit in der Businesslogik auf
  • die Businesslogik bleibt somit frei von Effekten und technischen Belangen

Fehlerbehandlung ohne Exceptions

Idee: Fehler als Werte repräsentieren

Option[T] und Either[A, B]

Option[T]

Beinhaltet einen Wert vom Typ T oder nicht. Wie eine Liste mit maximal einem Element.


Besser als null, weil

  • der Typ dokumentiert, dass kein Wert da sein kann
  • Fehlerbehandlung zusammengefasst werden kann
  • wir dem Compiler ein Option[T] nicht als T unterjubeln können

Fehlerhandling mit null

    val m1: java.util.Map[String, Integer] = ...
    val m2: java.util.Map[String, Integer] = ...
    val m3: java.util.Map[String, Integer] = ...
            
    val key = "two"
    var result: Integer = null
    val v1 = m1.get(key)
    if (v1 != null) {
      val v2 = m2.get(key)
      if (v2 != null) {
        val v3 = m3.get(key)
        if (v3 != null) {
          result = v1 + v2 + v3
        }
      }
    }

Fehlerhandling mit Option

    val m1: Map[String, Int] = ...
    val m2: Map[String, Int] = ...
    val m3: Map[String, Int] = ...
            
    val key = "two"
    val result: Option[Int] = for {
      v1 <- m1.get(key)
      v2 <- m2.get(key)
      v3 <- m3.get(key)
    } yield v1 + v2 + v3

Fehlerbehandlung verzögern

    val x: Option[Int] = ...
    val res: Int = x.map(i => i + 2)
                    .filter(i => i < 10)
                    .getOrElse(42)

An geeigneter Stelle kann entschieden werden wie mit einem nicht vorhandenen Wert umgegangen werden soll.

Either[A, B]

Ist entweder ein A oder ein B.

Vorteil gegenüber Option: Fehlerursache kann zurückgegeben werden

def safeToInt(s: String): Either[NumberFormatError, Int] = ...
safeToInt("120").right.map(i => i + 1)

Es gibt noch mehr Datentypen, um mit Fehlern umzugehen.

Effekte mit referentiell transparenten Funktionen

Beispiel Zufallszahlen

Idee: Datentyp einführen, der den Kontext Zufälligkeit repräsentiert

Rng[T]

Analog zu Option[T]: Kontext ist mögliche Fehlen eines Wertes

Zufallszahlen mit Rng

Rng[Double] repräsentiert einen zufälligen Double


    val d1: Rng[Double] = Rng.double // und
    val d2: Rng[Double] = Rng.double // sind gleichbedeutend
          

    Rng.seetseed(7).flatMap(_ => d1).run.unsafePerformIO()
    // 0.7306990420600421
    
    Rng.seetseed(7).flatMap(_ => d2).run.unsafePerformIO()
    // 0.7306990420600421
          

Beispiel mit Rng

Was sind die Vorteile funktionaler Programmierung?

  • Designvorteile durch separation of concerns
  • einfach zu testen
  • einfach zu parallelisieren
  • ...

Was sind die Nachteile?

  • Paradigmenwechsel erfordert ein Umdenken

THE END

- Slides: http://fthomas.github.io/fp-talk
- Code: https://github.com/fthomas/fp-talk
- Functional Programming in Scala
- IO: https://github.com/scalaz/scalaz
- Rng: https://github.com/NICTA/rng