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 Arrow
s 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")
.
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
.
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.