Đúng vậy para
. So sánh với catamorphism, hoặc 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
Một số người gọi các paramorphisms là "nguyên thủy đệ quy" trái ngược với catamorphisms ( foldr
) là "lặp".
Trong foldr
đó hai tham số được cung cấp một giá trị được tính toán đệ quy cho mỗi subobject đệ quy của dữ liệu đầu vào (đây, đó là phần cuối của danh sách), para
các tham số của nhận được cả subobject ban đầu và giá trị được tính toán đệ quy từ nó.
Một chức năng ví dụ được thể hiện độc đáo para
là tập hợp các số lượng thích hợp của một danh sách.
suff :: [x] -> [[x]]
suff = para (\ x xs suffxs -> xs : suffxs) []
vậy nên
suff "suffix" = ["uffix", "ffix", "fix", "ix", "x", ""]
Có thể đơn giản hơn vẫn là
safeTail :: [x] -> Maybe [x]
safeTail = para (\ _ xs _ -> Just xs) Nothing
trong đó nhánh "khuyết điểm" bỏ qua đối số được tính toán đệ quy của nó và chỉ trả về phần đuôi. Được đánh giá một cách lười biếng, việc tính toán đệ quy không bao giờ xảy ra và phần đuôi được trích xuất trong thời gian không đổi.
Bạn có thể xác định foldr
bằng cách sử dụng para
khá dễ dàng; nó phức tạp hơn một chút để xác định para
từ đó foldr
, nhưng chắc chắn là có thể và mọi người nên biết nó được thực hiện như thế nào!
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)
Bí quyết để xác định para
với foldr
là để tái tạo một bản sao của dữ liệu gốc, để chúng ta truy cập vào một bản sao của đuôi ở mỗi bước, mặc dù chúng ta không có quyền truy cập vào bản gốc. Cuối cùng, snd
loại bỏ bản sao của đầu vào và chỉ cung cấp giá trị đầu ra. Nó không hiệu quả lắm, nhưng nếu bạn quan tâm đến tính biểu hiện tuyệt đối, nó không para
mang lại nhiều hơn foldr
. Nếu bạn sử dụng foldr
phiên bản -encoded này para
, sau cùng safeTail
sẽ mất thời gian tuyến tính, sao chép phần tử đuôi từng phần tử.
Vậy là xong: para
là một phiên bản tiện lợi hơn foldr
cho phép bạn truy cập ngay vào phần đuôi của danh sách cũng như giá trị được tính từ đó.
Trong trường hợp chung, làm việc với một kiểu dữ liệu được tạo như điểm cố định đệ quy của một hàm
data Fix f = In (f (Fix f))
bạn có
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)
và một lần nữa, cả hai đều có thể xác định được lẫn nhau, para
được định nghĩa cata
bởi cùng một thủ thuật "tạo bản sao"
para psi = snd . cata (\ fxt -> (In (fmap fst fxt), psi fxt))
Một lần nữa, para
không có gì diễn đạt hơn cata
, nhưng thuận tiện hơn nếu bạn cần truy cập dễ dàng vào các cấu trúc con của đầu vào.
Chỉnh sửa: Tôi nhớ một ví dụ hay khác.
Xem xét cây tìm kiếm nhị phân được cung cấp bởi Fix TreeF
nơi
data TreeF sub = Leaf | Node sub Integer sub
và thử xác định chèn cho cây tìm kiếm nhị phân, trước tiên là a cata
, sau đó là a para
. Bạn sẽ tìm thấy para
phiên bản dễ dàng hơn nhiều, vì tại mỗi nút, bạn sẽ cần phải chèn vào một cây con nhưng giữ nguyên trạng thái còn lại.
para f base xs = foldr (uncurry f) base $ zip xs (tail $tails xs)
, methinks.