Haskell中的变量问题

很简单的一个问题,、
在Haskell中实现下面Lisp代码的功能。唉,Haskell啊,纯函数式啊,Monad啊,我刚学啊,不知道怎么办啊
:cry: :cry: :cry:

code="common lisp"
(lambda (i) (incf n i)))
[/code]

真的?但是我曾经发现,用Haskell写的一些代码,长度仅有等价的C或者Python代码的一半。

比如这个,高阶函数和List Comprehension比循环简洁的多
[url]http://cloverprince.iteye.com/blog/319072[/url]

但是,真正成问题的是,我不够聪明,以至于有时候我无法指出如何写出精简的代码。

Haskell里变量是不可变的。函数是没有副作用的。所以,不可能去修改一个变量的值。

其实,你的意思是,“调用”一个函数的时候,和某个符号(变量名)x相关的状态被改变;下次调用另一个函数,而且这个函数需要依赖于这个变量x,那么这个函数将返回不同的值。这样也做不到,因为既然是“函数”,那么给定相同的自变量,返回的值总是相同的,否则就不是“函数”了。

可以用State monad模拟这种行为。实际上GHC已经包含了State这种类型。

可以参考这个:[url]http://www.haskell.org/haskellwiki/State_Monad[/url],说实话,不太直观,但后面介绍了标准库的用法。

下面是一个简单的实现。

我们假设状态是Int型,定义为StateType。我们定义一种容器:保存一个值,附带当前的状态。就是那个ValueWithState。所谓“当前状态”,就是那个可以随便改变的Int值。

然后定义一个Action,里面存了一个函数,输入旧状态,输出一个值和一个新状态。可以认为Action就是一步“操作”(可以和之前所有的运算结果相关),同时这步操作可能会改变当前状态。也就理解成“可能会发生副作用的操作”。

我们定义几个特殊操作

第一个是return x。函数return输入变量x产生一个操作,产生的这个操作不改变状态,但是会改变“当前值”,且“当前值”就是x。

第二个是set n。这个操作会直接把当前状态改成n,但是这个操作本身不返回新的值。这里不得不让它返回()。

第三个是get。这个操作会把“当前状态”放到“当前值”里,也就是“读取那个隐藏的状态”。

然后就是add n了。其实有get和set就能随意改变“状态”了,加上这个add比较方便,就是把当前状态增加一些,不返回值(返回())。

然后,为了让它成为Monad,我们必须允许把两个“Action”捆绑在一起,让它们看上去像一个Action。(注意,第二个Action可能依赖于第一个Action的运行结果,所以>>=的定义是 Monad a -> (a -> Monad b) -> Monad b)

实现Monad要求的>>=函数,就是把“把两个Action捆绑起来”装得像一点。稍微有点麻烦。“完成两部操作”,就是:首先,在已知初始状态state0的情况下,完成第一步操作。然后,利用第一步操作的结果value1,制造出第二个操作(因为第二个操作可能依赖第一个)。然后已知第一步操作后的状态state1的条件下,执行第二个操作。这样得到的新值和新状态就是整体执行的结果了。

然后就可以尽情地用do notation执行一些“看上去像命令式”的过程了。

[code="haskell"]module Main where

type StateType = Int

data ValueWithState valueType = ValueWithState StateType valueType deriving Show

data Action resultType = Action {
doAction :: StateType -> (ValueWithState resultType)
}

get = Action {
doAction = \oldState -> ValueWithState oldState oldState
}

set n = Action {
doAction = \oldState -> ValueWithState n ()
}

addOne = Action {
doAction = \oldState -> ValueWithState (oldState+1) ()
}

add n = Action {
doAction = \oldState -> ValueWithState (oldState+n) ()
}

instance Monad Action where
return result = Action {
doAction = \oldState -> ValueWithState oldState result
}

action1 >>= f = Action {
    doAction = \state0 -> 
        let ValueWithState state1 value1 = doAction action1 state0
            action2 = f value1
            ValueWithState state2 value2 = doAction action2 state1
        in ValueWithState state2 value2
}

someAction = do
add 3
s1 <- return "Hello"
add 2
s2 <- return "World"
s3 <- get
return (s1 ++ s2 ++ (show s3))

ValueWithState finalState finalValue = doAction someAction 42

main = do
putStrLn (show finalState)
putStrLn finalValue

[/code]

试试转换一下思维,能用命令式实现的一定能够用函数式方法实现。

可以使用haskell的lambda实现,如下即可:

inc n = \i -> incf n i

可以使用haskell的lambda实现,如下即可:

inc n = \i -> incf n i