let funcs = aux
您只在函数范围内提供了funcs
一个新绑定f
,这意味着funcs
您所指的g
是全局范围内的绑定 - 定义为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
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 而不是全局变量;不要轻易转向全局变量。