8

我正在使用 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 函数抛出错误。你知道我怎样才能模拟一个全局变量吗?

4

2 回答 2

20

let funcs = aux您只在函数范围内提供了funcs一个新绑定f,这意味着funcs您所指的g是全局范围内的绑定 - 定义为Map.empty. 在运行时无法更改纯值、全局或其他值。但是,可以使用可变引用。最好是在本地,但也可以在全球范围内进行一些不安全的黑客攻击。

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

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

另一方面,如果您出于某种原因绝对需要全局变量,则使用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

这个技巧创建了一个全局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

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

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

于 2013-05-29T10:54:36.617 回答
3

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

  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
    
于 2013-05-29T10:25:55.623 回答