Haskell:如何在纯函数中产生副作用 [英] Haskell: How to make side effects in pure functions

查看:51
本文介绍了Haskell:如何在纯函数中产生副作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm a Haskell beginner and I feel trouble about how to present some side effects in some pure functions, i.e, a very easy size function...

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 呈现一些副作用.我该怎么办?谢谢.

I know there're lots of errors... the return which I mean this lambda returns x itself so that the list's values may not be changed... In fact, I want to use StateT to present some side effect. How could I do this? Thanks.

推荐答案

首先,在学习过程中的这一点上,您可能不必担心副作用".另外,您正在尝试混合两个monad,分别是 State IO ,在您似乎都没有掌握的那一点上.因此,您可能应该更轻松一些.

Firstly, at this point in the learning process you probably shouldn't be worrying about "side effects". Also you're trying to mix two monads, State and IO, at a point where you don't seem to have mastered either. So you probably should take it easier.

可以使用 IORefs IO monad中执行状态操作,您可以将其视为可变变量.如果我是你,我还不会去那里.然后是 State monad,大致来说,它是在纯设置中模拟有状态功能的简便方法.

It is possible to perform stateful actions within the IO monad using IORefs, which you can think of as mutable variables. If I were you I wouldn't go there just yet. Then there is the State monad which, roughly speaking, is a handy way of simulating stateful functions in a pure setting.

从理论上讲,您可以想到一个有状态函数 f :: a->b 作为类型 f ::(a,s)->的纯函数;(b,s),其中 s 代表您可以访问和更改的某种状态.上面的代码不太适合monad框架,因为在monad m 中,我们希望 a->m b 表示从 a b 的有效功能.但这很容易适应.类型(a,s)->(b,s)可以取消获取 a->s->(b,s),我们将 mb 设为 s->(b,s),所以 a->m b 表示 a->s->(b,s).

In theory, you can think of a stateful function f :: a -> b as a pure function of type f :: (a,s) -> (b,s), where s represents some state which you can access and change. The above doesn't quite fit the monad framework, because in a monad m we want a -> m b to represent effectful functions from a to b. But it's easy to adapt. The type (a,s) -> (b,s) can be uncurried to obtain a -> s -> (b,s) and we take m b to be s -> (b,s), so a -> m b represents a -> s -> (b,s).

这就是monad State s 所代表的含义.对于每种 b 类型, State s b 类型为 s->.(b,s),可以理解为给我丢失的初始状态 s ,这样我就可以计算出 b 和最终状态 s .状态函数 a-> State sb a-> s->(b,s),可以读取就像说此函数需要一个 a 并产生一个计算结果,给定初始状态 s 会产生结果 b 和最终状态s .

So this is what the monad State s represents. For every type b the type State s b is s -> (b,s), which can be read as saying "give me the missing initial state s so I can compute a b and a final state s. And a stateful function a -> State s b is a -> s -> (b,s), which can be read as saying "this function takes an a and produces a computation which given an initial state s produces a result b and a final state s.

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

This is just to give you a rough idea of how it works. Now here's some code that does what you want. Let's start with a simple approach.

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

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

The type is State Int () because you are simply updating an integer state and returning no value (the state is all we care about).

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

The procedure is very similar to a usual recursive function for calculating size (without accumulator), but we do the work by updating state. To run this example just do,

runState (size list) 0 

获取某些列表.请注意,此处的初始状态 0 无关紧要,因为该算法的工作原理是将空列表的状态设置为 0 ,然后添加 1 对于每个元素.

for some list. Note the 0 which is the initial state is irrelevant here, because the algorithm works by setting the state as 0 for the empty list then adding 1 for each element.

现在可以累积使用的版本了,

Now a version that works in an accumulating fashion,

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

再次运行此示例即可,

runState (sizeAc list) 0 

请注意,在这种情况下,您必须使用 0 作为初始状态.函数的作用是,对于列表中的每个元素,它都会通过在状态值上加一个来更新状态.对于空列表,它不执行任何操作.

Note in this case you must use 0 as the initial state. What the function does is, for each element of the list it updates the state by adding one to the state's value. For an empty list it does nothing.

最后一个带有 map 的版本,因为它出现在您的初次尝试中.首先,我们执行计数动作.

Finally a version with map, since it appears in your initial attempt. First we implement the counting action.

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

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

This action consists of accessing the state and updating it with an added unit. Then build a list of such actions for each element in the list.

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

请注意结果的类型是列表.结果是一个列表,其中所有元素都是操作 count .然后,我们使用 sequence _ 依次执行这些动作,其类型如下(专门用于列表和特定的monad).

Note the type of the result is a list. The result is a list where all elements are the action count. Then we execute those actions in sequence, using sequence_, whose type is as below (specialised to lists and to our particular monad).

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

结果函数为

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

再次可以通过

runState (size' list) 0

并再次注意初始状态 0 是必不可少的.

and note again here the initial state 0 is essential.

在这一点上,这可能仍然有些复杂.您需要对monad类,状态符号和州monad的特殊性有更好的了解.无论如何,这是您应该去的地方,而不是将State与IO混合使用.

This might still feel somewhat complicated at this point. You'll need to have a better understanding of the monad class, the do notation and the particularities of the State monad. In any case this is where you should be heading, not mixing State with IO.

这篇关于Haskell:如何在纯函数中产生副作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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