我正在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
单变量而不是全局变量;不要把变成全局变量
函数式编程没有状态或全局变量,每个函数都应该能够独立于其他所有函数运行。这里有一些技巧:
- 文件的读写状态
http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html v: readFile
-
使用状态单子 (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