【问题标题】:help with reader monad帮助读者单子
【发布时间】:2010-08-10 17:11:37
【问题描述】:

我是haskell的新手,我必须编写一个上下文感知程序,所以我想我可以使用Reader Monad来保持从文件中读取的上下文,我知道如何读取文件并将内容放入列表中tuplesssomething like [([Char],[Char])],但我不知道如何实现 Reader Monad,以便在不使用命令式样式的情况下使程序的所有组件都可以使用该环境,特别是我不知道如何设置和使用环境,据我了解,我应该将它作为参数提供给所有需要使用 runReader 函数 env 的环境的函数,但我很困惑,有人可以给我一些指示或好的教程吗?提前致谢

【问题讨论】:

  • 您确定首先需要Reader 吗? “使环境对所有组件可用”通常不是在 Haskell 中编写代码的最佳方式。你能更详细地描述你正在从事的任务吗?
  • @Travis Brown:如果您有大量本质上是静态的数据,在整个程序的许多地方都需要按原样使用,并且仅在运行时可用,例如通过加载数据,这可能是有意义的文件。例如,想象一个程序,其中所有文本都本地化并在程序启动时从资源文件加载。
  • 事实上,如果我觉得有什么可疑之处,那就是 [([Char], [Char])] 类型。知道它是一个环境,它听起来有点像一个字符串字典,它应该至少是一个Data.Map.Map String String,如果不是更迷人的东西,比如可爱的bytestring trie
  • 回答 Travis:我正在使用缓存服务器,它根据上下文以不同的方式运行,在我的情况下,上下文是一个 file.txt,可以在服务器工作时修改.阅读了一些关于 reader monad 和 writer monad 的内容后,我认为 reader monad 可以很好地管理环境,而 writer monad 可以用于缓存,但我不确定,因此我向论坛咨询,现在我将检查什么是字节串-trie

标签: haskell monads


【解决方案1】:

使用任何“普通”monad[0] 的基本方案几乎完全一样。本质上:

  • 编写返回单子类型值的函数,如果愿意,可以使用 do 表示法,就像编写 IO 函数(如 main)一样。
  • 为您正在使用的 monad 使用任何特定函数。
  • 使用标准规则相互调用这些函数:
    • 使用<- 绑定来自 same monad 的值以获得“inside”值,从而导致另一个值“run”。
    • 使用let 绑定任何其他值,使其独立于一元结构。
  • 使用特定 monad 的专用“运行”函数来评估 monad 计算并获得最终结果。

这样做,monad 描述的额外功能的所有混乱细节(在这种情况下,传递一个额外的环境参数)都会自动处理。

现在,通常的 Reader 操作是 asklocal

  • ask 是持有环境的一元值;在 do 块中,您使用它的方式与在 IO monad 中使用 getLine 之类的方式相同。
  • local 在 Reader monad 中获取一个提供新环境和计算的函数,在由前者修改的环境中运行后者,然后获取结果并将其放入当前函数中。换句话说,它使用本地修改的环境运行子计算。

“运行”函数是创造性地命名的runReader,它只是在 Reader monad 和环境值中进行计算,使用后者运行前者,并在 monad 之外返回最终结果。

例如,这里有一些函数在 Reader monad 中进行一些无意义的计算,其中环境是表示何时停止的“最大值”:

import Control.Monad.Reader

computeUpToMax :: (Int -> Int) -> Int -> Reader Int [Maybe Int]
computeUpToMax f x = do 
    maxVal <- ask
    let y = f x
    if y > maxVal
        then return []
        else do zs <- local (subtract y) (computeUpToMax f y)
                z <- frob y
                return (z:zs)

frob :: Int -> Reader Int (Maybe Int)
frob y = do
    maxVal <- ask
    let z = maxVal - y
    if z == y 
        then return Nothing
        else return $ Just z

要运行它,你会使用这样的东西:

> runReader (computeUpToMax (+ 1) 0) 9
[Just 8, Just 6, Nothing]

...9 是初始环境。

几乎完全相同的结构可用于其他 monad,例如 StateMaybe[],尽管在后两种情况下,您通常只使用最终的 monadic 结果值而不是使用“运行”功能。

[0]:正常意味着不涉及编译器魔法,最明显的“异常”monad当然是IO

【讨论】:

  • 绑定&gt;&gt;=在哪里发挥作用?
  • @CMCDragonkai: 用于翻译do 块。像x &lt;- foo 这样的行变成了绑定foo &gt;&gt;= \x -&gt;,其中lambda 的主体是do 块的其余部分(翻译版本)。
【解决方案2】:

我认为最简单的方法是看看如何在不使用 Reader 的情况下解决此问题,然后比较翻译版本。这是我正在处理的程序中的一个精简示例,其中环境是一组用于更新显示的回调函数。它稍微复杂一些,因为它使用的是 ReaderT 而不是 Reader,但一切都以基本相同的方式工作。

runProcess :: Env -> State -> Action -> IO State
runProcess env state action = do
  newstate <- processAction state action
  let ufunc = mainUFunc env              -- get the callback to update the display
  ufunc newstate                         -- update the display
  return newstate

现在我将更改它以使用 Reader monad 传递环境。由于代码已经在 IO 中,所以需要使用 monad 转换器版本,ReaderT

runProcessR :: State -> Action -> ReaderT Env IO State
runProcessR state action = do
  newstate <- lift $ processAction state action
  env <- ask                              -- get the environment from the reader
  liftIO $ (mainUFunc env) newstate       -- updating is in IO; it needs to be lifted
  return newstate

此时,程序的主循环基本上是:

loop :: State -> ReaderT Env IO ()
loop = do
  action <- liftIO getAction
  if action == EndLoop
    then return ()
    else do
      st' <- processActionR st action
      loop st'

mainLoop :: IO ()
mainLoop = do
  env <- setUpCallbacks
  let st = initState
  runReaderT $ loop st

这就是您可以使用 Reader 的方式。过去使用环境参数的每个函数都不再需要。不占用环境的函数可以直接使用,如果是monadic,可以直接提升。

【讨论】:

    【解决方案3】:

    这是恕我直言最好的 monad 资源 - All About Monads,这是Reader monad 的一部分。

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-20
      • 2018-06-30
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多