我有一个名为的函数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一次?
您可以使用以下技巧,根据此方法调整以定义全局可变变量.对于您的应用程序,您不需要可变变量,但是通过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中运行程序.通常你会在某种单子反正可以运行任何非平凡的程序的大块(例如,上面,你似乎愿意运行foo并bar在IO单子),所以这是很少太大的问题.就像是:
{-# 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)