25

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

例如,我想做:

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

预期输出:

1
4

4 回答 4

26

简单的方法是引入变量名的阴影:

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是行不通的,因为let在 Haskell 中进行递归定义——它实际上是 Scheme 的letrec. i右侧的是let i = i+1i其左侧的 ——而不是i预期的上层。所以我们通过引入另一个变量来打破这个等式j

另一种更简单的方法是 <-do-notation 中使用一元绑定。这是可能的,因为一元绑定不是递归的。

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

如何“思考功能”

这里要理解的一件事是,具有纯的——不可变的——值的函数编程(就像我们在 Haskell 中一样)迫使我们在代码中明确时间。

在命令式设置中,时间是隐含的。我们“改变”我们的变量——但任何改变都是顺序的。我们永远无法改变那个 var刚才的样子——只能改变从现在开始的样子。

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

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

于 2012-08-12T15:57:24.300 回答
8

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

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)
于 2012-08-12T16:53:30.460 回答
4

有几种解决方案可以将命令式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' } 
于 2012-08-12T12:24:26.100 回答
1

注意:这不是一个理想的答案,但是嘿,有时提供任何东西可能有点好。

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

例如:

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

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

甚至是一个匿名函数来做到这一点。

于 2020-05-29T01:16:20.517 回答