Haskell 如何使用简单的技术用我自己的底层表示(类似于STRef或STArray)在ST monad中实现动作?
我想通过像Haskell 如何使用简单的技术用我自己的底层表示(类似于STRef或STArray)在ST monad中实现动作?,haskell,ffi,mutable,state-monad,Haskell,Ffi,Mutable,State Monad,我想通过像STArray或STRef在STmonad中提供的接口那样的接口,从FFI操作特定类型的结构。对于这种对这个结构有用的操作,我将有自己的特定方法,这些方法的名称可以理解(比如数组的readArray和writeArray) 实现这一点最简单的方法是什么 对于那些不知道用于此的一些特殊GHC技术的人来说,STArray(基于)的实现看起来太复杂了 我能用Haskell更简单、更易懂的语言写点什么吗 评论 我不是问如何通过FFI访问结构 我更愿意用C编写getter和setter函数,并希
STArray
或STRef
在ST
monad中提供的接口那样的接口,从FFI操作特定类型的结构。对于这种对这个结构有用的操作,我将有自己的特定方法,这些方法的名称可以理解(比如数组的readArray
和writeArray
)
实现这一点最简单的方法是什么
对于那些不知道用于此的一些特殊GHC技术的人来说,STArray
(基于)的实现看起来太复杂了
我能用Haskell更简单、更易懂的语言写点什么吗
评论
我不是问如何通过FFI访问结构
我更愿意用C编写getter和setter函数,并希望在Haskell中镜像它们(以获得ST操作,如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)
因此,产生了这样一种想法,即可以在“效果”和“纯”之间建立一种中间形式,以节省程序员的工作。仅限于“有限状态”的“效果”:
除了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操作
也许,这在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)
所有这些都附带了一个警告:如果您的C函数不是异常安全的或违反引用透明性,Haskell类型的系统将不会帮助您,并且您可能最终会公开
unsafePerformIO
C数组和“原生”Haskell STArrays是不同的东西。在处理FFI时,通常使用IO
,而不是ST
。如果您确实确定您的C函数没有外部可观察到的副作用,并且希望将FFI引入ST,那么您可能需要使用FFI编写函数,然后使用unsafeIOToST
(在Control.Monad.ST.Unsafe中)。@user2407038啊,好的,谢谢!我可能在寻找unsafeIOToST
@user2407038顺便说一句,我不相信你能让FFI直接将你的函数封送到ST s,而不是IO。在这种情况下它肯定会有用,但我不知道是否有很多人使用这种用例。您当然可以提交功能请求!谢谢!这是非常清楚和有益的。我不明白为什么要使用ptrfoo
来键入指针,因为Foo
是类似的Haskell数据类型,但我不相信C结构和Haskell数据具有相同的内存表示形式Foo
内部甚至有Int
,而不是CInt
。当然,Ptr
的参数在现实中并没有太大区别,只是不用于混合指向不同类型的指针,但是选择Foo
作为参数不是很容易混淆吗?我会让writeBar
的参数反过来:比如c_writeBar
(以及我更新问题的签名示例);该顺序将与writeSTRef::STRef s a->a->ST s()
和writeArray::(MArray a e m,Ix I)=>a I e->I->e->m()
匹配Ptr Foo
的原因是“正确的”这是因为Ptr
的类型参数是幻象类型;其中没有实际的Foo
存储(看起来像data Ptr a=Ptr Addr#
,其中Addr#是指针的基本类型,基本上只是Word64)。没有“魔力”碰巧将c-struct转换为Haskell类型。我使用了Ptr Foo
,因为道德上Ptr Foo
是一个指向Foo
类型值的指针-Ptr Foo
不能用与Foo
本身相同的内存布局表示其数据这一事实是不相关的。当然,如果需要,可以选择不同的类型发现它有助于清晰(Ptr CFoo
).至于论点的顺序,这实际上与问题的内容无关-它更具风格。如果您认为这有助于未来读者的可读性或理解,请随意编辑答案。当然,这只是风格,没有任何实际的缺点。如果您不介意,我将交换它们以匹配标准库中的示例。
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)
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)