Haskell:Monad变换器和全局状态

3
我正在学习 Haskell,试图编写一个包含“全局状态”变量的程序: Vars 。我想在每次调用函数时更改状态中的某个组件(例如var1)。更改可以是对组件的简单函数(例如 +4)。此外,它还会打印出更改了的组件。到目前为止,这是我的工作成果(但我被卡住了)。编辑:运行代码后,我想看到全局状态的最新版本。
import Control.Monad.State
import Control.Monad.IO.Class (liftIO)

data Vars = Vars {
 var1 :: Int,
 var2 :: Float
} deriving (Show)

sample :: StateT Vars IO a
sample = do 
        a <- change
        liftIO $ print a
        -- I want to call change again and apply more change to the state


change  :: StateT Vars IO a
change  = do
        dd <- get
         -- I don't know what to do next!

main = do 
  runStateT sample (Vars 20 3)
  evalStateT sample (Vars 20 3)

请问您可以指定程序所需的输出吗?在运行“main”函数后,您希望观察到什么? - Shersh
@Shersh 谢谢你的提示。我已经完成了。 - 4xx
1个回答

2

让我们从简单和小的部分开始逐步解决您的问题。这是编程中很重要的技能,FP以一种不错的方式教授这种技能。此外,使用 State monad,特别是在monad-transformers中使用多个效果,有助于您思考效果并更好地理解事物。

  1. You want to update var1 inside your immutable data type. This can be done only by creating new object. So let's write such function:

    plusFour :: Vars -> Vars
    plusFour (Vars v1 v2) = Vars (v1 + 4) v2
    

    There exist ways in Haskell to write this function much shorter though less understandable, but we don't care about those things now.

  2. Now you want to use this function inside State monad to update immutable state and by this simulate mutability. What can be told about this function only by looking at its type signature: change :: StateT Vars IO a? We can say that this function have several effects: it has access to Vars state and it can do arbitrary IO actions. Also this function returns value of type a. Hmm, this last one is strange. What is a? What this function should return? In imperative programming this function will have type void or Unit. It just do things, it doesn't return everything. Only updates context. So it's result type should be (). It can be different. For example we might want to return new Vars after change. But this is generally bad approach in programming. It makes this function more complex.

  3. After we understood what type function should have (try to always start with defining types) we can implement it. We want to change our state. There're functions that operates with stateful parts of our context. Basically, you interested in this one:

    modify :: Monad m => (s -> s) -> StateT s m ()

    modify function takes function which updates state. After you run this function you can observe that state is modified according to passed function. Now change can be written like this:

    change :: StateT Vars IO ()
    change = modify plusFour
    

    You can implement modify (and thus change using only put and get functions which is nice exercise for beginner).

  4. Let's now call change function from some other function. What does calling mean in this case? It means that you execute monadic action change. This action changes your context, you don't care about it's result because it's (). But if you run get function (which binds whole state to variable) after change you can observe new change. If you want to print only changed component, like var1 you can use gets function. And, again, what type should sample have? What should it return? If on the caller side you're interested only in resulting state, then, again, it should be () like this:

    sample :: StateT Vars IO ()
    sample = do
        change
        v1 <- gets var1
        liftIO $ print v1
        change
        v1' <- gets var1 
        liftIO $ print v1'  -- this should be v1 + 4
    

这应该能让你更好地理解正在发生的事情。Monad transformers需要一些时间来适应它们,尽管它是一个强大的工具(不完美但非常有用)。

作为附注,我想补充说,使用常见的Haskell设计模式可以更好地编写这些函数。但是,你现在不需要关心这些,只需尝试理解这里正在发生的事情。


非常感谢您的精确回答!您所说的“设计模式”是指“镜头”吗? - 4xx
@4xx 《Lenses》是一个库,但它带来了该模式的一部分。我的意思是,这种类型的change函数应该是change :: (MonadState s m, HasVar1 s) => m () - Shersh
请问您能否向我推荐一些资料,解释为什么“change”应该以那种方式书写? - 4xx
1
@4xx 这个视频非常好,而且非常清晰:https://www.youtube.com/watch?v=GZPup5Iuaqw - Shersh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接