我正在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函数抛出错误。你知道我怎样才能模拟全局变量?
答案 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}
以这种方式保持您的状态受限,可以更容易地跟踪您的程序的增长;你总是知道哪些计算可能改变你的状态,因为它的类型清楚地表明了这一点。
另一方面,如果你出于某种原因绝对需要全局变量,那么使用IORef
和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
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)
函数式编程没有状态或全局变量,每个函数都应该能够从其他所有人那里运行。有一些技巧:
http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:readFile
使用州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