我想知道Haskell中的Functor
个实例在多大程度上是由算子法确定的(唯一的)。
由于ghc
可以为至少“普通”数据类型派生Functor
个实例,因此它们似乎必须至少在各种情况下都是唯一的。
为方便起见,Functor
定义和仿函数法则为:
class Functor f where
fmap :: (a -> b) -> f a -> f b
fmap id = id
fmap (g . h) = (fmap g) . (fmap h)
问题:
可以从map
的{{1}}个Functor
实例开始,推导出data List a = Nil | Cons a (List a)
的定义吗?如果是这样,为了做到这一点,必须做出哪些假设?
是否有任何Haskell数据类型有多个Functor
个实例满足函子法?
什么时候可以ghc
派生functor
个实例,什么时候不能呢?
所有这些都取决于我们如何定义平等吗? Functor
法律以值的相等性表示,但我们不要求Functors
拥有Eq
个实例。那么这里有一些选择吗?
关于平等,肯定有一种我称之为“构造函数平等”的概念,它允许我们推断[a,a,a]
对于[a,a,a]
的任何值a
都是“等于”a
任何类型,即使(==)
没有为其定义Functor
。所有其他(有用的)平等概念可能比这种等价关系更粗糙。但我怀疑{{1}}法律中的平等更多是“推理平等”关系,并且可以是特定于应用的。有什么想法吗?
答案 0 :(得分:20)
见Brent Yorgey的Typeclassopedia:
与我们将遇到的其他类型类不同,给定类型最多只有一个Functor的有效实例。这个can be proven通过free theorem表示fmap的类型。实际上,GHC can automatically derive许多数据类型的Functor实例。
答案 1 :(得分:2)
“什么时候GHC可以派生出一个仿函数实例?什么时候不能呢?”
当我们有有意的循环数据结构时。类型系统不允许我们表达强制循环的意图。 所以,ghc 可以派生一个实例,类似到我们想要的那个,但不一样。
循环数据结构 可能是唯一一种应该以不同的方式实现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
,因为边缘总是由两个半边缘组成。
编辑2:
在haskell社区中,引用“绑结”对于这种数据结构是众所周知的。它是关于数据结构在语义上是无限的,因为它们循环。它们只消耗有限的内存。示例:给定ones = 1:ones
,我们将具有twos
的语义等效实现:
twos = fmap (+1) ones
twos = fix ((+1)(head ones) :)
如果我们遍历(前n个元素)twos
并且仍然引用该列表的开头,则这些实现的速度不同(每次评估1 + 1只比一次)和内存消耗(O (n)vs O(1))。