Tôi thực sự thích ý tưởng về làm việc với catamorphisms/anamorphisms một cách chung chung, nhưng có vẻ như với tôi nó có một hiệu suất nhược điểm đáng kể:Có thể làm cho GHC tối ưu hóa (deforest) các chức năng chung như catamorphisms?
Giả sử chúng ta muốn làm việc với một cấu trúc cây trong cách phân loại - để mô tả gấp khác nhau bằng cách sử dụng chung catamorphism function:
newtype Fix f = Fix { unfix :: f (Fix f) }
data TreeT r = Leaf | Tree r r
instance Functor TreeT where
fmap f Leaf = Leaf
fmap f (Tree l r) = Tree (f l) (f r)
type Tree = Fix TreeT
catam :: (Functor f) => (f a -> a) -> (Fix f -> a)
catam f = f . fmap (catam f) . unfix
Bây giờ chúng ta có thể viết các chức năng như:
depth1 :: Tree -> Int
depth1 = catam g
where
g Leaf = 0
g (Tree l r) = max l r
Thật không may, phương pháp này có một nhược điểm đáng kể: Dur tính toán, các phiên bản mới của TreeT Int
được tạo ở mọi cấp độ trong fmap
chỉ để được tiêu thụ ngay lập tức bởi g
. So với định nghĩa cổ điển
depth2 :: Tree -> Int
depth2 (Fix Leaf) = 0
depth2 (Fix (Tree l r)) = max (depth1 l) (depth1 r)
depth1
của chúng tôi sẽ luôn làm chậm sự căng thẳng không cần thiết trên GC. Một giải pháp là sử dụng hylomorphisms và kết hợp các cây tạo và gấp lại với nhau. Nhưng thường chúng ta không muốn làm điều đó, chúng ta có thể muốn một cây được tạo ra ở một nơi và sau đó đi qua một nơi khác để được xếp lại sau. Hoặc, để được thư mục nhiều lần với catamorphisms khác nhau.
Có cách nào giúp GHC tối ưu hóa depth1
không? Nội dung nào đó như inlining catam g
và sau đó fusing/deforestingg . fmap ...
bên trong?
Tôi đến muộn bên này, nhưng không nên có một '+ 1' ở đâu đó trong trường hợp' Tree' của 'g' (hoặc' depth2') cho hàm để tính chiều sâu của cây? Nếu không, tôi không thể thấy cách 'depth1' hoặc' depth2' có thể trả về bất cứ thứ gì trừ 0. –
Ngoài ra, tôi nghĩ 'depth1' thực ra phải là' depth2' trong định nghĩa 'depth2'. –