Dav*_*vid 4 haskell state-monad reader-monad
我试图了解读者和/或州monad的实际需求.我见过的所有例子(包括很多关于stackoverflow,因为我已经寻找合适的例子,我可以使用以及各种书籍和博客文章)的形式(伪代码)
f = do
foo <- ask
do something with foo
g = do
foo <- ask
do something else using foo
h = runReader
(
f
g
)
Run Code Online (Sandbox Code Playgroud)
换句话说,调用两个函数并(可能)从一个调用到下一个调用保持某种状态.但是,我没有发现这个例子特别令人信服,因为(我认为)我可以让f返回一些状态,然后将该状态传递给g.
我希望看到一个例子,使用一个整数(比如说)作为要保留的状态,而不是两个顺序调用f然后从中心位置调用g,而不是调用f然后在内部调用g然后在主程序中更改了状态(如果状态monad).
我见过的大多数(实际上是所有)示例花费了大量时间专注于monad的定义,然后展示如何设置单个函数调用.对我来说,这就是能够进行嵌套调用并让状态随身携带,以证明它为什么有用.
Gab*_*lez 14
这是一个调用另一个有状态子例程的有状态子例程的非平凡示例.
import Control.Monad.Trans.State
f :: State Int ()
f = do
r <- g
modify (+ r)
g :: State Int Int
g = do
modify (+ 1)
get
main = print (execState f 4)
Run Code Online (Sandbox Code Playgroud)
在这个例子中,初始状态开始于4,有状态计算开始于f. f内部调用g,将状态递增到5,然后返回当前状态(静止5).这将控制恢复为f,将值绑定5到r然后增加当前状态r,给出最终状态10:
>>> main
10
Run Code Online (Sandbox Code Playgroud)
几乎所有你可以用monads做的事情,没有它们你可以做.(当然,有些特殊的像ST,STM,IO等等,但是这是一个不同的故事.)但是:
举一个例子:通常需要有一种提供唯一名称的生成器,比如生成代码等.这可以使用状态monad轻松完成:每次newName调用时,它都会输出一个新名称并递增内部州:
import Control.Monad.State
import Data.Tree
import qualified Data.Traversable as T
type NameGen = State Int
newName :: NameGen String
newName = state $ \i -> ("x" ++ show i, i + 1)
Run Code Online (Sandbox Code Playgroud)
现在让我们说我们有一棵树有一些缺失值.我们想为他们提供这样生成的名字.幸运的是,有一个泛型函数mapM允许遍历任何monad的任何可遍历结构(没有monad抽象,我们就没有这个函数).现在修复树很容易.对于每个值,我们检查它是否已填充(然后我们只是将return其提升到monad中),如果没有,则提供一个新名称:
fillTree :: Tree (Maybe String) -> NameGen (Tree String)
fillTree = T.mapM (maybe newName return)
Run Code Online (Sandbox Code Playgroud)
想象一下,如果没有monad实现这个函数,只需要显式状态 - 手动通过树并携带状态.最初的想法将在样板代码中完全丢失.而且,该功能对于Tree和非常具体NameGen.
但是对于monad,我们可以走得更远.我们可以参数化名称生成器并构造更通用的函数:
fillTreeM :: (Monad m) => m String -> Tree (Maybe String) -> m (Tree String)
fillTreeM gen = T.mapM (maybe gen return)
Run Code Online (Sandbox Code Playgroud)
注意第一个参数m String.它不是一个恒定的String值,它是String在m需要时生成新内部的一个配方.
然后原始的可以重写为
fillTree' :: Tree (Maybe String) -> NameGen (Tree String)
fillTree' = fillTreeM newName
Run Code Online (Sandbox Code Playgroud)
但是现在我们可以将相同的功能用于许多不同的目的.例如,使用Randmonad并提供随机生成的名称.
或者,在某些时候我们可能会认为没有填充节点的树无效.然后我们只是说,无论我们要求新名称,我们都会中止整个计算.这可以实现
checkTree :: Tree (Maybe String) -> Maybe (Tree String)
checkTree = fillTreeM Nothing
Run Code Online (Sandbox Code Playgroud)
其中,Nothing这里是类型Maybe String,其中,而不是试图产生一个新的名字,中止内计算Maybe单子.
如果没有monad的概念,这种抽象水平几乎是不可能的.