gpt4 book ai didi

haskell - 在 StateT 中组合多个状态

转载 作者:行者123 更新时间:2023-12-03 14:30:43 28 4
gpt4 key购买 nike

我正在编写一个作为守护进程运行的程序。
要创建守护进程,用户需要提供一组
每个所需类的实现(其中一个是数据库)
所有这些类都有功能StateT s IO a 形式的类型签名,
但是 s每个类(class)都不一样。

假设每个类都遵循这种模式:

import Control.Monad (liftM)
import Control.Monad.State (StateT(..), get)

class Hammer h where
driveNail :: StateT h IO ()

data ClawHammer = MkClawHammer Int -- the real implementation is more complex

instance Hammer ClawHammer where
driveNail = return () -- the real implementation is more complex

-- Plus additional classes for wrenches, screwdrivers, etc.

现在我可以定义一个记录,代表由
每个“插槽”的用户。
data MultiTool h = MultiTool {
hammer :: h
-- Plus additional fields for wrenches, screwdrivers, etc.
}

守护进程在 StateT (MultiTool h ...) IO () 中完成大部分工作。
单子(monad)。

现在,由于多功能工具包含锤子,我可以在任何情况下使用它
需要锤子的地方。换句话说, MultiTool类型
如果我编写这样的代码,可以实现它包含的任何类:
stateMap :: Monad m => (s -> t) -> (t -> s) -> StateT s m a -> StateT t m a
stateMap f g (StateT h) = StateT $ liftM (fmap f) . h . g

withHammer :: StateT h IO () -> StateT (MultiTool h) IO ()
withHammer runProgram = do
t <- get
stateMap (\h -> t {hammer=h}) hammer runProgram

instance Hammer h => Hammer (MultiTool h) where
driveNail = withHammer driveNail

但是 withHammer 的实现, withWrench , withScrewdriver , ETC。
基本相同。能写点东西就好了
像这样...
--withMember accessor runProgram = do
-- u <- get
-- stateMap (\h -> u {accessor=h}) accessor runProgram

-- instance Hammer h => Hammer (MultiTool h) where
-- driveNail = withMember hammer driveNail

但这当然不会编译。

我怀疑我的解决方案过于面向对象。
有没有更好的办法?
单子(monad)变压器,也许?
提前感谢您的任何建议。

最佳答案

如果您想使用像您的情况那样的大型全局状态,那么您想要使用的是镜头,正如 Ben 所建议的那样。我也推荐 Edward Kmett 的 镜头 图书馆。但是,还有另一种可能更好的方法。
服务器具有程序连续运行并在状态空间上执行相同操作的特性。当您想要模块化服务器时,麻烦就开始了,在这种情况下,您需要的不仅仅是一些全局状态。您希望模块有自己的状态。
让我们将模块视为将请求转换为响应的东西:

Module :: (Request -> m Response) -> Module m
现在,如果它有某种状态,那么这种状态就会变得引人注目,因为模块下次可能会给出不同的答案。有多种方法可以做到这一点,例如:
Module :: s -> ((Request, s) -> m (Response s)) -> Module m
但是表达这一点的更好和等效的方式是以下构造函数(我们将很快围绕它构建一个类型):
Module :: (Request -> m (Response, Module m)) -> Module m
该模块将请求映射到响应,但同时也会返回其自身的新版本。让我们更进一步,使请求和响应具有多态性:
Module :: (a -> m (b, Module m a b)) -> Module m a b
现在,如果一个模块的输出类型与另一个模块的输入类型匹配,那么您可以像常规函数一样组合它们。该组合是关联的并且具有多态身份。这听起来很像一个类别,事实上它是!它是一个范畴、一个应用仿函数和一个箭头。
newtype Module m a b =
Module (a -> m (b, Module m a b))

instance (Monad m) => Applicative (Module m a)
instance (Monad m) => Arrow (Module m)
instance (Monad m) => Category (Module m)
instance (Monad m) => Functor (Module m a)
我们现在可以组合两个模块,它们有自己的本地状态,甚至都不知道!但这还不够。我们想要更多。可以切换的模块怎么样?让我们扩展我们的小模块系统,以便模块实际上可以选择不给出答案:
newtype Module m a b =
Module (a -> m (Maybe b, Module m a b))
这允许与 (.) 正交的另一种组合形式。 : 现在我们的类型也是 Alternative 的一族仿函数:
instance (Monad m) => Alternative (Module m a)
现在一个模块可以选择是否响应请求,如果不响应,则尝试下一个模块。简单的。您刚刚重新发明了电线类别。 =)
当然,您不需要重新发明它。 Netwire库实现了这种设计模式,并带有一个预定义的“模块”(称为线)的大型库。见 Control.Wire教程的模块。

关于haskell - 在 StateT 中组合多个状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13915923/

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