Typeclassopedia
Funktoren, Monaden, Arrows: Typklassen für Typkonstruktoren

von Robert Steuck


Einleitung | Grundlagen | Vom Funktor zur Monade | Monoid | Arrows | Quellenverzeichnis

Arrows

Die Typklasse Arrow bietet die Möglichkeit der Abstraktion von Funktionen. Sie basiert auf der Typklasse Category, die eine grundlegende Abstraktion für Funktionskomposition darstellt.


class Category (~>) where
  id  :: a ~> a
  (.) :: b ~> c -> a ~> b -> a ~> c

Hierbei steht ~> für eine Typvariable eines Infixtypkonstruktors. Dies dient nur der besseren lesbarkeit (a ~> b entspricht cat a b). Die Implementierung für normale Funktionen benutzt die namesgleichen Funktionen aus dem Paket Prelude:


instance Category (->) where
  id x  = Prelude.id x
  g . h = g Prelude.. h

Da alle Instanzen von Arrow auch Instanzen von Category sein müssen, lassen sie sich wie normale Funtkionen komponieren und es existiert immer ein Identitäts-Arrow:


class Category (~>) => Arrow (~>) where
  arr    :: (a -> b) -> (a ~> b)
  first  :: (a ~> b) -> ((a,c) ~> (b,c))
  
  second :: (a ~> b) -> ((c,a) ~> (c,b))
  (***)  :: (a ~> b) -> (a' ~> b') -> ((a,a') ~> (b,b'))
  (&&&)  :: (a ~> b) -> (a ~> b') -> (a ~> (b,b'))

Die Funktion arr wandelt eine einstellige Funktion in einen Arrow um. Die Funktionen first und second wandeln einen Arrow in einen neuen Arrow um, der auf dem ersten bzw. zweiten Element eines Tupels arbeitet und das jeweils andere Element unverändert lässt. (***) und (&&&) kombinieren zwei Arrows zu einem.

Für die vollständige Implementierung einer Instanz von Arrow ist es lediglich notwending die Funktionen arr und first zu implementieren. Für alle anderen Funktionen gibt es eine Defaultimplementierung:


instance Arrow (->) where
  arr g = g
  first g = \(x,y) -> (g x,y)

Die Typklasse Category exportiert eine zusätzliche Kompositionsfunktion (>>>). Sie unterscheidet sich von der normalen Komposition nur durch die Reihenfolge der Parameter. Somit lassen sich Arrows auf eine natürliche Art kombinieren:


((*2) &&& (*3))
>>> (first (*20))
>>> (second show)
$ 10

Der erste Arrow nimmt eine Zahl als Parameter und liefert ein Paar, dessen Elemente das Zwei- bzw Dreifache der Eingabe sind. Der zweite Arrow nimmt ein Paar als Argument und liefert ein neues Paar, dessen erstes Element mit 20 multipliziert wurde, wobei das zweite Element unangetastet bleibt. Der Dritte Arrow wandelt das zweite Element des Eingabepaares in einen String um. Somit ist das Ergebnis des Kombinierten Arrows mit der Eingabe 10 der Wert (400,"30").


ArrowChoice

Durch die Typklasse ArrowChoice wird der Typklasse Arrow eine erste Flexibilisierung hinzugefügt:


class Arrow (~>) => ArrowChoice (~>) where
  left :: (a ~> b) -> ((Either a c) ~> (Either b c))
  
  right :: (a ~> b) -> ((Either c a) ~> (Either c b))
  (+++) :: (a ~> b) -> (a' ~> b') -> ((Either a a') ~> (Either b b'))
  (|||) :: (a ~> c) -> (b ~> c) -> ((Either a b) ~> c)

Wie schon bei Arrow ist nur die Implementierung einer Funktion (left) notwendig. Waren bei Arrow noch alle Berechnungsschritte von Anfag an vorbestimmt, können diese jetzt je nach anliegendem Datentypen variieren. Alle Berechnungspfade sinde jedoch vor Start der Berechnung vorbestimmt:


((*2) +++ (*3)) >>> ((`div` 10) ||| (`div` 20))
$ Right 100

Für Werte Left x verhält sich diser Arrow wie \x -> (x * 2) `div` 10. Für Werte die mit Right markiert sind verhält er sich wie \x -> (x * 3) `div` 20.


ArrowApply

Durch die Typklasse ArrowApply wird die daynamische gestaltung des Berechnungspfades möglich:


class Arrow (~>) => ArrowApply (~>) where
  app :: (a ~> b, a) ~> b

ArrowApply ermöglich es einen neuen Arrow, der z.B als Zwischenergebnis einer Berechnung entstanden ist, auszuführen. Instanzen der Typklassen ArrowApply und Monad sind in ihrer Ausdruckskraft identisch.


Einleitung | Grundlagen | Vom Funktor zur Monade | Monoid | Arrows | Quellenverzeichnis