如何在函数式编程中增加变量

时间:2012-08-12 12:24:26

标签: haskell functional-programming

如何在函数式编程语言中增加变量?

例如,我想这样做:

main :: IO ()
main = do
    let i = 0
    i = i + 1
    print i

预期产出:1。

4 个答案:

答案 0 :(得分:21)

简单的方法是引入变量名的 shadowing

main :: IO ()                  -- another way, simpler, specific to monads:
main = do                         main = do
    let i = 0                         let i = 0
    let j = i                         i <- return (i+1)
    let i = j+1                       print i
    print i                    -- because monadic bind is non-recursive

打印1

只是写let i = i+1不起作用,因为Haskell中的 let 会生成递归定义 - 它实际上是Scheme的 {{1 }} 即可。 letrec右侧的i指的是左侧的let i = i+1 - 指向上级i可能是有意的。因此,我们通过引入另一个变量i来打破这个等式。

另一种更简单的方法是在j - 符号中使用monadic bind <-。这是可能的,因为monadic绑定递归。

在这两种情况下,我们都使用相同的名称引入 new 变量,从而“遮蔽”旧实体,即使其不再可访问。

如何“思考功能”

这里要理解的一点是,使用纯 - 不可变 - 值的函数式编程(就像我们在Haskell中一样)迫使我们使时间显式在我们的代码中。

在命令式设置时间是隐式的。我们“改变”我们的变量 - 但任何变化都是顺序。我们永远不会改变var 之前的内容 - 从现在开始只有 。< / p>

在纯函数式编程中,这只是明确的。这可以采用的最简单的形式之一是在命令式编程中使用值列表作为顺序更改的 记录 。更简单的是使用不同的变量来表示实体在不同时间点的不同值(参见static single assignment form或SSA)。

因此,我们不是“改变”那些无法真正改变的东西,而是制作它的增强副本,然后传递 ,用它代替旧东西。

答案 1 :(得分:6)

作为一般规则,你没有(而且你不需要)。但是,为了完整性。

import Data.IORef
main = do
    i <- newIORef 0       -- new IORef i
    modifyIORef i (+1)    -- increase it by 1
    readIORef i >>= print -- print it

但是,任何说你需要使用像MVar,IORef,STRef等的答案都是错误的。有一种纯粹的功能性方法,在这个小的快速编写的例子中看起来并不是很好。

import Control.Monad.State
type Lens a b = ((a -> b -> a), (a -> b))
setL = fst
getL = snd
modifyL :: Lens a b -> a -> (b -> b) -> a
modifyL lens x f = setL lens x (f (getL lens x))
lensComp :: Lens b c -> Lens a b -> Lens a c
lensComp (set1, get1) (set2, get2) =         -- Compose two lenses
    (\s x -> set2 s (set1 (get2 s) x)        -- Not needed here
     , get1 . get2)                          -- But added for completeness

(+=) :: (Num b) => Lens a b -> Lens a b -> State a ()
x += y = do
    s <- get
    put (modifyL x s (+ (getL y s)))

swap :: Lens a b -> Lens a b -> State a ()
swap x y = do
    s <- get
    let x' = getL x s
    let y' = getL y s
    put (setL y (setL x s y') x')

nFibs :: Int -> Int
nFibs n = evalState (nFibs_ n) (0,1)

nFibs_ :: Int -> State (Int,Int) Int
nFibs_ 0 = fmap snd get -- The second Int is our result
nFibs_ n = do
    x += y       -- Add y to x
    swap x y     -- Swap them
    nFibs_ (n-1) -- Repeat
  where x = ((\(x,y) x' -> (x', y)), fst)
        y = ((\(x,y) y' -> (x, y')), snd)

答案 2 :(得分:3)

将命令式i=i+1编程转换为函数式编程有几种解决方案。递归函数解决方案是函数式编程的推荐方法,创建状态几乎不是你想要做的。

过了一段时间,你会发现如果你需要一个索引,你可以使用[1..],但是需要花费大量的时间和练习来进行功能性思考而不是强制性思考。

这是另一种与i=i+1类似的方法,因为没有任何破坏性更新。请注意,State monad示例仅用于说明,您可能需要[1..]代替:

module Count where
import Control.Monad.State

count :: Int -> Int
count c = c+1

count' :: State Int Int
count' = do
    c <- get
    put (c+1)
    return (c+1)

main :: IO ()
main = do
            -- purely functional, value-modifying (state-passing) way:
    print $ count . count . count . count . count . count $ 0
            -- purely functional, State Monad way
    print $ (`evalState` 0) $ do { 
            count' ; count' ; count' ; count' ; count' ; count' } 

答案 3 :(得分:1)

注意:这不是一个理想的答案,但是,嘿,有时什么也可以给一点。

一个简单的增加变量的函数就足够了。

例如:

incVal :: Integer -> Integer
incVal x = x + 1

main::IO()
main = do
   let i  = 1
   print (incVal i)

甚至是匿名函数也可以做到这一点。