Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one interface with a C enum using Haskell and FFI?

Tags:

enums

haskell

ffi

Let's say charm.c has an enum key and a function get_key() that returns a key type value.

How can I expose a corresponding Haskell Key record and function getKey :: IO Key?

And how can I do this without manually specifying how every single enum value maps to a Haskell value?

like image 550
mcandre Avatar asked Jul 14 '11 07:07

mcandre


1 Answers

For @KevinReid, here's an example of how to do this with c2hs.

Given the enum key in the file charm.h (I have no idea what's in the enum, so I just filled in a few values)

typedef enum
  {
    PLAIN_KEY = 0,
    SPECIAL_KEY  = 1,
    NO_KEY = 2
  }
key;

key get_key();

You can use c2hs's enum hook like this:

{#enum key as Key {underscoreToCase} deriving (Eq, Show)#}

To bind to a function, you can use either call or fun. call is simpler, but doesn't do any marshaling. Here are examples of both. The ffi-wrapped get_key will return a CInt, so you need to either manually marshal it (if using call) or specify the marshaller (if using fun). c2hs doesn't include enum marshallers so I've written my own here:

module Interface  where -- file Interface.chs

{#enum key as Key {underscoreToCase} deriving (Eq, Show)#}

getKey = cIntToEnum `fmap` {#call get_key #}

{#fun get_key as getKey2 { } -> `Key' cIntToEnum #}

cIntToEnum :: Enum a => CInt -> a
cIntToEnum = toEnum . cIntConv

C2hs will generate the following Haskell from this (slightly cleaned up):

data Key = PlainKey
         | SpecialKey
         | NoKey
         deriving (Eq,Show)
instance Enum Key where
  fromEnum PlainKey = 0
  fromEnum SpecialKey = 1
  fromEnum NoKey = 2

  toEnum 0 = PlainKey
  toEnum 1 = SpecialKey
  toEnum 2 = NoKey
  toEnum unmatched = error ("Key.toEnum: Cannot match " ++ show unmatched)

getKey = cIntToEnum `fmap` get_key

getKey2 :: IO (Key)
getKey2 =
  getKey2'_ >>= \res ->
  let {res' = cIntToEnum res} in
  return (res')

cIntToEnum :: Enum a => CInt -> a
cIntToEnum = toEnum . cIntConv

foreign import ccall safe "foo.chs.h get_key"
  get_key :: (IO CInt)

foreign import ccall safe "foo.chs.h get_key"
  getKey2'_ :: (IO CInt)
like image 50
John L Avatar answered Sep 20 '22 15:09

John L