我有一个名为generateUID
的函数,它是通过FFI连接的外部C函数。
此函数为每个调用生成新的唯一ID,但我需要一个uid用于整个程序。如果我使用的是C / C ++,我会创建一个像这样的函数。
int getUID() {
static int uid = generateUID();
return uid;
}
这样我就可以像这样使用
int foo() { return getUID() + 1; }
int bar() { return getUID() + 2; }
int main() {
printf("%d\n", foo() + bar();
}
在haskell中,我使用了这样的功能
getUID :: IO Int
getUID = generateUID -- this is attached to C lib with FFI
foo = (+1) <$> getUID
bar = (+2) <$> getUID
main = (+) <$> foo <*> bar >>= print
但是,如果我使用此代码,则getUID
被调用两次。我所知道的唯一解决方案是将它们合并为一个do
表示法,但在我的实际代码中,foo
和bar
被过于频繁地重用以合并到其他函数。
如何才能让我的haskell版本getUID
只调用generateUID
一次?
答案 0 :(得分:7)
您可以使用以下技巧,改编自this method for defining global mutable variables。对于您的应用程序,您不需要可变变量,但通过在顶级定义中使用unsafePerformIO
,您可以强制仅调用IO操作generateUID
一次,并将其回传值记在未来的电话中:
getUID :: Int
{-# NOINLINE getUID #-}
getUID = unsafePerformIO generateUID
例如,以下完整示例将重新使用第一个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)
这样做的好处是getUID
只是一个纯值,因此如果需要,您可以在IO
monad之外使用它。
不可否认,许多人认为这是一个可怕的黑客。通常,更简洁的替代方法是使用包含全局ID值的Reader
组件在monad中运行程序。通常你会在某种monad中运行任何非平凡程序的大块(例如,在上面,你似乎愿意在foo
monad中运行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