Reputation: 3054
I have Haskell code which needs to interface with a C library somewhat like this:
// MyObject.h
typedef struct MyObject *MyObject;
MyObject newMyObject(void);
void myObjectDoStuff(MyObject myObject);
//...
void freeMyObject(MyObject myObject);
The original FFI code wraps all of these functions as pure functions using unsafePerformIO
. This has caused bugs and inconsistencies because the sequencing of the operations is undefined.
What I am looking for is a general way of dealing with objects in Haskell without resorting to doing everything in IO
. What would be nice is something where I can do something like:
myPureFunction :: String -> Int
-- create object, call methods, call destructor, return results
Is there a nice way to achieve this?
Upvotes: 5
Views: 1209
Reputation: 71485
Disclaimer: I've never actually worked with C stuff from Haskell, so I am not speaking from experience here.
But what springs to mind for me is to write something like:
withMyObject :: NFData r => My -> Object -> Constructor -> Params -> (MyObject -> r) -> r
You wrap the C++ constructor/destructor as IO operations. withMyObject
uses IO to sequence the constructor, calling the user-specified function, calling the destructor, and returning the result. It can then unsafePerformIO
that entire do
block (as opposed to the individual operations within it, which you've already cooking doesn't work). You need to use deepSeq
too (which is why the NFData
constraint is there), or laziness could defer the use of the MyObject
until after it's been destructed.
The advantages of this is are:
MyObject -> r
functions using whatever ordinary code you like, no monads requiredMyObject
in order to call such functions in the middle of other ordinary pure code, with the help of withMyObject
withMyObject
MyObject
after calling the destructor on it1unsafePerformIO
, and therefore that's the only place you have to carefully worry about whether you've got the sequencing correct to justify that it's safe after all. There's also only one place you have to worry about making sure you use the destructor properly.It's basically the "construct, use, destruct" pattern with the particulars of the "use" step abstracted out as a parameter so that you can has a single implementation cover every time you need to use that pattern.
The main disadvantage is that it's a bit awkward to construct a MyObject
and then pass it to several unrelated functions. You have to bundle them up into a function that returns a tuple of each of the original results, and then use withMyObject
on that. Alternatively if you also expose the IO
versions of he constructor and destructor separately the user has the option of using those if IO
is less awkward than making wrapper functions to pass to withMyObject
(but then it's possible for the user to accidentally use the MyObject
after freeing it, or forget to free it).
1 Unless you do something silly like use id
as the MyObject -> r
function. Presumably there's no NFData MyObject
instance though. Also that sort of error would tend to come from willful abuse rather than accidental misunderstanding.
Upvotes: 0
Reputation: 3054
My final solution. It probably has subtle bugs that I haven't considered, but it is the only solution so far which has met all of the original criteria:
Unfortunately the implementation is a bit complicated.
E.g.
// Stack.h
typedef struct Stack *Stack;
Stack newStack(void);
void pushStack(Stack, int);
int popStack(Stack);
void freeStack(Stack);
c2hs file:
{-# LANGUAGE ForeignFunctionInterface, GeneralizedNewtypeDeriving #-}
module CStack(StackEnv(), runStack, pushStack, popStack) where
import Foreign.C.Types
import Foreign.Ptr
import Foreign.ForeignPtr
import qualified Foreign.Marshal.Unsafe
import qualified Control.Monad.Reader
#include "Stack.h"
{#pointer Stack foreign newtype#}
newtype StackEnv a = StackEnv
(Control.Monad.Reader.ReaderT (Ptr Stack) IO a)
deriving (Functor, Monad)
runStack :: StackEnv a -> a
runStack (StackEnv (Control.Monad.Reader.ReaderT m))
= Foreign.Marshal.Unsafe.unsafeLocalState $ do
s <- {#call unsafe newStack#}
result <- m s
{#call unsafe freeStack#} s
return result
pushStack :: Int -> StackEnv ()
pushStack x = StackEnv . Control.Monad.Reader.ReaderT $
flip {#call unsafe pushStack as _pushStack#} (fromIntegral x)
popStack :: StackEnv Int
popStack = StackEnv . Control.Monad.Reader.ReaderT $
fmap fromIntegral . {#call unsafe popStack as _popStack#}
test program:
-- Main.hs
module Main where
import qualified CStack
main :: IO ()
main = print $ CStack.runStack x where
x :: CStack.StackEnv Int
x = pushStack 42 >> popStack
build:
$ gcc -Wall -Werror -c Stack.c
$ c2hs CStack.chs
$ ghc --make -Wall -Werror Main.hs Stack.o
$ ./Main
42
Upvotes: 2
Reputation: 53871
The idea is to keep passing a baton from each component to force each component to be evaluated in sequence. This is basically what the state monad is (IO
is really a weird state monad. Kinda).
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Control.Monad.State
data Baton = Baton -- Hide the constructor!
newtype CLib a = CLib {runCLib :: State Baton a} deriving Monad
And then you just string operations together. Injecting them into the CLib
monad will mean they're sequenced. Essentially, you're faking your own IO
, in a more unsafe way since you can escape.
Then you must ensure that you add construct
and destruct
to the end of all CLib
chains. This is easily done by exporting a function like
clib :: CLib a -> a
clib m = runCLib $ construct >> m >> destruct
The last big hoop to jump through is to make sure that when you unsafePerformIO
whatever's in construct
, it actually gets evaluated.
Frankly, this is all kinda pointless since it already exists, battle proven in IO
. Instead of this whole elaborate process, how about just
construct :: IO Object
destruct :: IO ()
runClib :: (Object -> IO a) -> a
runClib = unsafePerformIO $ construct >>= m >> destruct
If you don't want to use the name IO
:
newtype CLib a = {runCLib :: IO a} deriving (Functor, Applicative, Monad)
Upvotes: 5