10

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?

Remarks

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).

Some thoughts on easy declaration of this kind of interface (a possible GHC extension?)

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?

imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
  • C Arrays and "native" Haskell STArrays are different things. Typically you use `IO`, not `ST` when working with FFI. If you are really sure your C functions have no externally observable side effects, and you wish to bring the FFI into ST, you will probably need to write the functions with the FFI and then use `unsafeIOToST` (in Control.Monad.ST.Unsafe). – user2407038 Feb 27 '15 at 15:53
  • @user2407038 Ah, ok, thanks! I was probably looking for `unsafeIOToST`. – imz -- Ivan Zakharyaschev Feb 27 '15 at 15:55
  • @user2407038 BTW, [What is the difference between `ioToST` and `unsafeIOToST` from GHC.IO?](http://stackoverflow.com/q/28769550/94687) – imz -- Ivan Zakharyaschev Feb 27 '15 at 16:18
  • 1
    I don't believe that you can get the FFI to marshal your functions directly to ST s, instead of IO. It would be useful in this case certainly, but I don't know if there are many people with this use case. You can certainly submit a feature request! – user2407038 Feb 27 '15 at 21:53

1 Answers1

11

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.

user2407038
  • 14,400
  • 3
  • 29
  • 42
  • Thanks a lot! This is very clear and helpful. I don't understand why you use `Ptr Foo` for typing the pointers, because `Foo` is the *analoguous* Haskell data type, but I don't believe that the C struct and the Haskell data have the same in-memory representation. `Foo` even has `Int` inside, not `CInt`. Of course, the parameter to `Ptr` doesn't make much difference in reality and only serves not to mix pointers to different types, but isn't the choice of `Foo` as the parameter confusing? – imz -- Ivan Zakharyaschev Feb 27 '15 at 19:01
  • I'd make the arguments of `writeBar` the other way round: like in `c_writeBar` (and the examples of signatures I've updated my question with); that order would match `writeSTRef :: STRef s a -> a -> ST s ()` and `writeArray :: (MArray a e m, Ix i) => a i e -> i -> e -> m ()` – imz -- Ivan Zakharyaschev Feb 27 '15 at 19:08
  • 1
    The reason that `Ptr Foo` is "correct" is because the type parameter to `Ptr` is a phantom type; no actual `Foo` is stored inside of it (it looks like `data Ptr a = Ptr Addr#`, where Addr# is the primitive type of pointers and is basically just Word64). No "magic" happens to convert the c-struct to a Haskell type. I used `Ptr Foo` because morally `Ptr Foo` is a pointer to a value of type `Foo` - the fact that `Ptr Foo` doesn't represent its data with the same memory layout as `Foo` itself isn't relevant. Of course, you can pick a different type if you find it helps clarity (`Ptr CFoo`). – user2407038 Feb 27 '15 at 21:51
  • As for the order of arguments, that doesn't really relate to the content of the question - it is more style. If you think it would help readability or understanding for future readers, please feel free to edit the answer. – user2407038 Feb 27 '15 at 21:57
  • Sure, it's just style, and no real drawback. If you don't mind, I'll swap them to match the example from standard library. – imz -- Ivan Zakharyaschev Feb 27 '15 at 22:01
  • Another tiny stylistic issue for me was that `STArray` and `STRef` have ST prefixed, but `FooST` has it as a postfix. Are there some (stylistic) reasons to do it one or another way? – imz -- Ivan Zakharyaschev Feb 27 '15 at 22:07
  • Also just an arbitrary style choice. I threw together the example quite quickly so I wasn't thinking about those details. If you think of any small issues like this, please make any appropriate edits. – user2407038 Feb 28 '15 at 01:19