Apa itu paramorfisme?

96

Membaca makalah klasik ini , saya terjebak pada paramorphisms. Sayangnya bagiannya cukup tipis, dan halaman Wikipedia tidak mengatakan apa-apa.

Terjemahan Haskell saya adalah:

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

Tapi saya tidak mengerti itu - saya tidak memiliki intuisi untuk tanda tangan tipe atau hasil yang diinginkan.

Apa itu paramorfisme, dan apa sajakah contoh berguna dalam tindakan?


Ya, saya pernah melihat pertanyaan - pertanyaan ini , tetapi tidak mencakup paramorfisme secara langsung dan hanya menunjuk pada sumber daya yang mungkin berguna sebagai referensi, tetapi tidak sebagai bahan pembelajaran.

Matt Fenwick
sumber
1
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs), methinks.
Daniel Fischer
Menurut halaman wiki ini , paramorphisms "model rekursi primitif atas tipe data induktif". Apakah itu berarti sesuatu / membantu?
huon
4
Makalah "Fisi" Jeremy Gibbons yang saya tunjuk dalam komentar untuk salah satu pertanyaan tersebut adalah bahan pembelajaran yang sangat berguna. cs.ox.ac.uk/jeremy.gibbons/publications/fission.pdf Ia bekerja melalui berbagai pola rekursi dengan sangat jelas.
stephen tetley
1
Penulisan ulang Daniel dapat disederhanakan sebagai para f base xs = foldr g base (init $ tails xs) where g (x:xs) = f x xs . Ini mengingatkan pada Common Lisp'smaplist .
Will Ness

Jawaban:

110

Ya itu para. Bandingkan dengan katamorfisme, atau 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

Beberapa orang menyebut paramorfisme "rekursi primitif" berbeda dengan katamorfisme ( foldr) menjadi "iterasi".

Di mana foldrdua parameter diberi nilai yang dihitung secara rekursif untuk setiap subobjek rekursif dari data masukan (di sini, itu adalah ekor dari daftar), paraparameter mendapatkan subobjek asli dan nilai yang dihitung secara rekursif darinya.

Contoh fungsi yang diekspresikan dengan baik paraadalah kumpulan daftar yang mencukupi.

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

yang seperti itu

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

Mungkin masih lebih sederhana

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

di mana cabang "kontra" mengabaikan argumen yang dihitung secara rekursif dan hanya mengembalikan ekornya. Dievaluasi dengan malas, komputasi rekursif tidak pernah terjadi dan tail diekstraksi dalam waktu yang konstan.

Anda dapat mendefinisikan foldrdengan paracukup mudah; itu sedikit rumit untuk menentukan paradari foldr, tapi itu pasti mungkin, dan semua orang harus tahu bagaimana hal itu dilakukan!

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)

Trik untuk menentukan paradengan foldradalah merekonstruksi salinan data asli, sehingga kami mendapatkan akses ke salinan ekor di setiap langkah, meskipun kami tidak memiliki akses ke aslinya. Pada akhirnya, sndbuang salinan input dan berikan nilai output saja. Ini tidak terlalu efisien, tetapi jika Anda tertarik pada ekspresifitas semata, paramemberi Anda tidak lebih dari foldr. Jika Anda menggunakan foldrversi yang disandikan ini para, maka safeTailakan membutuhkan waktu linier, menyalin elemen ekor demi elemen.

Jadi, begitulah: paraadalah versi yang lebih nyaman foldryang memberi Anda akses langsung ke ekor daftar serta nilai yang dihitung darinya.

Dalam kasus umum, bekerja dengan tipe data yang dihasilkan sebagai titik tetap rekursif dari sebuah functor

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

kamu punya

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)

dan sekali lagi, keduanya dapat didefinisikan satu sama lain, dengan paradidefinisikan dari catatrik "buat salinan" yang sama

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

Sekali lagi, paratidak lebih ekspresif daripada cata, tetapi lebih nyaman jika Anda membutuhkan akses mudah ke substruktur input.

Edit: Saya ingat contoh bagus lainnya.

Pertimbangkan pohon pencarian biner yang diberikan oleh Fix TreeFtempat

data TreeF sub = Leaf | Node sub Integer sub

dan coba definisikan penyisipan untuk pohon penelusuran biner, pertama sebagai a cata, lalu sebagai a para. Anda akan menemukan paraversinya jauh lebih mudah, karena pada setiap node Anda perlu memasukkan satu subpohon tetapi mempertahankan yang lain sebagaimana adanya.

pekerja babi
sumber