模拟全局变量

Joh*_*ith 6 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"
Run Code Online (Sandbox Code Playgroud)

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

val*_*man 19

有了let funcs = aux你只给funcs在范围的新的绑定f功能,这意味着funcs你参照g的是一个在全球范围内-这是定义为一个Map.empty.在运行时无法更改全局或其他纯值.但是,可以使用可变引用.最好是在当地,但也在全球范围内有一些不安全的hackery.

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

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}
Run Code Online (Sandbox Code Playgroud)

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

另一方面,如果你出于某种原因绝对需要全局变量,那么使用IORefs和一个众所周知但相当丑陋的技巧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
Run Code Online (Sandbox Code Playgroud)

这个技巧创建了一个全局IORef,可以在IOmonad中读取和更新.这意味着执行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
Run Code Online (Sandbox Code Playgroud)

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

总之,考虑使用Statemonad而不是全局变量; 不要轻易转向全局变量.