如何在函数式编程语言中增加变量?
例如,我想做:
main :: IO ()
main = do
let i = 0
i = i + 1
print i
预期输出:
1
如何在函数式编程语言中增加变量?
例如,我想做:
main :: IO ()
main = do
let i = 0
i = i + 1
print i
预期输出:
1
简单的方法是引入变量名的阴影:
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+1
指i
其左侧的 ——而不是i
预期的上层。所以我们通过引入另一个变量来打破这个等式j
。
另一种更简单的方法是 <-
在do
-notation 中使用一元绑定。这是可能的,因为一元绑定不是递归的。
在这两种情况下,我们都会以相同的名称引入新变量,从而“遮蔽”旧实体,即使其不再可访问。
这里要理解的一件事是,具有纯的——不可变的——值的函数式编程(就像我们在 Haskell 中一样)迫使我们在代码中明确时间。
在命令式设置中,时间是隐含的。我们“改变”我们的变量——但任何改变都是顺序的。我们永远无法改变那个 var刚才的样子——只能改变从现在开始的样子。
在纯函数式编程中,这只是明确的。这可以采用的最简单的形式之一是使用值列表作为命令式编程中顺序更改的记录。更简单的是完全使用不同的变量来表示实体在不同时间点的不同值(参见单一赋值和静态单一赋值形式,或 SSA)。
因此,我们不是“改变”那些无论如何都无法真正改变的东西,而是制作它的增强副本,然后传递它,用它代替旧的东西。
作为一般规则,您不需要(而且您不需要)。但是,为了完整性。
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)
有几种解决方案可以将命令式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' }
注意:这不是一个理想的答案,但是嘿,有时提供任何东西可能有点好。
增加变量的简单函数就足够了。
例如:
incVal :: Integer -> Integer
incVal x = x + 1
main::IO()
main = do
let i = 1
print (incVal i)
甚至是一个匿名函数来做到这一点。