這需要經驗。有一件事要記住,monad變換器不知道它正在轉換的monad的任何內容,所以外部變量被內部行爲「綁定」。因此,
StateT s (ListT m) a
首先是由於內部monad的非確定性計算。那麼,正常情況下采取非決定論,你就增加了狀態 - 即非決定論的每個「分支」都會有它自己的狀態。
Constrast與ListT (StateT s m) a
,這主要是狀態 - 即只會有整個計算(模m
)一個狀態,計算將採取行動的狀態爲「單線程」,因爲這是State
手段。非決定性將在此之上 - 因此分支機構將能夠觀察以前失敗分支的狀態變化。 (在這個特定的組合中,這真的很奇怪,而且我從不需要它)。
這裏是一個diagram丹Piponi這給一些有用的直覺:
我還發現它有助於拓展到實現類型,給我的是什麼樣的計算的感覺。 ListT
很難擴展,但是您可以將其視爲「非確定性」,並且StateT
很容易擴展。所以對於上面的例子,我看看
StateT s (ListT m) a =~ s -> ListT m (a,s)
即,它需要一個傳入狀態,並返回多個傳出狀態。這給你一個它如何工作的想法。類似的方法是查看您的堆棧所需的run
函數的類型 - 它是否與您所擁有的信息和所需信息相匹配?
以下是一些經驗法則。它們不能代替花時間通過擴展和查找來確定你真正需要哪一個,但是如果你只是在某種必要意義上尋找「添加功能」,那麼這可能會有所幫助。
ReaderT
,WriterT
和StateT
是最常見的變壓器。首先,它們都是相互通勤的,所以與你放入的順序無關(如果你使用全部三條,考慮使用RWS
)。另外,在實踐中,我通常希望在外面使用「更豐富」的變壓器,如ListT
,LogicT
和ContT
。
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
時,如果計算失敗,則不會得到狀態,因此必須中止發生在失敗計算中的任何狀態更改。你想要哪一個取決於你想要做什麼 - 然而,前者對應於命令式程序員的直覺。 (這不一定需要努力)
我希望這給了你一個關於如何考慮變壓器堆棧的想法,所以你有更多的工具來分析你的堆棧應該是什麼樣子。如果您將問題確定爲單子計算,那麼獲得正確的單子是最重要的決策之一,並非總是那麼簡單。花點時間,探索可能性。