Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何使用简单的技术用我自己的底层表示(类似于STRef或STArray)在ST monad中实现动作?_Haskell_Ffi_Mutable_State Monad - Fatal编程技术网

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)