Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement actions in ST-monad with my own underlying representation (similarly to STRef or STArray) using simple techniques?

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?

like image 618
imz -- Ivan Zakharyaschev Avatar asked Feb 27 '15 15:02

imz -- Ivan Zakharyaschev


1 Answers

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.

like image 104
user2407038 Avatar answered Nov 01 '22 14:11

user2407038