在C#中使用高阶的Haskell类型 [英] Using higher-order Haskell types in C#

查看:141
本文介绍了在C#中使用高阶的Haskell类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我如何使用和C#(dllimport的)调用与高阶类型签名哈斯克尔功能,如...

How can I use and call Haskell functions with higher-order type signatures from C# (DLLImport), like...

double :: (Int -> Int) -> Int -> Int -- higher order function

typeClassFunc :: ... -> Maybe Int    -- type classes

data MyData = Foo | Bar              -- user data type
dataFunc :: ... -> MyData



什么是C#中的相应类型的签名?

What are the corresponding type signature in C#?

[DllImport ("libHSDLLTest")]
private static extern ??? foo( ??? );

此外(因为它可能会更容易):如何使用C#中的未知哈斯克尔类型,所以我至少可以通过他们身边,没有C#知道任何特定类型的?我需要正确认识的最重要的功能是通过围绕一个类型的类(如单子或箭头)

Additionally (because it may be easier): How can I use "unknown" Haskell types within C#, so I can at least pass them around, without C# knowing any specific type? The most important functionality I need right know is to pass around a type class (like Monad or Arrow).

我已经知道的如何编写一个Haskell库DLL 和C#中使用,但仅限于一阶的功能。我也知道的#1 - 在.NET 调用一个Haskell功能, 为什么不是GHC提供.NET 和的 HS-DOTNET ,在那里我没有找到任何文档和示例(针对C#哈斯克尔方向)

I already know how to compile a Haskell library to DLL and use within C#, but only for first-order functions. I'm also aware of Stackoverflow - Call a Haskell function in .NET, Why isn't GHC available for .NET and hs-dotnet, where I didn't find ANY documentation and samples (for the C# to Haskell direction).

推荐答案

我会在这里阐述一下我对FUZxxl的帖子发表评论。结果
你贴都可以使用 FFI 。一旦你使用FFI你可以因为你已经想通了编译程序到一个DLL导出功能。

I'll elaborate here on my comment on FUZxxl's post.
The examples you posted are all possible using FFI. Once you export your functions using FFI you can as you've already figured out compile the program into a DLL.

.NET的设计采用了能够用C,C ++,COM等,这可方便地将意图意味着,一旦你能够编译功能一个DLL,你可以调用它(相对)从.NET容易。正如我在你链接到我的其他职务前面提到的,记住该叫你导出的函数时指定约定。在.NET中的标准是 STDCALL ,而(大多数)的Haskell的例子 FFI 使用导出 ccall

.NET was designed with the intention of being able to interface easily with C, C++, COM, etc. This means that once you're able to compile your functions to a DLL, you can call it (relatively) easy from .NET. As I've mentioned before in my other post that you've linked to, keep in mind which calling convention you specify when exporting your functions. The standard in .NET is stdcall, while (most) examples of Haskell FFI export using ccall.

到目前为止,我什么可通过FFI出口找到的唯一限制是态类型或类型未充分应用。例如比任何其他种类 * (您不能导出也许,但您可以导出也许诠释的实例)。

So far the only limitation I've found on what can be exported by FFI is polymorphic types, or types that are not fully applied. e.g. anything other than kind * (You can't export Maybe but you can export Maybe Int for instance).

我写了一个工具 Hs2lib 将涵盖和自动的,你在你的例子有功能的任何出口。它也有产生不安全 C#代码,这使得它非常即插即用的选项。我选择不安全的代码的原因是因为它更容易处理的指针,这反过来又使得它更容易做数据结构编组。

I've written a tool Hs2lib that would cover and export automatically any of the functions you have in your example. It also has the option of generating unsafe C# code which makes it pretty much "plug and play". The reason I've choosen unsafe code is because it's easier to handle pointers with, which in turn makes it easier to do the marshalling for datastructures.

要完成我将详细介绍该工具如何处理你的例子,我计划如何处理多态类型。

To be complete I'll detail how the tool handles your examples and how I plan on handling polymorphic types.


  • 高阶函数

在出口高阶函数,需要稍微改变的功能。高阶参数需要成为 FunPtr 。基本上,他们被视为明确的函数指针(或C#委托),这是orderedness如何在更高的命令式语言通常完成。结果
假设我们把内部 CINT 双类型是从

When exporting higher order functions, the function needs to be slightly changed. The higher-order arguments need to become elements of FunPtr. Basically They're treated as explicit function pointers (or delegates in c#), which is how higher orderedness is typically done in imperative languages.
Assuming we convert Int into CInt the type of double is transformed from

(Int -> Int) -> Int -> Int



into

FunPtr (CInt -> CInt) -> CInt -> IO CInt



这些类型的包装函数生成的( doubleA 在这种情况下),这是出口,而不是双击本身。包装函数的原函数导出值和预期的输入值之间映射。该IO需要,因为构建 FunPtr 不是一个纯粹的操作。结果
有一点要记住的是,只有这样,才能建造或取消引用 FunPtr 是静态地创建它指示GHC创建此存根进口。

These types are generated for a wrapper function (doubleA in this case) which is exported instead of double itself. The wrapper functions maps between the exported values and the expected input values for the original function. The IO is needed because constructing a FunPtr is not a pure operation.
One thing to remember is that the only way to construct or dereference a FunPtr is by statically creating imports which instruct GHC to create stubs for this.

foreign import stdcall "wrapper" mkFunPtr  :: (Cint -> CInt) -> IO (FunPtr (CInt -> CInt))
foreign import stdcall "dynamic" dynFunPtr :: FunPtr (CInt -> CInt) -> CInt -> CInt



包装函数允许我们创建一个 FunPtr 动态 FunPtr 允许一个尊重之一。

The "wrapper" function allows us to create a FunPtr and the "dynamic" FunPtr allows one to deference one.

在C#中,我们宣布输入作为的IntPtr 然后使用的Marshaller 辅助函数 Marshal.GetDelegateForFunctionPointer 创建一个函数指针,我们可以打电话,或反函数从一个函数指针创建一个的IntPtr

In C# we declare the input as a IntPtr and then use the Marshaller helper function Marshal.GetDelegateForFunctionPointer to create a function pointer that we can call, or the inverse function to create a IntPtr from a function pointer.

另外请记住,函数的调用约定作为参数传递给FunPtr必须以该参数被传递给函数的调用约定匹配传递。换句话说,通过&放大器;富要求具有相同的调用约定。

Also remember that the calling convention of the function being passed as an argument to the FunPtr must match the calling convention of the function to which the argument is being passed to. In other words, passing &foo to bar requires foo and bar to have the same calling convention.


  • 用户数据类型

导出用户数据类型其实是相当简单的。对于每一个需要导出的的可保存实例已为这一类型创建。这个实例指定GHC为了能够导入/导出,这种类型的需要编组的信息。除此之外,你需要定义尺寸对齐的类型,随着如何读/写一个指针的类型的值。我部分使用 Hsc2hs 该任务(因此在对C宏文件)。

Exporting a user datatype is actually quite straight forward. For every datatype that needs to be exported a Storable instance has to be created for this type. This instances specifies the marshalling information that GHC needs in order to be able to export/import this type. Among other things you would need to define the size and alignment of the type, along with how to read/write to a pointer the values of the type. I partially use Hsc2hs for this task (hence the C macros in the file).

newtypes 数据类型只有< STRONG>有一个的构造很简单。这些成为一个平面结构,因为,只有一个施工时/自毁这些类型的可能的选择。与多个构造类型成为工会(与布局结构体属性设置为明确在C#)。然而,我们还需要包括一个枚举识别正在使用的结构。

newtypes or datatypes with just one constructor is easy. These become a flat struct since there's only one possible alternative when constructing/destructing these types. Types with multiple constructors become a union (a struct with Layout attribute set to Explicit in C#). However we also need to include an enum to identify which construct is being used.

在一般情况下,数据类型定义为

in general, the datatype Single defined as

data Single = Single  { sint   ::  Int
                      , schar  ::  Char
                      }

创建以下可存储实例

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的诠释 - >单将出口作为 :: foo的目标类 - > PTR单
虽然有多个构造数据类型

The function foo :: Int -> Single would be exported as foo :: CInt -> Ptr Single While a datatype with multiple constructor

data Multi  = Demi  {  mints    ::  [Int]
                    ,  mstring  ::  String
                    }
            | Semi  {  semi :: [Single]
                    }



生成下面的C代码:

generates the following C code:

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



可存储实例是相对简单的,并应遵循从C结构定义更加容易。

The Storable instance is relatively straight forward and should follow easier from the C struct definition.


  • 应用类型

我的依赖示踪将为发出对类型也许诠释两个类型的依赖内部也许。这意味着,生成也许诠释头部看起来像

My dependency tracer would for emit for for the type Maybe Int the dependency on both the type Int and Maybe. This means, that when generating the Storable instance for Maybe Int the head looks like

instance Storable Int => Storable (Maybe Int) where

这就是aslong因为有应用程序的参数的可存储实例该类型本身还可以导出。

That is, aslong as there's a Storable instance for the arguments of the application the type itself can also be exported.

由于也许被定义为具有多态参数只是,创建结构时,某些类型的信息丢失。该结构将包含无效* 参数,你必须手动转换为正确的类型。另一种是在我看来,这是藏汉建立专门的结构太麻烦了。例如。结构MaybeInt。但是,可以从一个正常模块产生的特殊结构的量可以迅速爆炸这种方式。 (可能以后添加为标志)。

Since Maybe a is defined as having a polymorphic argument Just a, when creating the structs, some type information is lost. The structs would contain a void* argument, which you have to manually convert to the right type. The alternative was too cumbersome in my opinion, which was to create specialized structs aswell. E.g. struct MaybeInt. But the amount of specialized structures that could be generated from a normal module can quickly explode this way. (might add this as a flag later on).

要缓解这种信息丢失我的工具将导出发现的功能,如产生任何意见的黑线鳕文档包括。它也将放置原稿的Haskell类型签名中的注释为好。然后,IDE将提出这些作为其智能感知(代码compeletion)的一部分。

To ease this loss of information my tool will export any Haddock documentation found for the function as comments in the generated includes. It will also place the original Haskell type signature in the comment as well. An IDE would then present these as part of its Intellisense (code compeletion).

与所有的这些例子我ommited代码为东西方的.NET ,如果你有兴趣,你可以只查看 Hs2lib 的输出。

As with all of these examples I've ommited the code for the .NET side of things, If you're interested in that you can just view the output of Hs2lib.

有一些其他类型的需要特殊对待。特别是列表元组

There are a few other types that need special treatment. In particular Lists and Tuples.


  1. 列出需要获得通过数组从马歇尔的大小从,因为我们与其中数组的大小是不会隐知非托管语言接口。 Conversly当我们返回一个列表,我们还需要返回列表的大小。

  2. 元组是在类型特别打造的,为了以出口为主,我们必须首先地图他们一个正常的数据类型,并导出这些。在工具这样做了,直到8元组。

  1. Lists need to get passed the size of the array from which to marshall from, since we're interfacing with unmanaged languages where the size of the arrays are not implicitly known. Conversly when we return a list, we also need to return the size of the list.
  2. Tuples are special build in types, In order to export them, we have to first map them to a "normal" datatype, and export those. In the tool this is done up untill 8-tuples.


  • 态类型

与多态类型的问题例如:图::(一 - > B) - > [一个] - GT; [B] 是,尺寸 A 不知道。也就是说,没有办法预留空间参数和返回值,因为我们不知道它们是什么。我打算通过允许您为指定的可能值来支持这个 A B 并为这些类型的专业包装函数。在其它尺寸,在命令式语言,我会用超载出示您选择用户类型。

The problem with polymorphic types e.g. map :: (a -> b) -> [a] -> [b] is that the size of a and b are not know. That is, there's no way to reserve space for the arguments and return value since we don't know what they are. I plan to support this by allowing you to specify possible values for a and b and create specialized wrapper function for these types. On the other size, in the imperative language I would use overloading to present the types you've chosen to the user.

对于类,Haskell的开放世界假定通常是一个问题(例如,一个实例可以加入任何时间)。然而,在编译时只可用实例的一个静态已知名单。我打算提供尽可能利用这些名单将自动导出尽可能多的专门实例的选项。例如出口(+)出口专门功能的所有已知在编译时实例(例如诠释双击等)。

As for classes, Haskell's open world assumption is usually a problem (e.g. an instance can be added any time). However at the time of compilation only a statically known list of instances is available. I intend to offer an option that would automatically export as much specialized instances as possible using these list. e.g. export (+) exports a specialized function for all known Num instances at compile time (e.g. Int, Double, etc).

该工具也相当信任。因为我真的不能检查纯度的代码,我始终相信程序员是诚实的。例如。你不及格有副作用给需要一个纯函数的函数的函数。要诚实,标志着高排序参数作为是不纯的,以避免出现问题。

The tool is also rather trusting. Since I can't really inspect the code for purity, I always trust that the programmer is honest. E.g. you don't pass a function that has side-effects to a function that expects a pure function. Be honest and mark the higher-ordered argument as being impure to avoid problems.

我希望这可以帮助,我希望这不是太长。

I hope this helps, and I hope this wasn't too long.

更新:有一定程度的一大疑难杂症,我已经最近发现。我们必须记住,在.NET中的字符串类型是不可改变的。因此,当编组把它发送给了Haskell代码,我们得到的CWString存在的原件及复印件。我们的包含以释放此。当气相色谱在C#,执行它不会影响该CWString,这是一个拷贝。

Update : There's somewhat of a big gotcha that I've recently discovered. We have to remember that the String type in .NET is immutable. So when the marshaller sends it to out Haskell code, the CWString we get there is a copy of the original. We have to free this. When GC is performed in C# it won't affect the the CWString, which is a copy.

但问题是,当我们在Haskell代码中解放出来,我们不能用freeCWString。指针没有与C(MSVCRT.DLL)的ALLOC分配。有三种方式(即我所知道的)来解决这个问题。

The problem however is that when we free it in the Haskell code we can't use freeCWString. The pointer was not allocated with C (msvcrt.dll)'s alloc. There are three ways (that I know of) to solve this.


  • 使用CHAR调用一个Haskell时*在你的C#代码,而不是字符串功能。然后,您可以将指针释放,当你调用返回,或使用固定初始化功能。

  • CoTaskMemFree 在Haskell和自由指针在Haskell

  • 使用StringBuilder的,而不是字符串。我不完全知道这一个,但这个想法是,既然StringBuilder的是作为本机的指针来实现,现Marshaller只是传递this指针您Haskell代码(也可顺便说一句更新)。当调用返回后执行GC,StringBuilder的应该被释放。

  • use char* in your C# code instead of String when calling a Haskell function. You then have the pointer to free when you call returns, or initialize the function using fixed.
  • import CoTaskMemFree in Haskell and free the pointer in Haskell
  • use StringBuilder instead of String. I'm not entirely sure about this one, but the idea is that since StringBuilder is implemented as a native pointer, the Marshaller just passes this pointer to your Haskell code (which can also update it btw). When GC is performed after the call returns, the StringBuilder should be freed.

这篇关于在C#中使用高阶的Haskell类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆