I want to manipulate structs of a certain type from FFI through an interface like that provided with STArray
or STRef
in an ST
monad. I'll have my own specific methods with understandable names for the kind of manipulations that are useful for this struct (like readArray
and writeArray
for arrays).
What is the simplest way to implement this?
The implementation for STArray
(based on https://hackage.haskell.org/package/base-4.7.0.2/docs/src/GHC-Arr.html) looks too convoluted for those who do not know some special GHC techniques used for this.
Can I write something on a more simple, understandable level of Haskell?
I'm not asking about how to access a struct through FFI.
I'd rather write getter and setter functions in C, and I want to mirror them in Haskell (to get ST-actions like readArray
and writeArray
).
If I'm not mistaken, I can declare a foreign function as either an IO-action or pure (if I'm sure it is pure). I understand the latter as simply shortcutting wrapping it in unsafePerformIO
:
foreign import ccall safe "getValue.h getValue" effect :: CInt -> Ptr CChar
foreign import ccall safe "getValue.h getValue" pure :: CInt -> IO (Ptr CChar)
So, the idea arises that an intermediate form between "effect" and "pure" could be possible, to save programmer's work. An "effect" limited to a "limited state":
foreign import ccall safe "getValue.h writeValue" writeValue :: (ValueRef s) -> Value -> ST s () -- modeled after writeSTRef
in addition to the standard two variants for this function in GHC:
foreign import ccall safe "getValue.h writeValue" writeValue :: ValueRef -> Value -> IO ()
foreign import ccall safe "getValue.h writeValue" writeValue :: ValueRef -> Value -> () -- must be really bad!
I only can't get the details about ValueRef
right: if we define this unparameterized type, then how can the compiler use a parameterized one to give an ST-action?..
Probably, this is not available in GHC, but could be useful extension, couldn't it?
Based on my comment, I'll give a short example of how this can be done.
First start with your basic C module.
typedef struct { int bar; int baz; } foo ;
foo * newFoo ();
void freeFoo (foo * ) ;
int readBar ( foo * ) ;
int readBaz ( foo * ) ;
void writeBar ( foo * , int ) ;
void writeBaz ( foo * , int ) ;
Then the Haskell file.
{-# LANGUAGE ForeignFunctionInterface #-}
import Foreign.C
import Control.Monad.ST
import Control.Monad.ST.Unsafe
import Foreign.Ptr
import Foreign.ForeignPtr
import Control.Applicative
data Foo = Foo { bar :: Int, baz :: Int }
And all of your foreign imports.
foreign import ccall "newFoo" c_newFoo :: IO (Ptr Foo)
foreign import ccall "&freeFoo" p_freeFoo :: FunPtr (Ptr Foo -> IO ())
foreign import ccall "readBar" c_readBar :: Ptr Foo -> IO CInt
foreign import ccall "readBaz" c_readBaz :: Ptr Foo -> IO CInt
foreign import ccall "writeBar" c_writeBar :: Ptr Foo -> CInt -> IO ()
foreign import ccall "writeBaz" c_writeBaz :: Ptr Foo -> CInt -> IO ()
In case you need to do something special on the C side but don't want to have to force your user to call free
on your Foo
, you can use a ForeignPtr
in your actual representation.
data STFoo s = STFoo (ForeignPtr Foo)
Of course, this type must be abstract. If you are on GHC 7.8 or later, you should also include
{-# LANGUAGE RoleAnnotations #-} -- at the top
type role STFoo nominal
or people may be able to break the invariants you get from ST
. When you create a new STFoo
, you want to put the C side finalizer on it.
newFoo :: ST s (STFoo s)
newFoo = STFoo <$> unsafeIOToST (c_newFoo >>= newForeignPtr p_freeFoo)
Reading and writing is basically just some coercions.
readBar :: STFoo s -> ST s Int
readBar (STFoo x) = fromIntegral <$> unsafeIOToST (withForeignPtr x c_readBar)
writeBar :: STFoo s -> Int -> ST s ()
writeBar (STFoo x) i = unsafeIOToST $ withForeignPtr x $ \p ->
c_writeBar p (fromIntegral i)
You can also get a Haskell side Foo value, which will likely be the result of a computation inside ST. This is like freezeSTArray
.
freezeFoo :: STFoo s -> ST s Foo
freezeFoo (STFoo x) = unsafeIOToST $ withForeignPtr x $ \p -> do
bar <- fromIntegral <$> c_readBar p
baz <- fromIntegral <$> c_readBaz p
return (Foo bar baz)
This all comes with the caveat that if your C functions are not exception safe or violate referential transparency, the Haskell type system will not help you, and you may end up exposing unsafePerformIO
.
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