在haskell

时间:2018-01-27 18:11:01

标签: haskell

我有一个名为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表示法,但在我的实际代码中,foobar被过于频繁地重用以合并到其他函数。

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

1 个答案:

答案 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中运行barIO ,所以这很少是一个问题。类似的东西:

{-# 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