我想理解函子不动点的抽象概念,但是,我仍在努力弄清楚它的确切实现及其在 Haskell 中的变态。
例如,如果我根据“程序员的类别理论”一书 - 第 359 页定义,则以下代数
-- (Int, LiftF e Int -> Int)
data ListF e a = NilF | ConsF e a
lenAlg :: ListF e Int -> Int
lenAlg (ConsF e n) -> n + 1
lenAlg NilF = 0
根据变质的定义,可以将以下函数应用于 ListF 的不动点,即 List,以计算其长度。
cata lenAlg :: [e] -> Int
cata lenAlg = lenAlg . fmap (cata lenAlg) . unFix
我有两个困惑。首先,Haskell 编译器如何知道 List 是ListF 的不动点?我从概念上知道它是,但是编译器怎么知道,即,如果我们定义另一个与 List 相同的 List',我敢打赌编译器不会自动推断 List' 也是 ListF 的不动点,或者确实它?(我会很惊讶)。
其次,由于 cata lenAlg 的递归性质,它总是试图 unFix 数据构造函数的外层以暴露函数的内层(顺便说一句,我的这种解释是否正确?)。但是,如果我们已经在叶子节点,我们怎么能调用这个函数呢?
fmap (cata lenAlg) Nil
例如,有人可以帮助为以下函数调用编写执行跟踪以澄清吗?
cata lenAlg Cons 1 (Cons 2 Nil)
我可能遗漏了一些明显的东西,但是我希望这个问题对于其他有类似困惑的人仍然有意义。
答案摘要
@nm 回答了我的第一个问题,指出为了让 Haskell 编译器找出 Functor A 是 Functor B 的不动点,我们需要明确。在这种情况下,它是
type List e = Fix (ListF e)
@luqui 和@Will Ness 指出,由于 fmap 的定义,在叶子上调用 fmap (cata lenAlg),在这种情况下是 NilF,将返回 NilF。
-- f :: a -> b
fmap f (ConsF e a) = ConsF e (f b)
fmap f NilF = NilF
我会接受@nm 的回答,因为它直接解决了我的第一个(更大的)问题,但我确实喜欢所有三个答案。非常感谢您的帮助!