Monday, February 19, 2007

FFI in Haskell

I mentioned yesterday that one of the implementation characteristics of the next big language should be a simple foreign function interface.

Here's an example from a recent project. I needed to create UUIDs. Looking around, I found RFC 4122, which describes UUID generation, and helpfully provides a sample implementation in C. Unfortunately, that code generates random UUIDs that aren't very random (32 bits of randomness in a 128 bit value).

Now, I could reimplement this code (or something similar) in Haskell, and fix the lack-of-randomness issue. Or I could find another UUID generator, and reimplement that directly. But really, my goal here is to grab random 128-bit UUID values and move on. Getting distracted by a research project isn't part of the plan.

A little googling found that a version of uuid_generate(3) is bundled within e2fsprogs. My target system has e2fsprogs installed, so that's a plus. And, interestingly enough, 'man uuid_generate' on my MacBook turns up this manpage:
UUID_GENERATE(3)                                              UUID_GENERATE(3)

NAME
uuid_generate, uuid_generate_random, uuid_generate_time - create a new
unique UUID value

SYNOPSIS
#include <uuid/uuid.h>

void uuid_generate(uuid_t out);
void uuid_generate_random(uuid_t out);
void uuid_generate_time(uuid_t out);

....

AUTHOR
Theodore Y. Ts'o

AVAILABILITY
http://e2fsprogs.sourceforge.net/
(Turns out that Apple includes this as part of the standard C library. Thanks, Apple!)

A little more lurking, and I see that a uuid_t is merely an array of 16 characters. As the man page says, this is an output parameter. (It's odd to see code like this after programming in high level languages for so long, but this is C, and what are you going to do?)

Next, I started paging through the GHC User's Guide and the Standard Libraries documentation, and came up with this declaration:
foreign import ccall unsafe "uuid_generate" uuid_generate
:: Ptr Word8 -> IO ()
This declaration is a direct translation out of the man page.

Next, I needed to construct a 16-byte buffer to receive the UUID value from uuid_generate, which requires using some functions in the Foreign library. As soon as uuid_generate finishes, the buffer needs to be translated into a Haskell string. Note that the return value is a 128-bit integer, and I want to convert that into sequence of printable hex digits. Here's the more convenient interface into the UUID generator:
import Foreign
import Numeric (showHex)

uuid :: IO String
uuid = allocaBytes 16 $ \p -> do
uuid_generate p
uuid <- peekArray 16 p
return $ concat $ map toHex uuid

toHex :: Word8 -> String
toHex w = case showHex w "" of
w1:w2:[] -> w1:w2:[]
w2:[] -> '0':w2:[]
_ -> error "showHex returned []"
Thats it! Easy Peasy.

1 comment:

Anonymous said...

Nice work! UUIDs are really useful and important library additions, so I think it would be useful to find out how you could get something like this added to a library or put this code in a more visible place.

Perhaps here: http://www.haskell.org/haskellwiki/Category:Tutorials

(I'm not affiliated with the maintainers of www.haskell.org)

Thanks!