emanote/content/Haskell/Lenses.md

563 lines
15 KiB
Markdown
Raw Normal View History

2022-08-24 14:47:51 +00:00
# Lenses
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Wofür brauchen wir das überhaupt?
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Die Idee dahinter ist, dass man Zugriffsabstraktionen über Daten verknüpfen
kann. Als einfachen Datenstruktur kann man einen Record mit der entsprechenden
Syntax nehmen.
### Beispiel
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Person = P { name :: String
, addr :: Address
, salary :: Int }
data Address = A { road :: String
, city :: String
, postcode :: String }
-- autogeneriert unten anderem: addr :: Person -> Address
setName :: String -> Person -> Person
setName n p = p { name = n } --record update notation
setPostcode :: String -> Person -> Person
setPostcode pc p
= p { addr = addr p { postcode = pc } }
-- update of a record inside a record
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
### Probleme
2022-08-24 11:55:32 +00:00
Probleme mit diesem Code:
- für 1-Dimensionale Felder ist die record-syntax ok.
- tiefere Ebenen nur umständlich zu erreichen
- eigentlich wollen wir nur pe in p setzen, müssen aber über addr etc. gehen.
2022-08-24 14:47:51 +00:00
- wir brauchen wissen über die "Zwischenstrukturen", an denen wir nicht
interessiert sind
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
### Was wir gern hätten
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Person = P { name :: String
, addr :: Address
, salary :: Int }
-- a lens for each field
lname :: Lens' Person String
laddr :: Lens' Person Adress
lsalary :: Lens' Person Int
-- getter/setter for them
view :: Lens' s a -> s -> a
set :: Lens' s a -> a -> s -> s
-- lens-composition
composeL :: Lens' s1 s2 -> Lens s2 a -> Lens' s1 a
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
### Wie uns das hilft
2022-08-24 11:55:32 +00:00
Mit diesen Dingen (wenn wir sie hätten) könnte man dann
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Person = P { name :: String
, addr :: Address
, salary :: Int }
data Address = A { road :: String
, city :: String
, postcode :: String }
setPostcode :: String -> Person -> Person
setPostcode pc p
= set (laddr `composeL` lpostcode) pc p
~~~~~~~~~~~~~~~~~~
machen und wäre fertig.
2022-08-24 14:47:51 +00:00
## Trivialer Ansatz
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
### Getter/Setter als Lens-Methoden
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data LensR s a = L { viewR :: s -> a
, setR :: a -> s -> s }
composeL (L v1 u1) (L v2 u2)
= L (\s -> v2 (v1 s))
(\a s -> u1 (u2 a (v1 s)) s)
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
### Wieso ist das schlecht?
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
- extrem ineffizient
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Auslesen traversiert die Datenstruktur, dann wird die Funktion angewendet und
zum setzen wird die Datenstruktur erneut traversiert:
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 14:47:51 +00:00
over :: LensR s a -> (a -> a) -> s -> s
over ln f s = setR l (f (viewR l s)) s
~~~~~~~~~~~~~~~~~~
- Lösung: modify-funktion hinzufügen
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 14:47:51 +00:00
data LensR s a
= L { viewR :: s -> a
, setR :: a -> s -> s
, mod :: (a->a) -> s -> s
, modM :: (a->Maybe a) -> s -> Maybe s
, modIO :: (a->IO a) -> s -> IO s }
~~~~~~~~~~~~~~~~~~
Neues Problem: Für jeden Spezialfall muss die Lens erweitert werden.
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
### Something in common
2022-08-24 11:55:32 +00:00
Man kann alle Monaden abstrahieren. Functor reicht schon:
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data LensR s a
= L { viewR :: s -> a
, setR :: a -> s -> s
, mod :: (a->a) -> s -> s
, modF :: Functor f => (a->f a) -> s -> f s }
~~~~~~~~~~~~~~~~~~
Idee: Die 3 darüberliegenden durch modF ausdrücken.
2022-08-24 14:47:51 +00:00
### Typ einer Lens
2022-08-24 11:55:32 +00:00
Wenn man das berücksichtigt, dann hat einen Lens folgenden Typ:
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
type Lens' s a = forall f. Functor f
=> (a -> f a) -> s -> f s
~~~
2022-08-24 11:55:32 +00:00
Allerdings haben wir dann noch unseren getter/setter:
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
data LensR s a = L { viewR :: s -> a
, setR :: a -> s -> s }
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Stellt sich raus: Die sind isomorph! Auch wenn die von den Typen her komplett
anders aussehen.
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Benutzen einer Lens als Setter
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
set :: Lens' s a -> (a -> s -> s)
set ln a s = --...umm...
--:t ln => (a -> f a) -> s -> f s
-- => get s out of f s to return it
~~~~~~
2022-08-24 14:47:51 +00:00
Wir können für f einfach die "Identity"-Monade nehmen, die wir nachher wegcasten
können.
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
newtype Identity a = Identity a
-- Id :: a -> Identity a
runIdentity :: Identity s -> s
runIdentity (Identity x) = x
instance Functor Identity where
fmap f (Identity x) = Identity (f x)
~~~~~~~~~~~~~~~~~~
somit ist set einfach nur
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
set :: Lens' s a -> (a -> s -> s)
set ln x s
= runIdentity (ls set_fld s)
where
set_fld :: a -> Identity a
set_fld _ = Identity x
-- a was the OLD value.
-- We throw that away and set the new value
~~~~~~
oder kürzer (für nerds wie den Autor der Lens-Lib)
2022-08-24 14:47:51 +00:00
~~~ {.haskell }
set :: Lens' s a -> (a -> s -> s)
set ln x = runIdentity . ln (Identity . const x)
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Benutzen einer Lens als Modify
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Dasselbe wie Set, nur dass wir den Parameter nicht entsorgen, sondern in die
mitgelieferte Funktion stopfen.
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
over :: Lens' s a -> (a -> a) -> s -> s
over ln f = runIdentity . ln (Identity . f)
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Benutzen einer Lens als Getter
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
view :: Lens' s a -> (s -> a)
view ln s = --...umm...
--:t ln => (a -> f a) -> s -> f s
-- => get a out of the (f s) return-value
-- Wait, WHAT?
~~~~~~
2022-08-24 14:47:51 +00:00
Auch hier gibt es einen netten Funktor. Wir packen das "a" einfach in das "f"
und werfen das "s" am Ende weg.
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
newtype Const v a = Const v
getConst :: Const v a -> v
getConst (Const x) = x
instance Functor (Const v) where
fmap f (Const x) = Const x
-- throw f away. Nothing changes our const!
~~~~~~
somit ergibt sich
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
view :: Lens' s a -> (s -> a)
view ln s
= getConst (ln Const s)
-- Const :: s -> Const a s
~~~~~~
oder nerdig
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
view :: Lens' s a -> (s -> a)
view ln = getConst . ln Const
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Lenses bauen
2022-08-24 11:55:32 +00:00
Nochmal kurz der Typ:
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
type Lens' s a = forall f. Functor f
=> (a -> f a) -> s -> f s
~~~
2022-08-24 11:55:32 +00:00
Für unser Personen-Beispiel vom Anfang:
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Person = P { _name :: String, _salary :: Int }
name :: Lens' Person String
-- name :: Functor f => (String -> f String)
-- -> Person -> f Person
name elt_fn (P n s)
= fmap (\n' -> P n' s) (elt_fn n)
-- fmap :: Functor f => (a->b) -> f a -> f b - der Funktor, der alles verknüpft
-- \n' -> .. :: String -> Person - Funktion um das Element zu lokalisieren (WO wird ersetzt/gelesen/...)
-- elt_fn n :: f String - Funktion um das Element zu verändern (setzen, ändern, ...)
~~~~~~
Die Lambda-Funktion ersetzt einfach den Namen. Häufig sieht man auch
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
name elt_fn (P n s)
= (\n' -> P n' s) <$> (elt_fn n)
-- | Focus | |Function|
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Wie funktioniert das intern?
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
view name (P {_name="Fred", _salary=100})
-- inline view-function
= getConst (name Const (P {_name="Fred", _salary=100})
-- inline name
= getConst (fmap (\n' -> P n' 100) (Const "Fred"))
-- fmap f (Const x) = Const x - Definition von Const
= getConst (Const "Fred")
-- getConst (Const x) = x
= "Fred"
~~~~~~
2022-08-24 14:47:51 +00:00
Dieser Aufruf hat KEINE Runtime-Kosten, weil der Compiler direkt die Adresse des
Feldes einsetzen kann. Der gesamte Boilerplate-Code wird vom Compiler
wegoptimiert.
2022-08-24 11:55:32 +00:00
Dies gilt für jeden Funktor mit newtype, da das nur ein Typalias ist.
2022-08-24 14:47:51 +00:00
## Composing Lenses und deren Benutzung
2022-08-24 11:55:32 +00:00
Wie sehen denn die Typen aus?
Wir wollen ein
> Lens' s1 s2 -> Lens' s2 a -> Lens' s1 a
Wir haben 2 Lenses
> ln1 :: (s2 -> f s2) -> (s1 -> f s1)
> ln2 :: (a -> f a) -> (s2 -> f s2)
wenn man scharf hinsieht, kann man die verbinden
> ln1 . ln2 :: (a -> f s) -> (s1 -> f s1)
und erhält eine Lens. Sogar die Gewünschte!
Somit ist Lens-Composition einfach nur Function-Composition (.).
2022-08-24 14:47:51 +00:00
## Automatisieren mit Template-Haskell
2022-08-24 11:55:32 +00:00
Der Code um die Lenses zu bauen ist für records immer Identisch:
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Person = P { _name :: String, _salary :: Int }
name :: Lens' Person String
name elt_fn (P n s) = (\n' -> P n' s) <$> (elt_fn n)
~~~~~~
Daher kann man einfach
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
import Control.Lens.TH
data Person = P { _name :: String, _salary :: Int }
$(makeLenses ''Person)
~~~~~~
nehmen, was einem eine Lens für "name" und eine Lens für "salary" generiert.
2022-08-24 14:47:51 +00:00
Mit anderen Templates kann man auch weitere Dinge steuern (etwa wofür Lenses
generiert werden, welches Prefix (statt \_) man haben will etc. pp.).
2022-08-24 11:55:32 +00:00
Will man das aber haben, muss man selbst in den Control.Lens.TH-Code schauen.
2022-08-24 14:47:51 +00:00
## Lenses für den Beispielcode
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
import Control.Lens.TH
data Person = P { _name :: String
, _addr :: Address
, _salary :: Int }
data Address = A { _road :: String
, _city :: String
, _postcode :: String }
$(makeLenses ''Person)
$(makeLenses ''Address)
setPostcode :: String -> Person -> Person
setPostcode pc p = set (addr . postcode) pc p
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
## Shortcuts mit "Line-Noise"
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
-- ...
setPostcode :: String -> Person -> Person
setPostcode pc p = addr . postcode .~ pc $ p
-- | Focus |set|to what|in where
getPostcode :: Person -> String
getPostcode p = p ^. $ addr . postcode
-- |from|get| Focus |
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
Es gibt drölf-zillionen weitere Infix-Operatoren (für Folds,
Listenkonvertierungen, -traversierungen, ...)
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Virtuelle Felder
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Man kann mit Lenses sogar Felder emulieren, die gar nicht da sind. Angenommen
folgender Code:
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Temp = T { _fahrenheit :: Float }
$(makeLenses ''Temp)
-- liefert Lens: fahrenheit :: Lens Temp Float
centigrade :: Lens Temp Float
centigrade centi_fn (T faren)
= (\centi' -> T (cToF centi'))
<$> (centi_fn (fToC faren))
-- cToF & fToC as Converter-Functions defined someplace else
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
Hiermit kann man dann auch Funktionen, die auf Grad-Celsius rechnen auf Daten
anwenden, die eigenlich nur Fahrenheit speichern, aber eine Umrechnung
bereitstellen. Analog kann man auch einen Zeit-Datentypen definieren, der
intern mit Sekunden rechnet (und somit garantiert frei von Fehlern wie -3
Minuten oder 37 Stunden ist)
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Non-Record Strukturen
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Das ganze kann man auch parametrisieren und auf Non-Record-Strukturen anwenden.
Beispielhaft an einer Map verdeutlicht:
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
-- from Data.Lens.At
at :: Ord k => k -> Lens' (Map k v) (Maybe v)
-- oder identisch, wenn man die Lens' auflöst:
at :: Ord k, forall f. Functor f => k -> (Maybe v -> f Maybe v) -> Map k v -> f Map k v
at k mb_fn m
= wrap <$> (mb_fn mv)
where
mv = Map.lookup k m
wrap :: Maybe v -> Map k v
wrap (Just v') = Map.insert k v' m
wrap Nothing = case mv of
Nothing -> m
Just _ -> Map.delete k m
-- mb_fn :: Maybe v -> f Maybe v
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
## Weitere Beispiele
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
- Bitfields auf Strukturen die Bits haben (Ints, ...) in Data.Bits.Lens
- Web-scraper in Package hexpat-lens
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 14:47:51 +00:00
p ^.. _HTML' . to allNodes
. traverse . named "a"
. traverse . ix "href"
. filtered isLocal
. to trimSpaces
~~~~~~~~~~~~~~~~~~
Zieht alle externen Links aus dem gegebenen HTML-Code in p um weitere ziele
fürs crawlen zu finden.
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Erweiterungen
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Bisher hatten wir Lenses nur auf Funktoren F. Die nächstmächtigere Klasse ist
Applicative.
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
type Traversal' s a = forall f. Applicative f
=> (a -> f a) -> (s -> f s)
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
Da wir den Container identisch lassen (weder s noch a wurde angefasst) muss sich
etwas anderes ändern. Statt eines einzelnen Focus erhalten wir viele Foci.
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Was ist ein Applicative überhaupt? Eine schwächere Monade (nur 1x Anwendung und
kein Bind - dafür kann man die beliebig oft hintereinanderhängen).
2022-08-24 11:55:32 +00:00
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- Monade als Applicative:
pure = return
mf <*> mx = do { f <- mf; x <- mx; return (f x) }
~~~~~~~~~~~~~~~~~~
Recap: Was macht eine Lens:
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
data Adress = A { _road :: String
, _city :: String
, _postcode :: String }
road :: Lens' Adress String
road elt_fn (A r c p) = (\r' -> A r' c p) <$> (elt_fn r)
-- | "Hole" | | Thing to put in|
~~~~~~~~~~~~~~~~~~
Wenn man nun road & city gleichzeitig bearbeiten will:
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
addr_strs :: Traversal' Address String
addr_strs elt_fn (A r c p)
= ... (\r' c' -> A r' c' p) .. (elt_fn r) .. (elt_fn c) ..
-- | function with 2 "Holes"| first Thing | second Thing
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
fmap kann nur 1 Loch stopfen, aber nicht mit n Löchern umgehen. Applicative mit
<*> kann das.
2022-08-24 11:55:32 +00:00
Somit gibt sich
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
addr_strs :: Traversal' Address String
addr_strs elt_fn (A r c p)
= pure (\r' c' -> A r' c' p) <*> (elt_fn r) <*> (elt_fn c)
-- lift in Appl. | function with 2 "Holes"| first Thing | second Thing
-- oder kürzer
addr_strs :: Traversal' Address String
addr_strs elt_fn (A r c p)
= (\r' c' -> A r' c' p) <$> (elt_fn r) <*> (elt_fn c)
-- pure x <*> y == x <$> y
~~~~~~~~~~~~~~~~~~
Wie würd eine modify-funktion aussehen?
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
over :: Lens' s a -> (a -> a) -> s -> s
over ln f = runIdentity . ln (Identity . f)
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
over :: Traversal' s a -> (a -> a) -> s -> s
over ln f = runIdentity . ln (Identity . f)
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Der Code ist derselbe - nur der Typ ist generischer. Auch die anderen Dinge
funktioniert diese Erweiterung (für Identity und Const muss man noch ein paar
dummy-Instanzen schreiben um sie von Functor auf Applicative oder Monad zu heben
- konkret reicht hier die Instanzierung von Monoid). In der Lens-Library ist
daher meist Monad m statt Functor f gefordert.
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Wozu dienen die Erweiterungen?
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
Man kann mit Foci sehr selektiv vorgehen. Auch kann man diese durch Funktionen
steuern. Beispisweise eine Funktion anwenden auf
2022-08-24 11:55:32 +00:00
- Jedes 2. Listenelement
- Alle graden Elemente in einem Baum
- Alle Namen in einer Tabelle, deren Gehalt > 10.000€ ist
2022-08-24 14:47:51 +00:00
Traversals und Lenses kann man trivial kombinieren (`lens . lens` => `lens`,
`lens . traversal` => `traversal` etc.)
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
## Wie es in Lens wirklich aussieht
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
In diesem Artikel wurde nur auf Monomorphic Lenses eingegangen. In der richtigen
Library ist eine Lens
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
type Lens' s a = Lens s s a a
type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
sodass sich auch die Typen ändern können um z.B. automatisch einen Konvertierten
(sicheren) Typen aus einer unsicheren Datenstruktur zu geben.
2022-08-24 11:55:32 +00:00
Die modify-Funktion over ist auch
2022-08-24 14:47:51 +00:00
~~~ {.haskell}
2022-08-24 11:55:32 +00:00
> over :: Profunctor p => Setting p s t a b -> p a b -> s -> t
2022-08-24 14:47:51 +00:00
~~~
2022-08-24 11:55:32 +00:00
2022-08-24 14:47:51 +00:00
> *Edward is deeply in thrall to abstractionitis* - Simon Peyton Jones
2022-08-24 11:55:32 +00:00
Lens alleine definiert 39 newtypes, 34 data-types und 194 Typsynonyme...
Ausschnitt
2022-08-25 03:26:25 +00:00
~~~ { .haskell }
2022-08-24 11:55:32 +00:00
-- traverseOf :: Functor f => Iso s t a b -> (a -> f b) -> s -> f t
-- traverseOf :: Functor f => Lens s t a b -> (a -> f b) -> s -> f t
-- traverseOf :: Applicative f => Traversal s t a b -> (a -> f b) -> s -> f t
traverseOf :: Over p f s t a b -> p a (f b) -> s -> f t
~~~~~~~~~~~~~~~~~~
2022-08-24 14:47:51 +00:00
dafuq?