簡體   English   中英

為什么fmap必須映射List的每個元素?

[英]Why must fmap map every element of a List?

閱讀了這本書, 了解了一本Haskell For Great Good ,以及非常有用的維基書籍Haskell分類理論幫助我克服了將類別對象與編程對象混淆的常見類別錯誤,我仍然有以下問題:

為什么fmap必須映射List的每個元素?

我喜歡它,我只想理解這在理論上是如何合理的。 (或者更容易證明使用HoTT?)

在Scala表示法中, List是一個仿函數,它接受任何類型並將其映射到所有列表類型集合中的類型,例如,它將類型Int映射到類型List[Int]並將它映射到Int上的函數,例如

  • Int.successor: Int => Int to Functor[List].fmap(successor) : List[Int] => List[Int]
  • Int.toString: Int => String to Functor[List].fmap(toString): List[Int] => List[String]

現在, List[X]每個實例都是一個具有empty函數 (在Haskell中為mempty )和combine函數 (在Haskell中為mappend )的mappend 我的猜測是,人們可以使用Lists是Monoids這一事實來表明map必須映射列表的所有元素。 我的感覺是,如果從Applicative添加pure函數 ,這給了我們一個只包含其他類型的元素的列表。 例如Applicative[List[Int]].pure(1) == List(1) 由於這些元素上的map(succ)為我們提供了包含下一個元素的單例列表,因此它涵蓋了所有這些子集。 然后我想所有這些單例的combine函數為我們提供了列表中所有其他元素。 不知怎的,我想這限制了地圖的工作方式。

另一個有爭議的論點是map必須在列表之間映射函數。 由於List[Int]中的每個元素都是List[Int]類型,並且如果一個元素映射到List[String]則必須映射它的每個元素,或者一個不是正確的類型。

因此,這兩個論點似乎都指向了正確的方向。 但我想知道剩下的方式需要什么。

反例?

為什么這不是反例地圖功能?

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)

啊。 但它不符合id法。

def id[X](x: X): X = x

map(id[Int] _)(l1) == List(3)
id(l1) == List(3,2,1)

這依賴於稱為“參數化”的理論結果,首先由雷諾茲定義,然后由瓦德勒(以及其他人)開發。 也許關於這個主題的最着名的論文是“免費的定理!” 由瓦德勒。

其核心思想是,從多態型只有一個功能,我們可以得到關於函數的語義的一些信息。 例如:

foo :: a -> a

僅從這種類型,我們可以看到,如果foo終止,它就是身份功能。 直觀地說, foo無法區分不同a s,因為在Haskell中我們沒有例如可以檢查實際運行時類型的Java的instanceof 同樣的,

bar :: a -> b -> a

必須返回第一個參數。 並且baz :: a -> a -> a必須返回第一個或第二個。 並且quz :: a -> (a -> a) -> a必須將函數的某些固定次數應用於第一個參數。 你現在可能已經明白了。

可以從類型推斷的一般屬性非常復雜,但幸運的是它可以通過機械計算。 在范疇論中,這與自然變換的概念有關。

對於map類型,我們獲得以下可怕屬性:

forall t1,t2 in TYPES, f :: t1 -> t2.
 forall t3,t4 in TYPES, g :: t3 -> t4.
  forall p :: t1 -> t3.
   forall q :: t2 -> t4.
    (forall x :: t1. g (p x) = q (f x))
    ==> (forall y :: [t1].
          map_{t3}_{t4} g (map2_{t1}_{t3} p y) =
          map2_{t2}_{t4} q (map_{t1}_{t2} f y))

在上面, map是眾所周知的map函數,而map2是任意函數,它具有類型(a -> b) -> [a] -> [b]

現在,進一步假設map2滿足map2函數定律,特別是map2 id = id 然后我們可以選擇p = idt3 = t1 我們得到了

forall t1,t2 in TYPES, f :: t1 -> t2.
 forall t4 in TYPES, g :: t1 -> t4.
   forall q :: t2 -> t4.
    (forall x :: t1. g x = q (f x))
    ==> (forall y :: [t1].
          map_{t1}_{t4} g (map2_{t1}_{t1} id y) =
          map2_{t2}_{t4} q (map_{t1}_{t2} f y))

map2上應用map2函數法則:

forall t1,t2 in TYPES, f :: t1 -> t2.
 forall t4 in TYPES, g :: t1 -> t4.
   forall q :: t2 -> t4.
    (forall x :: t1. g x = q (f x))
    ==> (forall y :: [t1].
          map_{t1}_{t4} g y =
          map2_{t2}_{t4} q (map_{t1}_{t2} f y))

現在,讓我們選擇t2 = t1f = id

forall t1 in TYPES.
 forall t4 in TYPES, g :: t1 -> t4.
   forall q :: t1 -> t4.
    (forall x :: t1. g x = q x)
    ==> (forall y :: [t1].
          map_{t1}_{t4} g y =
          map2_{t1}_{t4} q (map_{t1}_{t1} id y))

map的仿函數定律:

forall t1, t4 in TYPES.
   forall g :: t1 -> t4, q :: t1 -> t4.
    g = q
    ==> (forall y :: [t1].
          map_{t1}_{t4} g y =
          map2_{t1}_{t4} q y)

意思是

forall t1, t4 in TYPES.
 forall g :: t1 -> t4.
    (forall y :: [t1].
          map_{t1}_{t4} g y =
          map2_{t1}_{t4} g y)

意思是

forall t1, t4 in TYPES.
          map_{t1}_{t4} = map2_{t1}_{t4}

加起來:

如果map2是具有多態類型(a -> b) -> [a] -> [b]任何函數,並且它滿足第一個map2 id = id函數法map2 id = id ,那么map2必須等同於標准map函數。

另請參閱Edward Kmett撰寫相關博客文章

請注意,在Scala中,只有在不使用x.isInstanceOf[]和其他可能會破壞參數的反射工具時,上述情況才會成立。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM