[英]What is the use of Applicative/Monad instances for Sum and Product?
[英]To what extent are Applicative/Monad instances uniquely determined?
如本問題所述, Functor
實例是唯一確定的(如果存在)。
對於列表,有兩個眾所周知的Applicative實例: []
和ZipList
。 因此, Applicative不是唯一的 (另請參見GHC可以為monad轉換器派生Functor和Applicative實例嗎? 為什么沒有-XDeriveApplicative
擴展? )。 但是, ZipList
需要無限列表,因為其pure
無限期地重復給定元素。
Applicative
實例的數據結構示例,也許是更好的示例? 更進一步,如果我們可以將[]
和ZipList
都擴展為Monad,我們將有一個示例,其中monad不是由數據類型及其Functor唯一地確定的。 ZipList
只有當我們將自己限制在無限列表 ( 流 )中時, ZipList
才具有Monad實例。 return
[]
會創建一個單元素列表,因此它需要有限列表。 因此:
在有兩個或多個不同實例的示例的情況下,如果它們必須/可以具有相同的Applicative實例,就會出現一個明顯的問題:
最后,我們可以對Alternative / MonadPlus提出相同的問題。 由於存在兩種不同的MonadPlus法則 ,這使情況變得復雜。 假設我們接受其中一組定律(對於應用,我們接受右/左分布/吸收 ,另請參見此問題 ),
如果以上任何一個都不是唯一的,我很想知道為什么,以得到一個提示。 如果不是,則為反例。
首先,由於Monoid
不是唯一的,所以Writer
Monad
或Applicative
都不一樣。 考慮
data M a = M Int a
那么您可以將Applicative
和Monad
實例同構為以下任意一個:
Writer (Sum Int)
Writer (Product Int)
給定類型s
的Monoid
實例,另一個具有不同Applicative
/ Monad
實例的同構對是:
ReaderT s (Writer s)
State s
至於將一個Applicative
實例擴展到兩個不同的Monad
,我不記得任何示例。 但是,當我試圖完全說服ZipList
是否真的不能成為Monad
,我發現以下對Monad
嚴格限制:
join (fmap (\x -> fmap (\y -> f x y) ys) xs) = f <$> xs <*> ys
但是,這並不能為所有值提供join
:對於列表,限制值是所有元素都具有相同長度的值,即具有“矩形”形狀的列表的列表。
(對於Reader
monad,其monadic值的“形狀”沒有變化,實際上它們都是m (mx)
值,因此它們確實具有唯一的擴展名。編輯:來思考一下, Either
, Maybe
和Writer
也只有“矩形” m (mx)
值,因此它們從Applicative
到Monad
的擴展名也是唯一的。)
但是,如果存在帶有兩個Monad
的Applicative
,我不會感到驚訝。
對於Alternative
/ MonadPlus
,對於使用“左分布”定律而不是“左捕獲” 定律的實例, 我無法回憶起任何定律 ,我看不出有什么可以阻止您僅將(<|>)
與flip (<|>)
交換。 我不知道是否有比較小的變化。
附錄:我突然想起我找到了一個示例Applicative
有兩個Monad
秒。 即,有限列表。 這是通常的Monad []
實例,但是您可以用以下函數替換其join
(基本上使空列表具有“感染性”):
ljoin xs
| any null xs = []
| otherwise = concat xs
(A,列表必須是有限的,因為否則null
檢查將永遠不會完成,並且會破壞join . fmap return == id
monad law。)
它與矩形列表中的join
/ concat
具有相同的值,因此具有相同的Applicative
。 我記得,事實證明,前兩個monad定律是自動產生的,您只需要檢查ljoin . ljoin == ljoin . fmap ljoin
ljoin . ljoin == ljoin . fmap ljoin
ljoin . ljoin == ljoin . fmap ljoin
。
鑒於每個Applicative
都有一個Backwards
對應項,
newtype Backwards f x = Backwards {backwards :: f x}
instance Applicative f => Applicative (Backwards f) where
pure x = Backwards (pure x)
Backwards ff <*> Backwards fs = Backwards (flip ($) <$> fs <*> ff)
對Applicative
唯一地確定是不尋常的,就像許多集合以多種方式擴展到類半體一樣(這是很不相關的)。
在此答案中 ,我將練習為非空列表至少查找四個不同的有效Applicative
實例:我不會在這里寵壞它,但會為如何狩獵提供很大的提示。
同時,在最近的一些出色的工作中(我幾個月前在一家暑期學校看到的),Tarmo Uustalu展示了一種解決此問題的巧妙方法,至少在底層函子是容器的情況下 ,雅培,阿爾滕基希和加尼。
警告:前面的依賴類型!
什么是容器? 如果您要處理依賴類型,則可以由兩個組件確定,均勻地呈現類似容器的函子F
直到同構為止,FX中的容器數據結構由某些形狀s:S和一些函數e:P s-> X的相關對提供,這些告訴您位於每個位置的元素。 也就是說,我們定義了容器的擴展名
(S <| P) X = (s : S) * (P s -> X)
(順便說一句,如果您將->
讀為反冪,它看起來很像廣義冪級數)。 三角形應該讓您想起樹形節點的側面,元素s:S標記頂點,基線表示位置集P s。 我們說某些函子是容器,如果它與某些S <| P
同構S <| P
S <| P
。
在Haskell中,您可以輕松地采用S = F ()
,但是構造P
可能會占用很多類型黑客。 但這是您可以在家嘗試的方法。 您會發現容器在所有常見的多項式類型形成操作以及標識,
Id ~= () <| \ _ -> ()
構圖,其中整個形狀僅由一個外部形狀和每個外部位置的內部形狀組成,
(S0 <| P0) . (S1 <| P1) ~= ((S0 <| P0) S1) <| \ (s0, e0) -> (p0 : P0, P1 (e0 p0))
還有其他一些東西,特別是張量 ,那里有一個外部形狀和一個內部形狀(因此“外部”和“內部”可以互換)
(S0 <| P0) (X) (S1 <| P1) = ((S0, S1) <| \ (s0, s1) -> (P0 s0, P1 s1))
因此, F (X) G
表示“ G
結構的F
結構-相同形狀”,例如[] (X) []
表示矩形列表。 但是我離題
容器之間的多態函數每個多態函數
m : forall X. (S0 <| P0) X -> (S1 <| P1) X
可以通過容器形態來實現, 容器形態由兩個組件以非常特殊的方式構造。
f : S0 -> S1
將輸入形狀映射到輸出形狀; g : (s0 : S0) -> P1 (f s0) -> P0 s0
將輸出位置映射到輸入位置。 那么我們的多態函數是
\ (s0, e0) -> (f s0, e0 . g s0)
其中從輸入形狀計算出輸出形狀,然后通過從輸入位置拾取元素來填充輸出位置。
(如果您是Peter Hancock,那么您對正在發生的事情有一個完全的比喻。形狀是命令;位置是響應;容器形態是設備驅動程序 ,一種方式翻譯命令,然后另一種方式響應。)
每個容器的態素都為您提供了一個多態函數,但反之亦然。 給定這樣一個m,我們可能會
(f s, g s) = m (s, id)
也就是說,我們有一個表示定理 ,說兩個容器之間的每個多態函數都由這樣一個f
, g
對給出。
那么Applicative
呢? 在構建所有這些機器的過程中,我們有點迷路了。 但它是值得的。 當monads和appadatives的基礎函子為容器時,多態函數pure
和<*>
, return
和join
必須由容器態射的相關概念表示。
首先,讓我們使用應用程序的半形形式。 我們需要
unit : () -> (S <| P) ()
mult : forall X, Y. ((S <| P) X, (S <| P) Y) -> (S <| P) (X, Y)
形狀的從左到右的地圖要求我們提供
unitS : () -> S
multS : (S, S) -> S
所以看起來我們可能需要一個半身像。 當您檢查適用法律時,您會發現我們恰恰需要一個半同等式。 為容器配備可應用的結構是通過適當的位置尊重操作來精確地將單面體結構細化為其形狀。 unit
無關(因為沒有源位置的選擇),但是對於mult
,我們需要在
multS (s0, s1) = s
我們有
multP (s0, s1) : P s -> (P s0, P s1)
滿足適當的身份和關聯性條件。 如果切換到漢考克的解釋,我們將為命令定義一個等分線(跳過,分號),在選擇第二個命令之前,無法查看對第一個命令的響應,就像命令是一副打孔卡一樣。 我們必須能夠將對組合命令的響應分成對單個命令的單個響應。
因此,形狀上的每個monoid都為我們提供了潛在的應用結構。 對於列表,形狀是數字(長度),並且有很多可供選擇的monoid。 即使形狀存在於Bool
,我們也有很多選擇。
那Monad
呢? 同時,對於M〜 M ~= S <| P
單子M
M ~= S <| P
。 我們需要
return : Id -> M
join : M . M -> M
首先看形狀,這意味着我們需要一種偏斜的類半體。
return_f : () -> S
join_f : (S <| P) S -> S -- (s : S, P s -> S) -> S
這是不平衡的,因為我們在右側獲得了很多形狀,而不僅僅是一個。 如果切換到漢考克的解釋,我們將為命令定義一種順序組成,在這種情況下,我們會根據第一個響應選擇第二個命令,就像在電傳打字機上進行交互一樣。 從幾何角度上講,我們正在解釋如何將樹的兩層粘合為一層。 如果這種組合物是獨特的,那將是非常令人驚訝的。
同樣,對於位置,我們必須以連貫的方式將單個輸出位置映射為對。 這對於單子來說比較棘手:我們首先選擇一個外部位置(響應),然后我們必須選擇一個適合於在第一個位置(在第一個響應之后選擇)的形狀(命令)的內部位置(響應)。
我很想鏈接到Tarmo的工作中,以獲取詳細信息,但似乎還沒有出現。 他實際上已經使用此分析來枚舉用於基礎容器的幾種選擇的所有可能的monad結構。 我期待着這篇論文!
編輯。 通過尊重其他答案,我應該觀察到,當無處不在P s = ()
,則(S <| P) X ~= (S, X)
與monad /應用結構彼此完全一致,並且與S
上的monoid結構。 也就是說,對於編寫器monad,我們只需要選擇形狀級別的操作,因為在每種情況下,值的位置恰好只有一個位置。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.