Haskell 同一个单子变压器的不同顺序之间有什么区别?

Haskell 同一个单子变压器的不同顺序之间有什么区别?,haskell,monads,monad-transformers,Haskell,Monads,Monad Transformers,我试图定义一个API来表示程序中的特定类型的过程 newtype Procedure a = { runProcedure :: ? } 有一个状态,由ID到记录的映射组成: type ID = Int data Record = { ... } type ProcedureState = Map ID Record 有三个基本操作: -- Declare the current procedure invalid and bail (similar to some definitions

我试图定义一个API来表示程序中的特定类型的过程

newtype Procedure a = { runProcedure :: ? }
有一个状态,由ID到记录的映射组成:

type ID = Int
data Record = { ... }
type ProcedureState = Map ID Record
有三个基本操作:

-- Declare the current procedure invalid and bail (similar to some definitions of fail for class Monad)
abort :: Procedure ()
-- Get a record from the shared state; abort if the record does not exist.
retrieve :: ID -> Procedure Record
-- Store (or overwrite) a record in the shared state.
store :: ID -> Record -> Procedure ()
我对这些操作有几个目标:

  • 程序可以做出假设 (不同于原始的
    Map.lookup
    call)关于 哪些记录可用,如果可用 他们的任何假设都是错误的, 整个过程返回 失败
  • 可以执行一系列程序 使用
    (从 类(可选)以便落下 返回到使 不同的假设。(类似于 STM's
    orElse
考虑到这些目标,我相信我需要一些
状态
可能
单子的组合

-- Which to choose?
type Procedure a = StateT ProcedureState Maybe a
type Procedure a = MaybeT (State ProcedureState) a
我不知道
的两个顺序可能
状态
的行为会有什么不同有人能解释这两种顺序之间的行为差异吗?

另外,如果你看到我最初的想法有问题(也许我是过度工程化了),请随意指出

结论:
这三个答案都很有帮助,但有一个共同的想法帮助我决定我想要的顺序。通过查看
runMaybeT
/
runStateT
的返回类型,很容易看出哪个组合具有我所寻找的行为。(在我的例子中,我想要返回类型
Maybe(procedurecreate,a)
)。

编辑:我最初是反向获取案例的。现在修好了

monad transformer堆栈的顺序之间的差异实际上只在剥离堆栈层时才起作用

type Procedure a = MaybeT (State ProcedureState) a
在本例中,您首先运行MaybeT,这将导致一个有状态计算,该计算返回一个
可能是一个

type Procedure a = StateT ProcedureState Maybe a
这里的
StateT
是外部单子,这意味着在使用初始状态运行StateT之后,您将得到一个
可能(a,procedurecreat)
。也就是说,计算可能已经成功,也可能没有成功

因此,您选择哪一个取决于您希望如何处理部分计算。使用外部的
MaybeT
,无论计算是否成功,您都会得到某种返回状态,这可能有用,也可能无用。使用外部的
StateT
,可以保证所有有状态事务都有效。根据您的描述,我自己可能会使用
StateT
变体,但我希望两者都可以工作


monad transformer排序的唯一规则是,如果涉及
IO
(或另一个非transformer monad),则它必须位于堆栈的底部。通常,如果需要,人们会使用
error
作为下一个最低级别。

如果您尝试为这两个版本编写“运行”函数,您将能够自己回答这个问题-我没有安装MTL+转换器,所以我自己无法完成。一个将返回(可能是一个州)另一个可能是(一个州)


编辑-我已经截断了我的响应,因为它添加了可能令人困惑的细节。John的回答一针见血。

让我们假设您在
IO
单子中使用了
IORef
而不是
StateT
来存储过程的状态

首先,有两种方法可以让
mzero
(或
fail
)在
IO
单子组合中工作:

  • mzero
    清除整个计算,以便
    mzero x=x
    ;或
  • mzero
    会导致当前计算不返回值,但
    IO
    会保留类型效果
听起来您想要第一个,因此一个过程设置的状态在
s链中为下一个过程“展开”

当然,这种语义是不可能实现的。在运行之前,我们不知道计算是否会调用
mzero
,但这样做可能会产生任意的
IO
效果,比如
发射导弹
,我们无法回滚

现在,让我们尝试用
Maybe
IO
构建两个不同的monad转换器堆栈:

  • 物联网可能
    ——哎呀,这根本不存在
  • MaybeT IO
存在的(
MaybeT IO
)给出了可能的
mzero
行为,不存在的
IOT可能
对应于其他行为


幸运的是,您使用的是
状态过程重述
,其效果可以回滚,而不是
IO
;您需要的monad transformer堆栈是
StateT procedure,可能是
one。

为了补充其他答案,我想描述一下如何在一般情况下解决这个问题。也就是说,给定两个变压器,它们的两个组合的语义是什么

上周,当我开始在一个解析项目中使用monad transformers时,我在这个问题上遇到了很多麻烦。我的方法是创建一个转换类型的表,每当我不确定时,我都会参考它。我是这样做的:

步骤1:创建基本单子类型及其对应变压器类型的表:

transformer           type                  base type (+ parameter order)

---------------------------------------------------------------

MaybeT   m a        m (Maybe a)            b.    Maybe b

StateT s m a        s -> m (a, s)          t b.  t -> (b, t)

ListT    m a        m [a]                  b.    [] b

ErrorT e m a        m (Either e a)         f b.  Either f b

... etc. ...
步骤2:将每个单声道转换器应用于每个基本单声道,用in代替
m
类型参数:

inner         outer         combined type

Maybe         MaybeT        Maybe (Maybe a)
Maybe         StateT        s -> Maybe (a, s)      --  <==  this !!
... etc. ...

State         MaybeT        t -> (Maybe a, t)      --  <== and this !!
State         StateT        s -> t -> ((a, s), t)
... etc. ...
内外组合式
也许,也许,也许(也许a)
也许是StateT s->Maybe(a,s)--(可能是a,t)-->(a,s,t)
... 等
(这一步有点痛苦,因为有二次数的组合……但这对我来说是一个很好的练习,我只需要做一次。)这里对我来说的关键是我写了
#!/usr/bin/env stack
-- stack script --resolver lts-11.14 --package list-t --package transformers

import ListT (ListT, traverse_, fromFoldable)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State (StateT, evalStateT, get, modify)

main :: IO()
main =  putStrLn "#### Task: summing up numbers in a stream"
     >> putStrLn "####       stateful (StateT) stream (ListT) processing"
     >> putStrLn "#### StateT at the base: expected result"
     >> ltst
     >> putStrLn "#### ListT at the base: broken states"
     >> stlt



-- (ListT (StateT IO)) stack
ltst :: IO ()
ltst = evalStateT (traverse_ (\_ -> return ()) ltstOps) 10

ltstOps :: ListT (StateT Int IO) ()
ltstOps = genLTST >>= processLTST >>= printLTST

genLTST :: ListT (StateT Int IO) Int
genLTST = fromFoldable [6,7,8]

processLTST :: Int -> ListT (StateT Int IO) Int
processLTST x = do
    liftIO $ putStrLn "process iteration LTST"
    lift $ modify (+x)
    lift get

printLTST :: Int -> ListT (StateT Int IO) ()
printLTST = liftIO . print



-- (StateT (ListT IO)) stack
stlt :: IO ()
stlt = traverse_ (\_ -> return ())
     $ evalStateT (genSTLT >>= processSTLT >>= printSTLT) 10

genSTLT :: StateT Int (ListT IO) Int
genSTLT = lift $ fromFoldable [6,7,8]

processSTLT :: Int -> StateT Int (ListT IO) Int
processSTLT x = do
    liftIO $ putStrLn "process iteration STLT"
    modify (+x)
    get

printSTLT :: Int -> StateT Int (ListT IO) ()
printSTLT = liftIO . print
$ ./order.hs   
#### Task: summing up numbers in a stream
####       stateful (StateT) stream (ListT) processing
#### StateT at the base: expected result
process iteration LTST
16
process iteration LTST
23
process iteration LTST
31
#### ListT at the base: broken states
process iteration STLT
16
process iteration STLT
17
process iteration STLT
18