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?
Constructor helps to initialize the object of a class. Whereas destructor is used to destroy the instances.
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() .
It never takes any parameters, and it never returns anything. You can't pass parameters to the destructor anyway, since you never explicitly call a destructor (well, almost never).
A destructor is called for a class object when that object passes out of scope or is explicitly deleted. A destructor is a member function with the same name as its class prefixed by a ~ (tilde). For example: class X { public: // Constructor for class X X(); // Destructor for class X ~X(); };
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)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With