如何使用简单的技术使用我自己的底层表示(类似于 STRef 或 STArray)在 ST-monad 中实现操作?

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

我想通过 STArraySTRefST monad 中提供的接口从 FFI 操作某种类型的结构。对于对该结构有用的操作类型,我将拥有自己的具有易于理解名称的特定方法(例如 readArraywriteArray 用于数组)。

最简单的实现方法是什么?

STArray 的实现(基于 https://hackage.haskell.org/package/base-4.7.0.2/docs/src/GHC-Arr.html)对于那些不了解用于此的特殊 GHC 技术的人来说看起来太复杂了。

我可以写一些更简单易懂的 Haskell 水平的东西吗?

备注

我不是在问如何通过 FFI 访问结构。

我宁愿用 C 编写 getter 和 setter 函数,我想在 Haskell 中镜像它们(以获得像 readArraywriteArray).

关于简单声明这种接口的一些想法(可能是 GHC 扩展?)

如果我没记错的话,我可以将外部函数声明为 IO 操作或纯函数(如果我确定它是纯函数)。我将后者理解为简单地将其包装在 unsafePerformIO:

foreign import ccall safe "getValue.h getValue" effect :: CInt -> Ptr CChar
foreign import ccall safe "getValue.h getValue" pure :: CInt -> IO (Ptr CChar)

因此,想法出现了 介于 "effect" 和 "pure" 之间的中间形式是可能的,以节省程序员的工作。一个"effect"限制为一个"limited state":

foreign import ccall safe "getValue.h writeValue" writeValue :: (ValueRef s) -> Value -> ST s () -- modeled after writeSTRef

除了 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!

我只是无法正确理解 ValueRef 的细节:如果我们定义这种未参数化的类型,那么编译器如何使用参数化的类型来给出 ST-action?..

可能这在 GHC 中不可用,但可能是有用的扩展,不是吗?

根据我的评论,我将举一个简短的例子来说明如何做到这一点。

首先从您的基本 C 模块开始。

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

然后是 Haskell 文件。

{-# 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 } 

还有你所有的国外进口。

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

如果您需要在 C 端做一些特殊的事情,但不想强迫您的用户在您的 Foo 上调用 free,您可以使用 ForeignPtr 在您的实际表示中。

data STFoo s = STFoo (ForeignPtr Foo) 

当然这个类型必须是抽象的。如果你使用的是 GHC 7.8 或更高版本,你还应该包括

{-# LANGUAGE RoleAnnotations #-} -- at the top

type role STFoo nominal 

或者人们可能会破坏你从 ST 得到的不变量。当您创建一个新的 STFoo 时,您想将 C 端终结器放在上面。

newFoo :: ST s (STFoo s) 
newFoo = STFoo <$> unsafeIOToST (c_newFoo >>= newForeignPtr p_freeFoo)

读写基本上就是一些强制。

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)       

你还可以获得一个Haskell侧Foo值,这很可能是ST内部计算的结果。这就像 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) 

这一切都伴随着警告,如果您的 C 函数不是异常安全的或违反引用透明性,Haskell 类型系统将无济于事,您最终可能会暴露 unsafePerformIO