2013-05-09 30 views
21

您如何設計和構建您的monadic堆棧?第一次我需要建立一個一元堆棧(使用變壓器)來解決現實世界的問題,但我不能完全肯定,其中爲了堆放變壓器。正如你已經知道,只要計算具有一種* -> *,基本上什麼都可以在變壓器發揮內單子的作用,從而幾個問題:如何設計一元堆棧?

  • 如果某些特定的變壓器是在的頂部堆棧(如ReaderT?WriterT?)
  • 我應該推動設計?直覺?類型? (例如,根據您的API的需要來塑造堆棧)
  • 是否每個堆棧都彼此同構(在某種程度上),還是有可能,如果我錯誤地構建堆棧,我可能最終無法使用某些潛在的monads或有一個大臃腫的lift . lift . liftIO [...]?我的直覺會建議,如果變壓器得到某些情況下(例如MonadReader,MonadIO等,像mtl大多數變壓器做),它不應該順序重要,我把變壓器。

我很有興趣聽聽經驗豐富的哈斯克勒斯關於最佳實踐或經驗法則的經驗。

forever $ print "Thanks!"

A.

回答

21

這需要經驗。有一件事要記住,monad變換器不知道它正在轉換的monad的任何內容,所以外部變量被內部行爲「綁定」。因此,

StateT s (ListT m) a 

首先是由於內部monad的非確定性計算。那麼,正常情況下采取非決定論,你就增加了狀態 - 即非決定論的每個「分支」都會有它自己的狀態。

Constrast與ListT (StateT s m) a,這主要是狀態 - 即只會有整個計算(模m)一個狀態,計算將採取行動的狀態爲「單線程」,因爲這是State手段。非決定性將在此之上 - 因此分支機構將能夠觀察以前失敗分支的狀態變化。 (在這個特定的組合中,這真的很奇怪,而且我從不需要它)。

這裏是一個diagram丹Piponi這給一些有用的直覺:

monad doodles

我還發現它有助於拓展到實現類型,給我的是什麼樣的計算的感覺。 ListT很難擴展,但是您可以將其視爲「非確定性」,並且StateT很容易擴展。所以對於上面的例子,我看看

StateT s (ListT m) a =~ s -> ListT m (a,s) 

即,它需要一個傳入狀態,並返回多個傳出狀態。這給你一個它如何工作的想法。類似的方法是查看您的堆棧所需的run函數的類型 - 它是否與您所擁有的信息和所需信息相匹配?

以下是一些經驗法則。它們不能代替花時間通過擴展和查找來確定你真正需要哪一個,但是如果你只是在某種必要意義上尋找「添加功能」,那麼這可能會有所幫助。

ReaderT,WriterTStateT是最常見的變壓器。首先,它們都是相互通勤的,所以與你放入的順序無關(如果你使用全部三條,考慮使用RWS)。另外,在實踐中,我通常希望在外面使用「更豐富」的變壓器,如ListTLogicTContT

ErrorT and MaybeT通常會在上面三個之外;讓我們來看看MaybeT如何與StateT交互:

MaybeT (StateT s m) a =~ StateT s m (Maybe a) =~ s -> m (Maybe a, s) 
StateT s (MaybeT m) a =~ s -> MaybeT m (a,s) =~ s -> m (Maybe (a,s)) 

MaybeT是在外面,狀態變化是觀察到即使計算失敗。當內部爲MaybeT時,如果計算失敗,則不會得到狀態,因此必須中止發生在失敗計算中的任何狀態更改。你想要哪一個取決於你想要做什麼 - 然而,前者對應於命令式程序員的直覺。 (這不一定需要努力)

我希望這給了你一個關於如何考慮變壓器堆棧的想法,所以你有更多的工具來分析你的堆棧應該是什麼樣子。如果您將問題確定爲單子計算,那麼獲得正確的單子是最重要的決策之一,並非總是那麼簡單。花點時間,探索可能性。

12

這是一個相當寬泛的問題。我只是想給你一些基本的想法。

首先,我建議儘可能保持基本monad多態。這將允許您在純和IO設置中重用代碼。這也會使你的代碼更加可組合。使用像MonadIO這樣的各種類也可以幫助保持代碼的多態性,這通常是件好事。

需要注意的一件重要事情是,monad變換器的順序實際上控制着它們的語義。我最喜歡的例子是將諸如ListT¹和EitherT之類的東西組合起來用於錯誤處理。如果外部有ListT,則整個計算可能會失敗,並顯示錯誤。如果外部有EitherT,則每個分支可以單獨失敗。所以你可以通過改變變形金剛的順序來實際控制錯誤與非確定性相互作用的方式!

如果您使用不依賴於訂單的單子變壓器 - e.g。將ReaderTWriterT合併起來並不重要,我相信 - 然後只需通過耳朵來播放,然後選擇最適合您的應用的播放方式即可。這是一種隨着經驗而變得更容易的選擇。

¹:ListTControl.Monad.Trans有一些問題,所以假設它是ListT done right