还有一次......我可以举一个做我想要的状态monad的例子吗?

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,将值绑定5r然后增加当前状态r,给出最终状态10:

>>> main
10
Run Code Online (Sandbox Code Playgroud)

  • `state sr`与`s - >(r,s)`是同构的,状态的线程是以自然的方式完成的.Monad的主要优点是能够使用do语法和其他Functor/Monad泛型函数. (2认同)
  • @David我鼓励你重新实施`State`作为练习.这真的不难,我希望它能完全清除你刚问的所有问题.你可以从标准库中窃取`State`类型的定义; 然后自己实现函数和monadic函数以及状态检索和更新函数. (2认同)

Pet*_*lák 8

几乎所有你可以用monads做的事情,没有它们你可以做.(当然,有些特殊的像ST,STM,IO等等,但是这是一个不同的故事.)但是:

  • 它们允许您封装许多常见模式,例如在这种情况下进行有状态计算,并隐藏其他需要的细节或样板代码; 和
  • 有很多函数适用于任何(或许多)monad,你可以专注于你正在使用的特定monad.

举一个例子:通常需要有一种提供唯一名称的生成器,比如生成代码等.这可以使用状态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值,它是Stringm需要时生成新内部的一个配方.

然后原始的可以重写为

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的概念,这种抽象水平几乎是不可能的.