模拟全局变量

时间:2013-05-29 10:22:14

标签: haskell functional-programming

我正在Haskell的一个项目中工作,我需要一个全局变量。目前我正在这样做:

 funcs :: Map.Map String Double
 funcs = Map.empty

 eliminate :: Maybe a -> a
 eliminate (Just a) = a

 insert :: String -> Double -> Map.Map String Double -> Map.Map String Double
 insert key value cache = Map.insert key value cache

 f = do

                 let aux = insert "aaa" 1 funcs
                 let funcs = aux
                 .........


 g = do
        if (Map.lookup "aaa" funcs) == Nothing then error "not defined" else putStr "ok"

问题是总是g函数抛出错误。你知道我怎样才能模拟全局变量?

2 个答案:

答案 0 :(得分:19)

使用let funcs = aux,您只在funcs函数的范围内为f提供了一个新约束,这意味着您在funcs中引用了g {1}}是全局范围内的一个 - 定义为Map.empty的范围。在运行时无法更改全局或其他纯值。但是,可以使用可变引用。最好是在当地,但也在全球范围内有一些不安全的hackery。

是否真的是否必须使用全局变量?如果您没有在整个程序中使用全局,则可能需要将所有使用它的计算包装在State monad中:

import Control.Monad.State
import qualified Data.Map as Map

funcs :: Map.Map String Double
funcs = Map.empty

f :: String -> Double -> State (Map.Map String Double) ()
f str d = do
  funcs <- get
  put (Map.insert str d funcs)

g :: State (Map.Map String Double) String
g = do
  funcs <- get
  if (Map.lookup "aaa" funcs) == Nothing then return "not defined" else return "ok"

main = putStrLn $ flip evalState funcs $ do {f "aaa" 1; g}

以这种方式保持您的状态受限,可以更容易地跟踪您的程序的增长;你总是知道哪些计算可能改变你的状态,因为它的类型清楚地表明了这一点。

另一方面,如果你出于某种原因绝对需要全局变量,那么使用IORefunsafePerformIO有一个众所周知但相当丑陋的技巧:

import Data.IORef
import System.IO.Unsafe
import qualified Data.Map as Map

{-# NOINLINE funcs #-}
funcs :: IORef (Map.Map String Double)
funcs = unsafePerformIO $ newIORef Map.empty

f :: String -> Double -> IO ()
f str d = atomicModifyIORef funcs (\m -> (Map.insert str d m, ()))

g :: IO ()
g = do
  fs <- readIORef funcs
  if (Map.lookup "aaa" fs) == Nothing then error "not defined" else putStrLn "ok"

main = do
  f "aaa" 1
  g

这个技巧创建了一个全局IORef,可以在IO monad中读取和更新。这意味着执行IO的任何计算都可能会改变全局值,这会给您带来全局状态的所有令人头疼的问题。除此之外,这个技巧也非常hacky,只有GHC的实现细节才有效(例如参见{-# NOINLINE funcs #-}部分)。

如果您决定使用此hack(我非常建议反对),请记住您可以绝对不将其与多态值一起使用。为了说明原因:

import Data.IORef
import System.IO.Unsafe

{-# NOINLINE danger #-}
danger :: IORef a
danger = unsafePerformIO $ newIORef undefined

coerce :: a -> IO b
coerce x = do
  writeIORef danger x
  readIORef danger

main = do
  x <- coerce (0 :: Integer) :: IO (Double, String) -- boom!
  print x

正如您所看到的,此技巧可以与多态一起使用来编写一个函数,将任何类型重新解释为任何其他类型,这显然会破坏类型安全性,因此可能导致程序出现段错误(充其量)。

总之,执行考虑使用State monad而不是全局变量; 不要轻轻转向全局变量。

答案 1 :(得分:3)

函数式编程没有状态或全局变量,每个函数都应该能够从其他所有人那里运行。有一些技巧:

  1. 将状态写入文件
  2. http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:readFile

    1. 使用州Monads http://en.wikibooks.org/wiki/Haskell/Understanding_monads/State

      import Data.IORef
      
      type Counter = Int -> IO Int
      
      makeCounter :: IO Counter
      makeCounter = do
          r <- newIORef 0
         return (\i -> do modifyIORef r (+i)
                          readIORef r)
      
      testCounter :: Counter -> IO ()
      testCounter counter = do
        b <- counter 1
        c <- counter 1
        d <- counter 1
        print [b,c,d]
      
      main = do
        counter <- makeCounter
        testCounter counter
        testCounter counter