我接受的激光聚焦答案对于我之前的一个问题,既令人费解又具有启发性,就在我重新打开我的真实世界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
或者是我的设计被破坏了?
askQ
数据类型编码还编码哪些答案是正确的,以及哪些答案被检查,所以
Question
简单。您可以定义一个小组合器来将 Reader 计算插入到 State 计算中:
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