“什么时候GHC可以派生出一个仿函数实例?什么时候不能呢?”
当我们有故意的循环数据结构 . 类型系统不允许我们表达强制循环的意图 . 因此,ghc可以派生出一个类似于我们想要的实例,但不一样 .
Circular data structures 可能是应该以不同的方式实现Functor的唯一情况 . 但话说回来,它会有相同的语义 .
data HalfEdge a = HalfEdge { label :: a , companion :: HalfEdge a }
instance Functor HalfEdge where
fmap f (HalfEdge a (HalfEdge b _)) = fix $ HalfEdge (f a) . HalfEdge (f b)
编辑:
HalfEdges是结构(在计算机图形中已知,3d网格......),表示图形中的无向边,您可以在其中引用任一端 . 通常它们存储更多对邻居HalfEdges,Nodes和Faces的引用 .
newEdge :: a -> a -> HalfEdge a
newEdge a b = fix $ HalfEdge a . HalfEdge b
从语义上讲,没有 fix $ HalfEdge 0 . HalfEdge 1 . HalfEdge 2 ,因为 edges are always composed out of exactly two half edges .
编辑2:
在haskell社区中,引用 "Tying the Knot" 以这种数据结构而闻名 . 它是关于数据结构在语义上是无限的,因为它们循环 . 它们只消耗有限的内存 . 示例:给定 ones = 1:ones ,我们将具有 twos 的这些语义等效实现:
twos = fmap (+1) ones
twos = fix ((+1)(head ones) :)
如果我们遍历(前n个元素) twos 并且仍然具有对该列表开头的引用,则这些实现的速度不同(每次评估1 1对仅一次)和内存消耗(O(n)vs O(1) ) .