簡體   English   中英

“讀者”monad

[英]The “reader” monad

好的,所以編寫器monad允許你將東西寫入[通常]某種容器,並在最后收回容器。 在大多數實現中,“容器”實際上可以是任何幺半群。

現在,還有一個“讀者”monad。 您可能認為 ,這將提供雙重操作 - 從某種容器逐步讀取,一次一個項目。 實際上,這不是通常的讀者monad提供的功能。 (相反,它只是提供了對半全局常量的輕松訪問。)

要真正寫一個單子雙重平時作家單子,我們就需要某種結構是雙重的獨異。

  1. 有沒有人知道這種雙重結構可能是什么?
  2. 有沒有人寫過這個monad? 它有一個眾所周知的名字嗎?

幺半群的雙重是共生的。 回想一下monoid被定義為(某些同構的)

 class Monoid m where
    create :: () -> m
    combine :: (m,m) -> m

有這些法律

 combine (create (),x) = x
 combine (x,create ()) = x
 combine (combine (x,y),z) = combine (x,combine (y,z))

從而

 class Comonoid m where
    delete :: m -> ()
    split :: m -> (m,m)

需要一些標准操作

 first :: (a -> b) -> (a,c) -> (b,c)
 second :: (c -> d) -> (a,c) -> (a,d)

 idL :: ((),x) -> x
 idR :: (x,()) -> x

 assoc :: ((x,y),z) -> (x,(y,z))

有法律的

idL $ first delete $ (split x) = x
idR $ second delete $ (split x) = x
assoc $ first split (split x) = second split (split x)

由於一個原因,這個類型類看起來很怪異。 它有一個實例

instance Comonoid m where
   split x = (x,x)
   delete x = ()

在Haskell中,這是唯一的例子。 我們可以將讀者重新定義為作者的精確對偶,但由於只有一個comonoid實例,我們得到了與標准讀者類型同構的東西。

所有類型都是共生類,這就是“笛卡爾封閉范疇”中的類別“笛卡爾”。 “Monoidal Closed Categories”類似於CCC但沒有這個屬性,並且與子結構類型系統有關。 線性邏輯的部分吸引力在於增加了對稱性,這是一個例子。 而具有子結構類型允許您定義具有更有趣屬性的comonoids(支持資源管理等事情)。 實際上,這提供了一個框架,用於理解C ++中復制構造函數和析構函數的作用(盡管由於指針的存在,C ++不強制執行重要的屬性)。

編輯:comonoids的讀者

newtype Reader r x = Reader {runReader :: r -> x}
forget :: Comonoid m => (m,a) -> a
forget = idL . first delete

instance Comonoid r => Monad (Reader r) where
   return x = Reader $ \r -> forget (r,x)
   m >>= f = \r -> let (r1,r2) = split r in runReader (f (runReader m r1)) r2

ask :: Comonoid r => Reader r r
ask = Reader id

請注意,在上面的代碼中,每個變量在綁定后只使用一次(因此這些變量都將使用線性類型)。 monad法律證明是微不足道的,只要求共生法律起作用。 因此, Reader真的是Writer雙重身份。

我不完全確定幺半群的雙重性應該是什么,但是想到雙重(可能是錯誤的)與某事物相反(僅僅是因為Comonad是Monad的雙重身份,並且具有所有相同的操作但相反的方式)。 而不是基於mappendmempty我會基於:

fold :: (Foldable f, Monoid m) => f m -> m

如果我們將f專門化為一個列表,我們得到:

fold :: Monoid m => [m] -> m

在我看來,這尤其包含了所有的monoid類。

mempty == fold []
mappend x y == fold [x, y]

那么,我猜這個不同的monoid類的對偶是:

unfold :: (Comonoid m) => m -> [m]

這是很多像我看到的hackage的幺因子類在這里

所以在此基礎上,我認為你描述的'讀者'單子會是供應單子 供應monad實際上是值列表的狀態轉換器,因此在任何時候我們都可以選擇從列表中提供項目。 在這種情況下,列表將是unfold.supply monad的結果

我應該強調,我不是哈斯克爾專家,也不是專家理論家。 但這就是你的描述讓我想到的。

供應基於State,這使得它對某些應用來說不是最理想的。 例如,我們可能想要創建一個無限的提供值樹(例如randoms):

tree :: (Something r) => Supply r (Tree r)
tree = Branch <$> supply <*> sequenceA [tree, tree]

但由於Supply基於State,所以所有標簽都是底部,除了樹下最左邊的路徑。

你需要一些可拆分的東西(比如@ PhillipJF的Comonoid )。 但是如果你試圖將它變成Monad則會出現問題:

newtype Supply r a = Supply { runSupply :: r -> a }

instance (Splittable r) => Monad (Supply r) where
    return = Supply . const
    Supply m >>= f = Supply $ \r ->
        let (r',r'') = split r in
        runSupply (f (m r')) r''

因為monad定律需要f >>= return = f ,所以這意味着在(>>=)的定義中r'' = r ..但是,monad定律還要求return x >>= f = fx ,所以r' = r也是如此。 因此,對於Supply是一個monad, split x = (x,x) ,這樣你就可以重新獲得常規的舊Reader了。

在Haskell中使用的很多monad都不是真正的monad - 即它們只滿足一些等價關系的規律。 例如,如果根據法律進行轉換,許多非確定性monad將以不同的順序給出結果。 但是沒關系,如果你只是想知道某個元素是否出現在輸出列表中而不是在哪里 ,那么它仍然是monad。

如果你允許Supply是一個等價關系的monad,那么你可以得到非常重要的分裂。 例如, 價值供應將構建可拆分的實體,這些實體將以未指定的順序(使用unsafe*魔法)從列表中分配出獨特的標簽 - 因此供應單值的供應將是標簽排列的單一元素。 這就是許多應用程序所需要的。 事實上,有一個功能

runSupply :: (forall r. Eq r => Supply r a) -> a

它抽象了這個等價關系,給出了一個明確定義的純接口,因為它允許你對標簽做的唯一事情是看它們是否相等,如果你置換它們就不會改變。 如果這runSupply是你讓上唯一的觀察Supply ,則Supply獨特標簽的供應是一個真正的單子。

暫無
暫無

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

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