如何使用简单的技术使用我自己的底层表示(类似于 STRef 或 STArray)在 ST-monad 中实现操作?
How to implement actions in ST-monad with my own underlying representation (similarly to STRef or STArray) using simple techniques?
我想通过 STArray
或 STRef
在 ST
monad 中提供的接口从 FFI 操作某种类型的结构。对于对该结构有用的操作类型,我将拥有自己的具有易于理解名称的特定方法(例如 readArray
和 writeArray
用于数组)。
最简单的实现方法是什么?
STArray
的实现(基于 https://hackage.haskell.org/package/base-4.7.0.2/docs/src/GHC-Arr.html)对于那些不了解用于此的特殊 GHC 技术的人来说看起来太复杂了。
我可以写一些更简单易懂的 Haskell 水平的东西吗?
备注
我不是在问如何通过 FFI 访问结构。
我宁愿用 C 编写 getter 和 setter 函数,我想在 Haskell 中镜像它们(以获得像 readArray
和writeArray
).
关于简单声明这种接口的一些想法(可能是 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
。
我想通过 STArray
或 STRef
在 ST
monad 中提供的接口从 FFI 操作某种类型的结构。对于对该结构有用的操作类型,我将拥有自己的具有易于理解名称的特定方法(例如 readArray
和 writeArray
用于数组)。
最简单的实现方法是什么?
STArray
的实现(基于 https://hackage.haskell.org/package/base-4.7.0.2/docs/src/GHC-Arr.html)对于那些不了解用于此的特殊 GHC 技术的人来说看起来太复杂了。
我可以写一些更简单易懂的 Haskell 水平的东西吗?
备注
我不是在问如何通过 FFI 访问结构。
我宁愿用 C 编写 getter 和 setter 函数,我想在 Haskell 中镜像它们(以获得像 readArray
和writeArray
).
关于简单声明这种接口的一些想法(可能是 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
。