首页 > 解决方案 > Haskell:如何在纯函数中产生副作用

问题描述

我是一个 Haskell 初学者,我对如何在一些纯函数中呈现一些副作用感到麻烦,即一个非常简单的size函数......

size :: [Int] -> StateT Int IO ()
size = fmap (\x -> do 
                     num <- get
                     put (num + 1)
                   return x)    -- some pseudo code like this... 

我知道有很多错误......return我的意思是这个 lambda 会返回x自身,因此列表的值可能不会改变......事实上,我想用它StateT来呈现一些副作用。我怎么能这样做?谢谢。

标签: haskell

解决方案


首先,在学习过程的这一点上,您可能不应该担心“副作用”。此外,您正在尝试混合两个单子,State并且IO,在您似乎也没有掌握的地步。因此,您可能应该更轻松一些。

IO可以使用 monad 在 monad中执行有状态的操作IORefs,您可以将其视为可变变量。如果我是你,我还不会去那里。然后是Statemonad,粗略地说,它是一种在纯环境中模拟有状态函数的便捷方式。

理论上,您可以将有状态函数f :: a -> b视为 type 的纯函数f :: (a,s) -> (b,s),其中s表示您可以访问和更改的某些状态。上面的内容不太适合 monad 框架,因为在 monad 中m我们想要a -> m b表示有效的函数 from ato b。但是很容易适应。类型 (a,s) -> (b,s)可以非curried得到 a -> s -> (b,s),我们取m bs -> (b,s),所以a -> m b表示a -> s -> (b,s)

所以这就是monad所State s代表的。对于每种类型b,类型State s bs -> (b,s),可以理解为“给我缺少的初始状态s,以便我可以计算 ab和最终状态s。有状态函数a -> State s bis a -> s -> (b,s),可以理解为“这个函数接受一个a并产生一个给定初始状态的计算s会产生结果b和最终状态s

这只是为了让您大致了解它的工作原理。现在这里有一些代码可以满足您的需求。让我们从一个简单的方法开始。

size :: [Int] -> State Int ()
size []     = put 0
size (x:xs) = do size xs
                 num <- get 
                 put (num + 1)

该类型是State Int ()因为您只是在更新整数状态并且不返回任何值(状态是我们所关心的全部)。

该过程与计算大小的常用递归函数非常相似(没有累加器),但我们通过更新状态来完成工作。要运行此示例,只需执行以下操作,

runState (size list) 0 

对于一些list. 请注意,0这里的初始状态无关紧要,因为该算法通过将状态设置0为空列表然后1为每个元素添加状态来工作。

现在是一个以累积方式工作的版本,

sizeAc :: [Int] -> State Int ()
sizeAc []     = return ()
sizeAc (x:xs) = do num <- get 
                   put (num + 1)
                   sizeAc xs

再次运行这个例子,

runState (sizeAc list) 0 

请注意,在这种情况下,您必须将0其用作初始状态。该函数所做的是,对于列表中的每个元素,它通过将状态值加一来更新状态。对于空列表,它什么也不做。

最后是带有 , 的版本map,因为它出现在您最初的尝试中。首先我们实现计数动作。

count :: State Int ()
count = do num <- get 
           put (num + 1)

此操作包括访问状态并使用添加的单元对其进行更新。然后为列表中的每个元素构建一个此类操作的列表。

sizeAux'   :: [Int] -> [State Int ()]
sizeAux' xs = map (\x -> count) xs

注意结果的类型是一个列表。结果是一个列表,其中所有元素都是 action count。然后我们按顺序执行这些动作,使用sequence_,其类型如下(专门用于列表和我们特定的 monad)。

 sequence_ :: [m a] -> m ()
 sequence_ :: [State Int ()] -> State Int ()

结果函数是

size'   :: [Int] -> State Int ()
size' xs = sequence_ (sizeAux' xs) 

再次可以通过它运行,

runState (size' list) 0

并在这里再次注意初始状态0是必不可少的。

在这一点上,这可能仍然有些复杂。您需要更好地理解 monad 类、do 表示法和 State monad 的特殊性。无论如何,这是您应该前进的方向,而不是将 State 与 IO 混为一谈。


推荐阅读