在haskell中模拟C函数静态变量

Yan*_*ang 4 haskell

我有一个名为的函数generateUID,它是通过FFI连接的外部C函数.此函数为每个调用生成新的唯一ID,但我需要一个uid用于整个程序.如果我使用C/C++,我会创建一个像这样的函数.

int getUID() {
    static int uid = generateUID();
    return uid;
}
Run Code Online (Sandbox Code Playgroud)

所以我可以像这样使用它

int foo() { return getUID() + 1; }
int bar() { return getUID() + 2; }
int main() {
    printf("%d\n", foo() + bar();
}
Run Code Online (Sandbox Code Playgroud)

在haskell中,我使用了这样的功能

getUID :: IO Int
getUID = generateUID -- this is attached to C lib with FFI
foo = (+1) <$> getUID
bar = (+2) <$> getUID
main = (+) <$> foo <*> bar >>= print
Run Code Online (Sandbox Code Playgroud)

但是,getUID如果我使用此代码,则会调用两次.我所知道的唯一解决方案是将它们合并为一种do符号,但是在我的实际代码中,foo并且bar过于频繁地重用以合并到其他函数.

如何才能让我的haskell版本只getUID调用generateUID一次?

K. *_*uhr 7

您可以使用以下技巧,根据此方法调整以定义全局可变变量.对于您的应用程序,您不需要可变变量,但是通过unsafePerformIO在顶级定义中使用,您可以强制generateUID仅调用IO操作一次并将其返回值记忆以供将来调用:

getUID :: Int
{-# NOINLINE getUID #-}
getUID = unsafePerformIO generateUID
Run Code Online (Sandbox Code Playgroud)

例如,以下完整示例将重新使用第一次getUID调用生成的相同随机数,以用于将来的任何调用:

{-# LANGUAGE ForeignFunctionInterface #-}

module Uid where

import System.IO.Unsafe

foreign import ccall "stdlib.h random" generateUID :: IO Int

getUID :: Int
{-# NOINLINE getUID #-}
getUID = unsafePerformIO generateUID

foo = getUID + 1
bar = getUID + 2

main = print $ (foo, bar)
Run Code Online (Sandbox Code Playgroud)

这有一个优点,getUID它只是一个纯值,因此IO如果需要,你可以在monad 之外使用它.

不可否认,许多人认为这是一个可怕的黑客.通常,更清晰的替代方法是使用Reader包含全局ID值的组件在monad中运行程序.通常你会在某种单子反正可以运行任何非平凡的程序的大块(例如,上面,你似乎愿意运行foobarIO单子),所以这是很少太大的问题.就像是:

{-# LANGUAGE ForeignFunctionInterface #-}

module Uid where

import Control.Monad.Reader

data R = R { globalUid :: Int }
type M = ReaderT R IO

foreign import ccall "stdlib.h random" generateUID :: IO Int

getUID :: M Int
getUID = asks globalUid

foo = (+1) <$> getUID
bar = (+2) <$> getUID

withUid :: M a -> IO a
withUid act = do
  uid <- generateUID
  runReaderT act (R uid) 

main = withUid $ do
  liftIO . print =<< (,) <$> foo <*> bar
Run Code Online (Sandbox Code Playgroud)