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だけです。つまり、で参照しているのはグローバルスコープのもの、つまり として定義されているものです。純粋な値は、グローバルであろうとなかろうと、実行時に変更することはできません。ただし、変更可能な参照を使用することは可能です。できればローカルに、しかし少し危険なハッカーを使ってグローバルに。funcsffuncsgMap.empty

しかし、グローバル変数を使用することは本当に必要ですか? プログラム全体でグローバルを使用していない場合は、State代わりにそれを使用するすべての計算をモナドでラップすることをお勧めします。

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内で読み取りと更新が可能な global を作成します。IOこれは、IO を実行する計算によってグローバルの値が変更される可能性があることを意味します。これにより、グローバルな状態のすばらしい頭痛の種がすべて与えられます。それとは別に、このトリックも非常にハックであり、GHC の実装の詳細のためにのみ機能します ({-# NOINLINE funcs #-}たとえば、その部分を参照してください)。

このハックを使用することに決めた場合 (私絶対に使用しないことをお勧めします)、ポリモーフィックな値では絶対に使用できないことに注意してください。理由を説明するには:

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

于 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 に答える