Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulating C function static variable in haskell

Tags:

haskell

I have a function called generateUID, which is external C function connected via FFI. This function generates new unique id for each call, but I need one uid for entire program. If I were using C/C++, I would've made a function like this one.

int getUID() {
    static int uid = generateUID();
    return uid;
}

So that I can use this like this

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

In haskell, I used function like this

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

However, getUID is called twice if I use this code. The only solution that I know of is to merge them into one do notation, but in my real code, foo and bar are reused too frequently to be merged to other function.

How can I make my haskell version getUID call generateUID only once?

like image 812
Remagpie Avatar asked Mar 08 '23 01:03

Remagpie


1 Answers

You can use the following trick, adapted from this method for defining global mutable variables. For your application, you don't need a mutable variable, but by using unsafePerformIO in a top-level definition, you can force the IO action generateUID to be called only once and its return value memoized for future calls:

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

For example, the following complete example will re-use the same random number generated by the first getUID call for any future calls:

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

This has the advantage that getUID is just a pure value, so you can use it outside of IO monad, if needed.

Admittedly, many people consider this a horrible hack. Typically, a cleaner alternative is to run the program in a monad with a Reader component that includes the global ID value. Usually you'll be running big chunks of any non-trivial program in some kind of monad anyway (e.g., above, you seemed willing to run foo and bar in the IO monad), so this is rarely much of an issue. Something like:

{-# 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
like image 113
K. A. Buhr Avatar answered Apr 30 '23 11:04

K. A. Buhr