Was sind Paramorphismen?

95

Beim Lesen dieses klassischen Papiers bin ich auf Paramorphismen fixiert. Leider ist der Abschnitt ziemlich dünn und die Wikipedia-Seite sagt nichts.

Meine Haskell-Übersetzung lautet:

para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f base = h
  where
    h []       =   base
    h (x:xs)   =   f x xs (h xs)

Aber ich verstehe das nicht - ich habe keine Intuition für die Typensignatur oder das gewünschte Ergebnis.

Was ist ein Paramorphismus und was sind einige nützliche Beispiele in Aktion?


Ja, ich habe diese Fragen gesehen , aber sie behandeln Paramorphismen nicht direkt und verweisen nur auf Ressourcen , die als Referenz hilfreich sein können, aber nicht als Lernmaterial.

Matt Fenwick
quelle
1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), denkt nach.
Daniel Fischer
Laut dieser Wiki-Seite modellieren Paramorphismen "die primitive Rekursion über induktive Datentypen". Bedeutet das etwas / Hilfe?
Huon
4
Jeremy Gibbons "Fission" -Papier, auf das ich in einem Kommentar zu einer dieser Fragen hingewiesen habe, ist sehr nützliches Lernmaterial. cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf Es funktioniert sehr deutlich durch zahlreiche Rekursionsmuster.
Stephen Tetley
1
Daniels Umschreiben kann vereinfacht werden als para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs . Dies erinnert an Common Lispmaplist .
Will Ness

Antworten:

109

Ja, das ist para. Vergleiche mit Katamorphose oder foldr:

para  :: (a -> [a] -> b -> b) -> b -> [a] -> b
foldr :: (a ->        b -> b) -> b -> [a] -> b

para  c n (x : xs) = c x xs (para c n xs)
foldr c n (x : xs) = c x    (foldr c n xs)
para  c n []       = n
foldr c n []       = n

Einige Leute nennen Paramorphismen "primitive Rekursion", während Katamorphismen ( foldr) "Iteration" sind.

Wenn foldrden beiden Parametern für jedes rekursive Unterobjekt der Eingabedaten ein rekursiv berechneter Wert zugewiesen wird (hier ist dies das Ende der Liste), erhalten paradie Parameter sowohl das ursprüngliche Unterobjekt als auch den daraus rekursiv berechneten Wert.

Eine Beispielfunktion, die sich gut ausdrückt, paraist die Sammlung der richtigen Suffizien einer Liste.

suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []

damit

suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]

Möglicherweise ist es noch einfacher

safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing

in dem der "cons" -Zweig sein rekursiv berechnetes Argument ignoriert und nur den Schwanz zurückgibt. Faul ausgewertet findet die rekursive Berechnung nie statt und der Schwanz wird in konstanter Zeit extrahiert.

Sie können festlegen , foldrmit paraganz leicht; es ist ein wenig schwieriger zu definieren , paraaus foldr, aber es ist durchaus möglich, und jeder sollte wissen , wie es geht!

foldr c n =       para  (\ x  xs  t ->           c x    t)       n
para  c n = snd . foldr (\ x (xs, t) -> (x : xs, c x xs t)) ([], n)

Der Trick , um die Definition paramit foldrist eine rekonstruieren Kopie der Originaldaten, so dass wir bei jedem Schritt Zugriff auf eine Kopie des Schwanzes gewinnen, auch wenn wir keinen Zugriff auf das Original hatten. Am Ende wird snddie Kopie der Eingabe verworfen und nur der Ausgabewert angegeben. Es ist nicht sehr effizient, aber wenn Sie an Ausdruckskraft interessiert sind, erhalten paraSie nicht mehr als foldr. Wenn Sie diese foldr-kodierte Version von verwenden para, dauert safeTailes schließlich linear, das Ende Element für Element zu kopieren.

Das war's also: Es paraist eine bequemere Version, mit foldrder Sie sofort auf das Ende der Liste sowie auf den daraus berechneten Wert zugreifen können.

Im allgemeinen Fall wird mit einem Datentyp gearbeitet, der als rekursiver Fixpunkt eines Funktors generiert wurde

data Fix f = In (f (Fix f))

du hast

cata :: Functor f => (f         t  -> t) -> Fix f -> t
para :: Functor f => (f (Fix f, t) -> t) -> Fix f -> t

cata phi (In ff) = phi (fmap (cata phi) ff)
para psi (In ff) = psi (fmap keepCopy   ff) where
  keepCopy x = (x, para psi x)

und wieder sind die beiden zueinander definierbar, mit paradefinierten catamit dem gleichen „eine Kopie“ Trick

para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))

Auch dies paraist nicht aussagekräftiger als cata, aber bequemer, wenn Sie einen einfachen Zugriff auf Unterstrukturen der Eingabe benötigen.

Edit: Ich erinnerte mich an ein anderes schönes Beispiel.

Betrachten Sie binäre Suchbäume, die durch Fix TreeFwhere angegeben werden

data TreeF sub = Leaf | Node sub Integer sub

und versuchen Sie, die Einfügung für binäre Suchbäume zuerst als cata, dann als zu definieren para. Sie werden die paraVersion viel einfacher finden, da Sie an jedem Knoten einen Teilbaum einfügen müssen, den anderen aber beibehalten müssen.

Schweinearbeiter
quelle