模拟全局变量



我正在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函数总是抛出错误。您知道如何模拟全局变量吗?

对于let funcs = aux,您只是在f函数的作用域中给funcs一个新的绑定,这意味着您在g中引用的funcs是全局作用域中的一个-定义为Map.empty的那个。不可能在运行时更改纯值,无论是全局值还是其他值。但是,可以使用可变引用。最好是局部的,但也可以是全局的,带有一点不安全的黑客行为。

真的需要使用全局变量吗?如果您没有在整个程序中使用全局变量,您可能希望将使用全局变量的所有计算封装在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}

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

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

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单子中读取和更新。这意味着任何执行IO的计算都可能改变全局的值,这会给您带来全局状态的所有令人头痛的问题。除此之外,这个技巧也非常粗糙,只适用于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单变量而不是全局变量;不要把变成全局变量

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

  1. 文件的读写状态

http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html v: readFile

  1. 使用状态单子 (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
    

最新更新