haskell - 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
来呈现一些副作用。我怎么能这样做?谢谢。
解决方案
首先,在学习过程的这一点上,您可能不应该担心“副作用”。此外,您正在尝试混合两个单子,State
并且IO
,在您似乎也没有掌握的地步。因此,您可能应该更轻松一些。
IO
可以使用 monad 在 monad中执行有状态的操作IORefs
,您可以将其视为可变变量。如果我是你,我还不会去那里。然后是State
monad,粗略地说,它是一种在纯环境中模拟有状态函数的便捷方式。
理论上,您可以将有状态函数f :: a -> b
视为 type 的纯函数f :: (a,s) -> (b,s)
,其中s
表示您可以访问和更改的某些状态。上面的内容不太适合 monad 框架,因为在 monad 中m
我们想要a -> m b
表示有效的函数 from a
to b
。但是很容易适应。类型 (a,s) -> (b,s)
可以非curried得到 a -> s -> (b,s)
,我们取m b
为s -> (b,s)
,所以a -> m b
表示a -> s -> (b,s)
。
所以这就是monad所State s
代表的。对于每种类型b
,类型State s b
是s -> (b,s)
,可以理解为“给我缺少的初始状态s
,以便我可以计算 ab
和最终状态s
。有状态函数a -> State s b
is 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 混为一谈。
推荐阅读
- php - 使用 PHP 按列对 CSV 数据进行排序
- ruby - __FILE__ 在使用 binding.pry 时返回不同的值
- python - 可视化文本分类
- c# - 敌人死亡后物品掉落
- c - int *val = otherVal 和 int val = otherVal 之间的指针差异
- javascript - 在 Slack 消息中格式化电话号码?
- bash - 如何将 SQL 调用传递给 AWK 脚本以指示文件上的文本替换?
- json - PostgresSQL JSON 查询
- fancybox-3 - 如何在 fancybox 3 中使用多个或选择器?
- highcharts - 生成服务器端 highchart.js 图表作为图像?