gpt4 book ai didi

haskell - 状态和 IO 单子(monad)

转载 作者:行者123 更新时间:2023-12-04 08:22:32 28 4
gpt4 key购买 nike

我一直在尝试围绕单子(monad)的概念进行思考,并且一直在尝试以下示例:

我有一个 Editor数据类型,表示文本文档的状态和一些处理它的函数。

data Editor = Editor {
lines :: [Line], -- editor contents are kept line by line
lineCount :: Int, -- holds length lines at all times
caret :: Caret -- the current caret position
-- ... some more definitions
} deriving (Show)

-- get the line at the given position (first line is at 0)
lineAt :: Editor -> Int -> Line
lineAt ed n = ls !! n
where
ls = lines ed

-- get the line that the caret is currently on
currentLine :: Editor -> Line
currentLine ed = lineAt ed $ currentY ed

-- move the caret horizontally by the specified amount of characters (can not
-- go beyond the current line)
moveHorizontally :: Editor -> Int -> Editor
moveHorizontally ed n = ed { caret = newPos }
where
Caret x y = caret ed
l = currentLine ed
mx = fromIntegral (L.length l - 1)
newX = clamp 0 mx (x+n)
newPos = Caret newX y


-- ... and lots more functions to work with an Editor

所有这些函数都作用于 Editor ,其中许多返回一个新的 Editor (插入符号已被移动或某些文本已被更改)所以我认为这可能是 State 的一个很好的应用程序monad 和我重写了大部分 Editor -函数现在看起来像这样:
lineAt' :: Int -> State Editor Line
lineAt' n = state $ \ed -> (lines ed !! n, ed)

currentLine' :: State Editor Line
currentLine' = do
y <- currentY'
lineAt' y

moveHorizontally' :: Int -> State Editor ()
moveHorizontally' n = do
(Caret x y) <- gets caret
l <- currentLine'
let mx = fromIntegral (L.length l - 1)
let newX = clamp 0 mx (x+n)
modify (\ed -> ed { caret = Caret newX y })

moveHorizontally' :: Int -> State Editor ()
moveHorizontally' n = do
(Caret x y) <- gets caret
l <- currentLine'
let mx = fromIntegral (L.length l - 1)
let newX = clamp 0 mx (x+n)
modify (\ed -> ed { caret = Caret newX y })

这非常棒,因为它让我可以在 do 中轻松地编写编辑操作。 -符号。

但是,现在我正在努力将其用于实际应用程序中。说我想用这个 Editor在执行某些 IO 的应用程序中。假设我想操作 Editor 的实例每次用户按下 l键盘上的键。

我需要另一个 State表示持有 Editor 的整体应用程序状态的 monad实例和一种使用 IO 的事件循环monad 从键盘读取并调用 moveHorizontally'通过修改其 Editor 来修改当前 AppState .

我已经阅读了一些关于这个主题的内容,似乎我需要使用 Monad 变形金刚 在底部构建一堆带有 IO 的 monad。我以前从未使用过 Monad Transformers,我不知道从这里做什么?我还发现 State monad 已经实现了一些功能(它似乎是 Monad Transformer 的一个特例?)但我对如何使用它感到困惑?

最佳答案

首先,让我们稍微备份一下。将问题隔离出来总是最好的。让纯函数与纯函数、State - 与 State 和 IO - 与 IO 分组。将多个概念交织在一起是 cooking 代码意大利面的一定秘诀。你不想要那顿饭。

话虽如此,让我们恢复您拥有的纯函数并将它们分组到一个模块中。然而,我们将应用小的修改以使它们符合 Haskell 约定 - 即,我们将更改参数顺序:

-- |
-- In this module we provide all the essential functions for
-- manipulation of the Editor type.
module MyLib.Editor where

data Editor = ...

lineAt :: Int -> Editor -> Line

moveHorizontally :: Int -> Editor -> Editor

现在,如果你真的想得到你的 State API 回来了,在另一个模块中实现很简单:
-- |
-- In this module we address the State monad.
module MyLib.State where

import qualified MyLib.Editor as A

lineAt :: Int -> State A.Editor Line
lineAt at = gets (A.lineAt at)

moveHorizontally :: Int -> State A.Editor ()
moveHorizontally by = modify (A.moveHorizontally by)

正如您现在看到的,遵循标准约定允许我们使用标准 State实用程序,如 gets modify 将已实现的功能轻松提升到 State单子(monad)。

但是,实际上提到的实用程序适用于 StateT monad-transformer,其中 State其实只是一个特例。所以我们也可以用更通用的方式实现同​​样的事情:
-- |
-- In this module we address the StateT monad-transformer.
module MyLib.StateT where

import qualified MyLib.Editor as A

lineAt :: Monad m => Int -> StateT A.Editor m Line
lineAt at = gets (A.lineAt at)

moveHorizontally :: Monad m => Int -> StateT A.Editor m ()
moveHorizontally by = modify (A.moveHorizontally by)

如您所见,所有更改的只是类型签名。

现在您可以在变压器堆栈中使用这些通用功能。例如。,
-- |
-- In this module we address the problems of the transformer stack.
module MyLib.Session where

import qualified MyLib.Editor as A
import qualified MyLib.StateT as B

-- | Your trasformer stack
type Session = StateT A.Editor IO

runSession :: Session a -> A.Editor -> IO (a, A.Editor)
runSession = runStateT

lineAt :: Int -> Session Line
lineAt = B.lineAt

moveHorizontally :: Int -> Session ()
moveHorizontally = B.moveHorizontally

-- |
-- A function to lift the IO computation into our stack.
-- Luckily for us it is already presented by the MonadIO type-class.
-- liftIO :: IO a -> Session a

因此,我们刚刚实现了关注点的精细隔离和代码库的极大灵 active 。

当然,到目前为止,这是一个非常原始的例子。通常,最终的 monad-transformer 堆栈具有更多级别。例如。,
type Session = ExceptT Text (ReaderT Database (StateT A.Editor IO))

要在所有这些级别之间跳转,典型的工具集是 the lift functionthe "mtl" library ,它提供类型类以减少 lift 的使用.不过我不得不提一下,并不是每个人(包括我自己)都是“mtl”的粉丝,因为在减少代码量的同时,它引入了一定的歧义和推理复杂性。我更喜欢使用 lift明确地。

转换器的重点是允许您以特别的方式使用一些新功能扩展现有的 monad(转换器堆栈也是一个 monad)。

至于您关于扩展应用程序状态的问题,您可以简单地将另一个 StateT 层添加到您的堆栈中:
-- |
-- In this module we address the problems of the transformer stack.
module MyLib.Session where

import qualified MyLib.Editor as A
-- In presence of competing modules,
-- it's best to rename StateT to the more specific EditorStateT
import qualified MyLib.EditorStateT as B
import qualified MyLib.CounterStateT as C

-- | Your trasformer stack
type Session = StateT Int (StateT A.Editor IO)

lineAt :: Int -> Session Line
lineAt = lift B.lineAt

moveHorizontally :: Int -> Session ()
moveHorizontally = lift B.moveHorizontally

-- | An example of addressing a different level of the stack.
incCounter :: Session ()
incCounter = C.inc

-- | An example of how you can dive deeply into your stack.
liftIO :: IO a -> Session a
liftIO io = lift (lift io)

关于haskell - 状态和 IO 单子(monad),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38890218/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com