Zygomorphism è l'alta falutin' mathsy nome che diamo a pieghe costruito da due semi-reciprocamente funzioni ricorsive. Darò un esempio.
Immaginate una funzione pm :: [Int] -> Int
(per più-meno), che alterna +
e -
alternativamente attraverso un elenco di numeri, in modo tale che pm [v,w,x,y,z] = v - (w + (x - (y + z)))
. È possibile scrivere fuori utilizzando la ricorsione primitiva:
lengthEven :: [a] -> Bool
lengthEven = even . length
pm0 [] = 0
pm0 (x:xs) = if lengthEven xs
then x - pm0 xs
else x + pm0 xs
Chiaramente pm0
non è compositional - è necessario controllare la lunghezza della lista completa in ogni posizione per determinare se si sta aggiungendo o sottraendo. Paramorphism modelli di ricorsione primitiva di questo tipo, quando la funzione di piegatura deve attraversare l'intera sottostruttura ad ogni iterazione della piega. Quindi possiamo almeno riscrivere il codice per conformarci a uno schema stabilito.
paraL :: (a -> [a] -> b -> b) -> b -> [a] -> b
paraL f z [] = z
paraL f z (x:xs) = f x xs (paraL f z xs)
pm1 = paraL (\x xs acc -> if lengthEven xs then x - acc else x + acc) 0
Ma questo è inefficiente. lengthEven
attraversa l'intera lista ad ogni iterazione del paramorfismo risultante in un algoritmo O (n).
Possiamo progredire osservando che sia lengthEven
e para
può essere espresso come una catamorphism con foldr
...
cataL = foldr
lengthEven' = cataL (\_ p -> not p) True
paraL' f z = snd . cataL (\x (xs, acc) -> (x:xs, f x xs acc)) ([], z)
... che suggerisce che potremmo essere in grado di fondere le due operazioni in un unico passaggio sulla lista.
pm2 = snd . cataL (\x (isEven, total) -> (not isEven, if isEven
then x - total
else x + total)) (True, 0)
Abbiamo avuto una piega che dipendeva dal risultato di un'altra piega, e siamo stati in grado di fonderli in un unico attraversamento della lista. Lo Zigomorfismo cattura esattamente questo schema.
zygoL :: (a -> b -> b) -> -- a folding function
(a -> b -> c -> c) -> -- a folding function which depends on the result of the other fold
b -> c -> -- zeroes for the two folds
[a] -> c
zygoL f g z e = snd . cataL (\x (p, q) -> (f x p, g x p q)) (z, e)
Su ogni iterazione della piega, f
vede la sua risposta dalla ultima iterazione come in un catamorphism, ma g
arriva a vedere le risposte entrambe le funzioni. g
si impiglia con f
.
Scriviamo pm
come zigomorfismo utilizzando la prima funzione di piegatura per contare se l'elenco è pari o dispari in lunghezza e il secondo per calcolare il totale.
pm3 = zygoL (\_ p -> not p) (\x isEven total -> if isEven
then x - total
else x + total) True 0
Questo è lo stile classico di programmazione funzionale. Abbiamo una funzione di ordine superiore che fa il grosso del consumare la lista; tutto ciò che dovevamo fare era collegare la logica per aggregare i risultati. La costruzione termina evidentemente (è necessario solo provare la terminazione per foldr
) ed è più efficiente rispetto alla versione originale scritta a mano per l'avvio.
parte: @AlexR sottolinea nei commenti che zygomorphism ha una sorella maggiore chiamato mutumorphism, che cattura la ricorsione reciproca in tutta la sua gloria . mutu
generalizza zygo
in quello entrambe le funzioni di piegatura sono consentite per ispezionare il risultato dell'altro dall'ultima iterazione .
mutuL :: (a -> b -> c -> b) ->
(a -> b -> c -> c) ->
b -> c ->
[a] -> c
mutuL f g z e = snd . cataL (\x (p, q) -> (f x p q, g x p q)) (z, e)
a recuperare zygo
da mutu
semplicemente ignorando l'argomento in più. zygoL f = mutuL (\x p q -> f x p)
Naturalmente, tutti questi modelli pieghevoli generalizzare sulla base di liste al punto di rilevamento di un funtore arbitraria:
newtype Fix f = Fix { unFix :: f (Fix f) }
cata :: Functor f => (f a -> a) -> Fix f -> a
cata f = f . fmap (cata f) . unFix
para :: Functor f => (f (Fix f, a) -> a) -> Fix f -> a
para f = snd . cata (\x -> (Fix $ fmap fst x, f x))
zygo :: Functor f => (f b -> b) -> (f (b, a) -> a) -> Fix f -> a
zygo f g = snd . cata (\x -> (f $ fmap fst x, g x))
mutu :: Functor f => (f (b, a) -> b) -> (f (b, a) -> a) -> Fix f -> a
mutu f g = snd . cata (\x -> (f x, g x))
Confrontare la definizione di zygo
con quella di zygoL
. Si noti inoltre che zygo Fix = para
e che le ultime tre pieghe possono essere implementate in termini di cata
. Nella foldologia tutto è collegato a tutto il resto.
È possibile recuperare la versione di elenco dalla versione generalizzata.
data ListF a r = Nil_ | Cons_ a r deriving Functor
type List a = Fix (ListF a)
zygoL' :: (a -> b -> b) -> (a -> b -> c -> c) -> b -> c -> List a -> c
zygoL' f g z e = zygo k l
where k Nil_ = z
k (Cons_ x y) = f x y
l Nil_ = e
l (Cons_ x (y, z)) = g x y z
pm4 = zygoL' (\_ p -> not p) (\x isEven total -> if isEven
then x - total
else x + total) True 0
conosci il tipo "zygo" e "futu" dovrebbero avere? – epsilonhalbe
'zygo'' Fix' versione: 'zygo :: Functor f => (fb -> b) -> (f (a, b) -> a) -> Correzione f -> a' – haroldcarr
' futu' 'Mu 'versione:' futu :: (Mu b, Functor (PF b)) => Ann b -> (a -> F b (Futu ba)) -> a -> b' (vedi vedere https: // hackage. haskell.org/package/pointless-haskell-0.0.9/docs/Generics-Pointless-RecursionPatterns.html) – haroldcarr