DeX*_*eX3 8 monads haskell state-monad monad-transformers
我一直在尝试围绕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
Run Code Online (Sandbox Code Playgroud)
所有这些函数都作用于a Editor,并且其中许多函数返回一个新的Editor(插入符号已被移动或某些文本已被更改)所以我认为这可能是Statemonad的一个很好的应用程序,我重写了大多数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 })
Run Code Online (Sandbox Code Playgroud)
这非常棒,因为它允许我在do注释中非常容易地编写编辑操作.
但是,现在我正在努力将其用于实际应用程序中.假设我想Editor在执行某些IO的应用程序中使用它.假设我想操纵Editor每次用户按下l键盘上的键的实例.
我需要另一个Statemonad代表整个应用程序状态,它包含一个Editor实例和一个事件循环,它使用IOmonad从键盘读取并moveHorizontally'通过修改它来调用修改当前的AppState Editor.
我已经阅读了一些关于这个主题的内容,似乎我需要使用Monad Transformers来构建一个底部有IO的monad堆栈.我以前从未使用过Monad变形金刚,我不知道该怎么办?我还发现Statemonad已经实现了一些功能(它似乎是Monad Transformer的一个特例?)但我对如何使用它感到困惑?
首先,让我们回顾一下.最好将问题隔离开来.让纯函数与纯函数,状态 - 状态和IO - 与IO组合在一起.交织多个概念是烹饪代码意大利面的一种方法.你不想吃那顿饭.
话虽如此,让我们恢复您拥有的纯函数并将它们分组到模块中.但是,我们将应用小修改使它们符合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
Run Code Online (Sandbox Code Playgroud)
现在,如果你真的想要恢复你的StateAPI,那么在另一个模块中实现它是微不足道的:
-- |
-- 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)
Run Code Online (Sandbox Code Playgroud)
正如您现在看到的那样,遵循标准约定允许我们使用标准State实用程序,例如gets并将modify已实现的函数简单地提升到Statemonad.
然而,实际上所提到的实用程序也适用于StateTmonad-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)
Run Code Online (Sandbox Code Playgroud)
如您所见,所有改变的都是类型签名.
现在,您可以在变换器堆栈中使用这些常规函数.例如,
-- |
-- 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
Run Code Online (Sandbox Code Playgroud)
因此,我们刚刚实现了关注点的细化隔离以及代码库的极大灵活性.
当然,现在,这是一个非常原始的例子.通常,最终的monad-transformer堆栈具有更多级别.例如,
type Session = ExceptT Text (ReaderT Database (StateT A.Editor IO))
Run Code Online (Sandbox Code Playgroud)
所有这些层次之间跳跃的典型工具集是在lift功能还是在"MTL"库,它提供了型类,以减少使用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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
532 次 |
| 最近记录: |