阅读《Learn you a Haskell For Great Good》一书和非常有用的 wiki 书籍文章Haskell Category Theory帮助我克服了将类别对象与编程对象混淆的常见类别错误,我仍然有以下问题:
为什么必须fmap
映射列表的每个元素?
我喜欢它,我只是想了解这在理论上是如何合理的。(或者也许更容易证明使用 HoTT 的合理性?)
在 Scala 表示法中,List
是一个函子,它接受任何类型并将其映射到所有列表类型集合中的类型,例如,它将类型映射到Int
类型List[Int]
,并将函数映射到Int
例如
Int.successor: Int => Int
到Functor[List].fmap(successor) : List[Int] => List[Int]
Int.toString: Int => String
到Functor[List].fmap(toString): List[Int] => List[String]
现在每个实例List[X]
都是一个带有empty
函数(mempty
在 Haskell 中)和combine
函数(mappend
在 Haskell 中)的幺半群。我的猜测是,可以使用 Lists 是 Monoids 的事实来表明map
必须映射列表的所有元素。我的感觉是,如果添加pure
Applicative 中的函数,这会给我们一个列表,其中只有一个其他类型的元素。例如Applicative[List[Int]].pure(1) == List(1)
。由于map(succ)
在这些元素上为我们提供了带有下一个元素的单例列表,因此这涵盖了所有这些子集。然后我想combine
所有这些单例的函数为我们提供了列表的所有其他元素。不知何故,我认为这限制了地图的工作方式。
另一个建议性的论点是map
必须在列表之间映射函数。由于 aList[Int]
中的每个元素都是 Int 类型,如果一个映射到List[String]
一个必须映射它的每个元素,否则一个将不是正确的类型。
因此,这两个论点似乎都指向了正确的方向。但我想知道剩下的路需要什么。
反例?
为什么这不是反例 map 函数?
def map[X,Y](f: X=>Y)(l: List[X]): List[Y] = l match {
case Nil => Nil
case head::tail=> List(f(head))
}
似乎遵守规则
val l1 = List(3,2,1)
val l2 = List(2,10,100)
val plus2 = (x: Int) => x+ 2
val plus5 = (x: Int) => x+5
map(plus2)(List()) == List()
map(plus2)(l1) == List(5)
map(plus5)(l1) == List(8)
map(plus2 compose plus5)(l1) == List(10)
(map(plus2)_ compose map(plus5)_)(l1) == List(10)
啊。但它不符合身份证法。
def id[X](x: X): X = x
map(id[Int] _)(l1) == List(3)
id(l1) == List(3,2,1)