如何使用C#(DLLImport)的高阶类型签名使用和调用Haskell函数,例如...
double :: (Int -> Int) -> Int -> Int -- higher order function
typeClassFunc :: ... -> Maybe Int -- type classes
data MyData = Foo | Bar -- user data type
dataFunc :: ... -> MyData
C#中对应的类型签名是什么?
[DllImport ("libHSDLLTest")]
private static extern ??? foo( ??? );
另外(因为可能更容易):如何在C#中使用“未知” Haskell类型,以便至少可以在C#不知道任何特定类型的情况下传递它们?我需要正确知道的最重要的功能是传递一个类型类(例如Monad或Arrow)。
我已经知道how to compile a Haskell library to DLL并在C#中使用,但仅用于一阶函数。我也知道Stackoverflow - Call a Haskell function in .NET,Why isn't GHC available for .NET和hs-dotnet,在这些地方我没有找到任何文档和示例(对于从C#到Haskell的方向)。
c#大神给出的解决方案
我将在这里对FUZxxl的帖子发表评论。
您发布的示例都可以通过FFI
使用。一旦使用FFI导出函数,就可以将程序编译成DLL。
.NET的设计旨在能够轻松地与C,C ++,COM等接口。这意味着一旦能够将函数编译成DLL,就可以从.NET(相对)容易地对其进行调用。正如我在链接到的其他文章中之前提到的那样,请记住在导出函数时指定的调用约定。 .NET中的标准是stdcall
,而(大多数)Haskell FFI
使用ccall
导出的示例。
到目前为止,我发现FFI可以导出的唯一限制是polymorphic types
或未完全应用的类型。例如种类*
以外的任何内容(例如,您不能导出Maybe
,但可以导出Maybe Int
)。
我编写了一个工具Hs2lib,该工具将自动覆盖并导出示例中具有的任何功能。它还可以选择生成unsafe
C#代码,从而使其几乎“即插即用”。我之所以选择不安全的代码,是因为它更易于使用指针,这反过来又使得对数据结构的编组更加容易。
为了完整起见,我将详细介绍该工具如何处理您的示例以及如何计划处理多态类型。
高阶函数
导出高阶函数时,需要稍作更改。高阶参数需要成为FunPtr的元素。基本上,它们被视为显式函数指针(或c#中的委托),这就是在命令式语言中通常如何实现更高的有序性。
假设我们将Int
转换为CInt
,则double类型将从
(Int -> Int) -> Int -> Int
进入
FunPtr (CInt -> CInt) -> CInt -> IO CInt
这些类型是为包装函数(在本例中为doubleA
)生成的,而不是double
本身被导出。包装函数在导出的值和原始函数的预期输入值之间映射。需要IO,因为构造FunPtr
并非纯粹的操作。
要记住的一件事是,构造或取消引用FunPtr
的唯一方法是通过静态创建导入来指示GHC为此创建存根。
foreign import stdcall "wrapper" mkFunPtr :: (Cint -> CInt) -> IO (FunPtr (CInt -> CInt))
foreign import stdcall "dynamic" dynFunPtr :: FunPtr (CInt -> CInt) -> CInt -> CInt
“包装器”功能允许我们创建一个FunPtr
,而“动态” FunPtr
允许我们引用一个。
在C#中,我们将输入声明为IntPtr
,然后使用Marshaller
辅助函数Marshal.GetDelegateForFunctionPointer创建可以调用的函数指针,或使用反函数从函数指针创建IntPtr
。
还请记住,作为参数传递给FunPtr的函数的调用约定必须与传递参数的函数的调用约定相匹配。换句话说,将&foo
传递给bar
要求foo
和bar
具有相同的调用约定。
用户数据类型
实际上,导出用户数据类型非常简单。对于每个需要导出的数据类型,必须为此类型创建一个Storable实例。该实例指定了GHC能够导出/导入此类型所需的编组信息。除其他事项外,您还需要定义类型的size
和alignment
,以及如何向指针读取/写入类型的值。我将Hsc2hs部分用于此任务(因此文件中的C宏)。
仅使用一个构造函数的newtypes
或datatypes
很容易。这些成为平面结构,因为构造/销毁这些类型时只有一种可能的选择。具有多个构造函数的类型成为并集(在C#中将Layout
属性设置为Explicit
的结构)。但是,我们还需要包含一个枚举,以标识正在使用的构造。
通常,数据类型Single
定义为
data Single = Single { sint :: Int
, schar :: Char
}
创建以下Storable
实例
instance Storable Single where
sizeOf _ = 8
alignment _ = #alignment Single_t
poke ptr (Single a1 a2) = do
a1x <- toNative a1 :: IO CInt
(#poke Single_t, sint) ptr a1x
a2x <- toNative a2 :: IO CWchar
(#poke Single_t, schar) ptr a2x
peek ptr = do
a1' <- (#peek Single_t, sint) ptr :: IO CInt
a2' <- (#peek Single_t, schar) ptr :: IO CWchar
x1 <- fromNative a1' :: IO Int
x2 <- fromNative a2' :: IO Char
return $ Single x1 x2
和C结构
typedef struct Single Single_t;
struct Single {
int sint;
wchar_t schar;
} ;
函数foo :: Int -> Single
将导出为foo :: CInt -> Ptr Single
而具有多个构造函数的数据类型
data Multi = Demi { mints :: [Int]
, mstring :: String
}
| Semi { semi :: [Single]
}
生成以下C代码:
enum ListMulti {cMultiDemi, cMultiSemi};
typedef struct Multi Multi_t;
typedef struct Demi Demi_t;
typedef struct Semi Semi_t;
struct Multi {
enum ListMulti tag;
union MultiUnion* elt;
} ;
struct Demi {
int* mints;
int mints_Size;
wchar_t* mstring;
} ;
struct Semi {
Single_t** semi;
int semi_Size;
} ;
union MultiUnion {
struct Demi var_Demi;
struct Semi var_Semi;
} ;
Storable
实例相对简单,从C struct定义中应该更容易理解。
应用类型
我的依赖项跟踪程序将为类型Maybe Int
和Int
都发出依赖项。这意味着,为Maybe
生成Storable
实例时,头部看起来像
instance Storable Int => Storable (Maybe Int) where
也就是说,只要存在用于应用程序参数的Storable实例,类型本身也可以导出。
由于Maybe Int
被定义为具有多态参数Maybe a
,因此在创建结构时,某些类型信息会丢失。这些结构将包含一个Just a
参数,您必须将其手动转换为正确的类型。在我看来,另一种选择太麻烦了,那就是还要创建专门的结构。例如。 struct MaybeInt。但是,普通模块可能生成的专用结构数量会以这种方式迅速爆炸。 (稍后可以将其添加为标志)。
为了缓解这种信息丢失,我的工具将导出针对该功能找到的所有void*
文档,作为生成的include中的注释。它还会将原始的Haskell类型签名也放入注释中。然后,IDE会将这些内容作为其Intellisense(代码互补)的一部分进行呈现。
与所有这些示例一样,我省略了.NET方面的代码。如果您对此感兴趣,则可以仅查看Hs2lib的输出。
还有一些其他类型需要特殊处理。特别是Haddock
和Lists
。
列表需要传递要进行编组的数组的大小,因为我们正在与不隐式知道数组大小的非托管语言进行交互。相反,当我们返回列表时,我们还需要返回列表的大小。
元组是特殊的内置类型,为了导出它们,我们必须首先将它们映射为“普通”数据类型,然后将其导出。在该工具中,最多完成8个元组。
多态类型
多态类型Tuples
的问题是e.g. map :: (a -> b) -> [a] -> [b]
和size
的a
未知。也就是说,由于我们不知道它们是什么,因此无法为参数和返回值保留空间。我计划通过允许您为b
和a
指定可能的值并为这些类型创建专门的包装器函数来支持这一点。另一方面,在命令性语言中,我将使用b
向用户展示您选择的类型。
至于类,Haskell的开放世界假设通常是一个问题(例如可以随时添加一个实例)。但是,在编译时,仅静态已知的实例列表可用。我打算提供一个选项,这些选项将使用这些列表自动导出尽可能多的专用实例。例如export overloading
在编译时为所有已知的(+)
实例(例如Num
,Int
等)导出专用功能。
该工具也相当值得信赖。由于我无法真正检查代码的纯度,因此我始终相信程序员是诚实的。例如。您不会将具有副作用的函数传递给需要纯函数的函数。坦白地说,将高阶论点标记为不合理,以避免出现问题。
我希望这会有所帮助,并且我希望这不会太久。
更新:我最近发现了一些大难题。我们必须记住,.NET中的String类型是不可变的。因此,当编组器将其发送到Haskell代码时,我们得到的CWString就是原始副本。我们必须释放它。在C#中执行GC时,它不会影响CWString,它是一个副本。
但是问题是,当我们在Haskell代码中释放它时,我们不能使用freeCWString。指针未分配C(msvcrt.dll)的分配。有三种方法(我知道)可以解决此问题。
调用Haskell函数时,请在C#代码中使用char *而不是String。然后,您可以在调用return时使指针空闲,或者使用fixed初始化函数。
在Haskell中导入CoTaskMemFree并在Haskell中释放指针
使用StringBuilder而不是String。我对此并不完全确定,但是想法是,由于StringBuilder是作为本机指针实现的,因此Marshaller只是将此指针传递给了您的Haskell代码(它也可以同时更新)。调用返回后执行GC时,应释放StringBuilder。
当用户关闭而无需付款时,我在CI框架中使用Razorpay,请创建razor支付模型,然后取消订单,我希望按状态更改为已取消的状态触发查询。所以我怎么能检测到这一点。我已经通过单击jQuery单击关闭功能但无法使用... javascript大神给出的解决方案 Razorpay提供了JS方法来检测模式关闭。您编写的任何JS代码都不会在结帐页面上运行,因为它是…
LeetCode题解求一根绳子被切两刀能组成一个三角形的概率。如题题解:我们可以设绳长为1,设:- 其中两段长为x, y且x, y都>0- 故第三段长为1-x-y且>0故可以在二维坐标轴画出一个三角形(由x=0;y=0;1-x-y=0围成)要想构成三角形还要满足:- x+y > 1-x-y => x+y > 0.5- x+1-x-y > y => y < 0.5- y+1…
LeetCode题解多人运动已知小猪每晚都要约好几个女生到酒店房间。每个女生 i 与小猪约好的时间由 [si , ei]表示,其中 si 表示女生进入房间的时间, ei 表示女生离开房间的时间。由于小猪心胸开阔,思想开明,不同女生可以同时存在于小猪的房间。请计算出小猪最多同时在做几人的「多人运动」。例子: Input : [[ 0 , 30] ,[ 5 , 10 ] , [15 , 2…
LeetCode题解136.single-number题目地址 https://leetcode.com/problems/single-number/description/ 题目描述 Given a non-empty array of integers, every element appears twice except for one. Find that single one. Note: Your…
Python numpy数据指针地址无需更改即可更改 - python编辑经过一些摆弄之后,到目前为止,我已经隔离了以下状态:一维数组在直接输入变量时提供两个不同的地址,而在使用print()时仅提供一个地址2D数组(或矩阵)在直接输入变量时提供三个不同的地址,在使用print()时提供两个地址3D数组在直接输入变量时提供两个不同的地址,而在使用print()时仅给出一个(显然与一维数组相同)像这样:>>> …