关于 Monad Transformer Stack pipeline 中不同步骤的不同要求

问题描述 投票:0回答:1

我接受的激光聚焦答案对于我之前的一个问题,既令人费解又具有启发性,就在我重新打开我的真实世界Haskell(多好的一本书!)并决定去额外的努力并将我的程序分割成最微小的部分,每个运行一个通用的 m

 monad,并有足够的 
class
 约束来编译其实现。本质上我想出了这些函数签名:

    给定一个
  • Report
     告诉每个问题的失败率和一组固定的 
    [Question]
    ,这两个分别代表可变状态(因此 
    MonadState Report
    m
     的约束)和不可变状态(因此 
    MonadReader [Question]
    ),据此每个 
    Question
     被分配一个 
    Rational
     被挑选的概率:
    
    distribQ :: (MonadState Report m, MonadReader [Question] m) => m [(Question, Rational)]
    
    
  • 选择一个问题只是从
  • IO
     产生的 
    Question
     分布中选择 
    [(Question, Rational)]
     的问题:
    
    distribQ
    
    
    打乱问题的替代答案(例如,这样人们就不会习惯记住问题 1 有答案 B),也只是 
    pickQ :: MonadIO m => [(Question, Rational)] -> m Question
  • ¹ 的问题
  • IO
    
    
    提出问题需要用户输入(因此 
    shuffleQ :: MonadIO m => Question -> m Question
  • ),此时用户可以继续回答问题,这将导致更新运行时
  • MonadIO
    (因此 
    Report
    ),或者决定 
    q
    uit(因此
    MonadState Report;不知道为什么没有MaybeT
    类...):
    
    MonadMaybe
    
    
    一旦用户通过 
    l
  • 选择答案,就会选中
  • askQ :: (MonadIO m, MonadState Report m) => Question -> MaybeT m Answer ¹,更新 Answer
    (因此为 
    Report
    ),如果答案错误,则会打印正确答案(因此为 
    MonadState Report
    ) ):
    
    MonadIO
    
    
    您可以看到上述签名很好地相互连接,并且实际上 
  • evalAns :: (MonadIO m, MonadState Report m) => Answer -> m ()
函数是这样实现的:

quiz

这或多或少映射到英语
“也许永远运行循环,包括为问题分配分布,选择一个,洗牌其替代方案,询问它,并评估答案;用给定的问题作为不可变状态来执行此操作,并且作为可变状态的故障率报告”
.

上述功能的实现可以在我的仓库

中找到。

现在,我所关心的是......事实上,quiz :: [Question] -> Report -> IO Report quiz qs r = (runMaybeT . forever) (distribQ >>= pickQ >>= shuffleQ >>= askQ >>= evalAns) `runReaderT` qs `execStateT` r

本身并没有改变

askQ

,只是读取它:
Report

在某种程度上,我觉得
askQ :: (MonadIO m, MonadState Report m) => Question -> MaybeT m Answer
askQ q = do
  r <- get
  (ans, e) <- (runMaybeT . forever)
              (getAns r)
              `execStateT` (q, False)
  if e
    then mzero
    else return ans
给了

MonadState Report

比它需要的更多的力量,因为
askQ
就足够了,而不是
r <- ask
,它与
r <- get
一起提供。
但是,看来我真的不能将
put

换成

MonadState

,否则管道会因
而中断
MonadReader

这表明我的尝试导致了
• Couldn't match type ‘[Question]’ with ‘Report’
    arising from a functional dependency between:
      constraint ‘MonadReader
                    Report
                    (Control.Monad.Trans.Reader.ReaderT
                       [Question] (Control.Monad.Trans.State.Strict.StateT Report IO))’
        arising from a use of ‘askQ’
      instance ‘MonadReader r (Control.Monad.Trans.Reader.ReaderT r m)’
        at <no location info>
• In the second argument of ‘(>>=)’, namely ‘askQ’
  In the first argument of ‘(>>=)’, namely
    ‘distribQ >>= pickQ >>= shuffleQ >>= askQ’
  In the first argument of ‘runMaybeT . forever’, namely
    ‘(distribQ >>= pickQ >>= shuffleQ >>= askQ >>= evalAns)’ [-Wdeferred-type-errors]
和想要成为的-

MonadReader [Question]

之间的冲突。
所以问题是双重的:

有没有办法限制

MonadReader Report
    的力量?
  • 或者是我的设计被破坏了?
  • (1)

askQ

数据类型编码还编码哪些答案是正确的,以及哪些答案被检查,所以

Question

简单。
    

您可以定义一个小组合器来将 Reader 计算插入到 State 计算中:
haskell functional-programming monad-transformers state-monad reader-monad
1个回答
0
投票
type Answer = Question

这并不完全完美,因为传递给
readOnly :: MonadState s m => (a -> ReaderT s m b) -> a -> m b
readOnly f a = do
  s <- get
  let m = f a
  runReaderT m s
的函数仍然知道您正在使用的整个

readOnly

,因此它 
可以
 从其参数中要求 
m 。但它允许您为该函数提供更具限制性的签名,承诺它实际上不会修改状态。
这里有一个使用它进行一些愚蠢的 Int 和 String 操作的示例;我将把这个想法应用到你的系统中的练习留给你。

MonadState


© www.soinside.com 2019 - 2024. All rights reserved.