[英]Confused about Haskell Monad Transformers
我很困惑m
應該放在Monad變形金剛的右側?
例如:
WriterT
定義為
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }
而ReaderT
被定義為
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
但不是
newtype ReaderT r m a = ReaderT { runReaderT :: m (r -> a) }
monad m
的放置將取決於應用於底層monad m
的monad變換器的功能和操作,因此它取決於讀者和編寫者應該添加到monad的功能。
有助於記住runReaderT
和runWriterT
並沒有真正做任何事情,盡管他們的名字是暗示性的。 他們只是打開一個新類型,而它們正在包裝的東西正在改變monad m
。
我的意思是,給定一個monad m
,你可以通過考慮類型的monadic動作添加一個讀者:
r -> m a
你可以通過考慮類型的monadic動作為它添加一個編寫器:
m (a, w)
並且您可以通過考慮類型的monadic動作為其添加讀者,編寫者和狀態:
r -> s -> m (a, s, w)
(也就是說,您不需要任何變換器包裝器來執行此操作,但它們可以使它更方便,特別是因為您可以使用現有的運算符,如>>=
和<*>
而不必定義自己的運算符。)
那么,當你將一個閱讀器添加到monad m
,為什么不把m
放在開頭並考慮下面類型的monadic動作?
m (r -> a)
事實上,您可以這樣做,但您很快就會發現這種添加閱讀器的方法實際上並沒有為monad m
添加太多功能。
例如,假設您正在編寫一個應該在值表中查找鍵的函數,並且您希望在讀取器中攜帶該表。 由於查找失敗,您可以在Maybe
monad中執行此操作。 所以,你想寫一些類似的東西:
myLookup :: Key -> Maybe Value
myLookup key = ...
但是,您希望使用提供鍵和值表的閱讀器來增強Maybe
monad。 如果我們使用m (r -> a)
模式,我們得到:
myLookup :: Key -> Maybe ([(Key,Value)] -> Value)
現在,讓我們嘗試實現它:
myLookup k = Just (\tbl -> ...)
我們已經看到了一個問題。 在允許編寫代碼來訪問\\tbl
之前,我們必須提供一個Just
(表示查找已成功)。 也就是說,monadic動作(失敗或返回值成功)不能依賴於r
中的信息,這些信息應該從簽名m (r -> a)
顯而易見。 使用備用r -> ma
模式更強大:
type M a = ([Key,Value]) -> Maybe a
myLookup :: Key -> M Value
myLookup key tbl = Prelude.lookup key tbl
@Thomas_M_DuBuisson給出了另一個例子。 如果我們嘗試讀取輸入文件,我們可能會寫:
readInput :: FilePath -> IO DataToProcess
readInput fp = withFile fp ReadMode $ \h -> ...
在閱讀器中攜帶文件路徑等配置信息會很好,所以讓我們使用模式m (r -> a)
將其轉換為:
data Config = Config { inputFile :: FilePath }
readConfig :: IO (Config -> DataToProcess)
readConfig = ...um...
我們因為無法編寫依賴於配置信息的IO操作而陷入困境。 如果我們使用備用模式r -> ma
,我們將設置:
type M a = Config -> IO a
readConfig :: M DataToProcess
readConfig cfg = withFile (inputFile cfg) ReadMode $ ...
@cdk提出的另一個問題是這個新的“monadic”動作類型:
m (r -> a)
甚至不是一個單子。 它更弱(只是一個應用程序)。
請注意,將單獨的應用程序讀取器添加到monad 仍然有用。 它只需要用於計算結構不依賴於r
的信息的計算中。 (因此,如果底層monad Maybe
允許計算發出錯誤信號,則r
的值可用於計算,但確定計算是否成功必須獨立於r
。)
然而, r -> ma
版本嚴格來說更強大,可以用作monadic和applicative閱讀器。
請注意,某些monadic轉換在多種形式中很有用。 例如,您可以(但有時只有@luqui在評論中指出)以兩種方式向m
monad添加一個編寫器:
m (a, w) -- if m is a monad this is always a monad
(m a, w) -- this is a monad for some, but not all, monads m
如果m
是IO
,則IO (a,w)
比(IO a, w)
更有用 - 對於后者,寫入的w
(例如,錯誤日志)不能取決於執行IO
的結果行動! 而且, (IO a, w)
實際上並不是monad; 這只是一個應用。
另一方面,如果m
是Maybe
,則(Maybe a, w)
寫入計算成功或失敗的內容,而Maybe (a, w)
如果返回Nothing
則丟失所有日志條目。 兩種形式都是monad,可以在不同情況下使用,它們對應於以不同順序堆疊變換器:
MaybeT (Writer w) -- acts like (Maybe a, w)
WriterT w Maybe -- acts like Maybe (a, w)
這同樣不是真正的堆疊Maybe
和Reader
以不同的順序。 這兩個都與“好”的讀者同構r -> Maybe a
:
MaybeT (Reader r)
ReaderT r Maybe
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.