在StateT中结合多个状态 [英] Combining multiple states in StateT

查看:98
本文介绍了在StateT中结合多个状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

假设每个类遵循以下模式:

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

class Hammer h其中
driveNail :: StateT h IO()

data ClawHammer = MkClawHammer Int - 真正的实现更复杂

实例Hammer ClawHammer其中
driveNail = return() - 真正的实现更复杂

- 加上扳手,螺丝刀等附加课程

现在我可以定义一个记录,表示用户为每个槽选择
实现的实现。

$ p $ data $ MultiTool h = MultiTool {
hammer :: h
- 加上扳手,螺丝刀等附加字段
}

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

现在,由于multitool 包含一把锤子,我可以在任何需要锤子的情况下使用它
。换句话说,如果我写这样的代码, MultiTool 类型
可以实现它包含的任何类:

  stateMap :: Monad m => (s  - > t) - > (t→s)→> StateT s m a  - > StateT t aa 
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

实例Hammer h => Hammer(MultiTool h)其中
driveNail = withHammer driveNail

但是 withHammer withWrench withScrewdriver 等。
基本上相同。能够写出像这样的
是很好的...

   -  withMember accessor runProgram = do 
- u< - get
- stateMap(\ h - > u {accessor = h})访问器runProgram

- 实例Hammer h => Hammer(MultiTool h)其中
- driveNail = withMember hammer driveNail

但当然不会编译。



我怀疑我的解决方案太面向对象。
有更好的方法吗?
Monad变形金刚,也许?
预先感谢您的任何建议。

解决方案

如果您想要像在您的案例,那么你想要使用的是镜头,正如Ben所建议的那样。我也推荐Edward Kmett的镜头库。然而,还有另一种方法,或许更好。

服务器具有程序连续运行并在状态空间上执行相同操作的属性。当你想要模块化你的服务器时,麻烦就开始了,在这种情况下,你不仅需要一些全局状态。您希望模块具有自己的状态。



让我们将模块视为将 Request 转换为 Response em :::

  Module ::(Request  - > m Response) - >模块m 

现在,如果它有一些状态,那么这个状态会变得明显,因为模块可能会给出下次不同的答案。有很多方法可以做到这一点,例如:

  Module :: s  - > ((Request,s) - > m(Response s)) - >模块m 

但是更好的和等效的表达方式是下面的构造函数(我们将构建一个(请求 - > m(响应,模块m)) - >< / p>

 模块m 

该模块将请求映射到响应,但沿途还会返回新版本的本身。让我们进一步讨论并做出多态的请求和响应:

  Module ::(a  - > m(b,Module mab)) - > Module mab 

现在,如果模块的输出类型与另一个模块的输入类型匹配,那么可以将它们组合像常规功能一样。这种组合是联想性的并且具有多态性特征。这听起来很像一个类别,实际上它是!

  newtype模块mab = 
模块(a - > m(b,Module mab))

instance(Monad m)=> Applicative(Module m a)
instance(Monad m)=>箭头(模块m)
实例(Monad m)=>类别(模块m)
实例(Monad m)=> Functor(Module ma)

现在我们可以编写两个模块,它们拥有各自的本地状态,关于它!但这还不够。我们想要更多。那些可以切换的模块呢?让我们扩展我们的小模块系统,使模块可以选择 not 来给出答案:

  newtype Module mab = 
Module(a - > m(Maybe b,Module mab))

这允许与(。)正交的另一种构图形式:现在,我们的类型也是一族替代函数:

  instance(Monad m)=>替代方案(模块ma)

现在模块可以选择是否响应请求,如果不是,下一个模块将被尝试。简单。您刚刚重新创建了电线类别。 =)



当然,您不需要重新创建它。 Netwire 库实现了这种设计模式,并附带一个预定义模块(称为连线)的大型库。请参阅 Control.Wire 模块获取教程。


I am writing a program that runs as a daemon. To create the daemon, the user supplies a set of implementations for each of the required classes (one of them is a database) All of these classes have functions have type signatures of the form StateT s IO a, but s is different for each class.

Suppose each of the classes follows this pattern:

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.

Now I can define a record that represents the implementation chosen by the user for each "slot".

data MultiTool h = MultiTool {
    hammer :: h
    -- Plus additional fields for wrenches, screwdrivers, etc.
  }

And the daemon does most of its work in the StateT (MultiTool h ...) IO () monad.

Now, since the multitool contains a hammer, I can use it in any situation where a hammer is needed. In other words, the MultiTool type can implement any of the classes it contains, if I write code like this:

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

But the implementations of withHammer, withWrench, withScrewdriver, etc. are basically identical. It would be nice to be able to write something like this...

--withMember accessor runProgram = do
--  u <- get
--  stateMap (\h -> u {accessor=h}) accessor runProgram

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

But of course that won't compile.

I suspect my solution is too object-oriented. Is there a better way? Monad transformers, maybe? Thank you in advance for any suggestions.

解决方案

If you want to go with a large global state like in your case, then what you want to use is lenses, as suggested by Ben. I too recommend Edward Kmett's lens library. However, there is another, perhaps nicer way.

Servers have the property that the program runs continuously and performs the same operation over a state space. The trouble starts when you want to modularize your server, in which case you want more than just some global state. You want modules to have their own state.

Let's think of a module as something that transforms a Request to a Response:

Module :: (Request -> m Response) -> Module m

Now if it has some state, then this state becomes noticable in that the module might give a different answer the next time. There are a number of ways to do this, for example the following:

Module :: s -> ((Request, s) -> m (Response s)) -> Module m

But a much nicer and equivalent way to express this is the following constructor (we will build a type around it soon):

Module :: (Request -> m (Response, Module m)) -> Module m

This module maps a request to a response, but along the way also returns a new version of itself. Let's go further and make requests and responses polymorphic:

Module :: (a -> m (b, Module m a b)) -> Module m a b

Now if the output type of a module matches another module's input type, then you can compose them like regular functions. This composition is associative and has a polymorphic identity. This sounds a lot like a category, and in fact it is! It is a category, an applicative functor and an arrow.

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)

We can now compose two modules that have their own individual local state without even knowing about it! But that's not sufficient. We want more. How about modules that can be switched among? Let's extend our little module system such that modules can actually choose not to give an answer:

newtype Module m a b =
    Module (a -> m (Maybe b, Module m a b))

This allows another form of composition that is orthogonal to (.): Now our type is also a family of Alternative functors:

instance (Monad m) => Alternative (Module m a)

Now a module can choose whether to respond to a request, and if not, the next module will be tried. Simple. You have just reinvented the wire category. =)

Of course you don't need to reinvent this. The Netwire library implements this design pattern and comes with a large library of predefined "modules" (called wires). See the Control.Wire module for a tutorial.

这篇关于在StateT中结合多个状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆