c# - 在C#中使用高阶Haskell类型

标签 c# haskell ffi

如何使用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 .NETWhy isn't GHC available for .NEThs-dotnet,在这些地方我没有找到任何文档和示例(对于从C#到Haskell的方向)。

最佳答案

我将在这里对FUZxxl的帖子发表评论。
您发布的示例都可以使用FFI来实现。一旦使用FFI导出函数,就可以将程序编译成DLL。

.NET的设计旨在能够轻松地与C,C++,COM等接口(interface)。这意味着,一旦能够将函数编译为DLL,就可以从.NET(相对)容易地对其进行调用。正如我之前在链接到的其他文章中提到的那样,请记住在导出函数时指定的调用约定。 .NET中的标准是stdcall,而(大多数)Haskell FFI导出示例使用ccall

到目前为止,我发现FFI可以导出的唯一限制是polymorphic types或未完全应用的类型。例如除了*类型以外的任何内容(例如,您不能导出Maybe,但可以导出Maybe Int)。

我编写了Hs2lib工具,该工具将自动覆盖并导出示例中具有的任何功能。它还可以选择生成unsafe C#代码,从而使其几乎“即插即用”。之所以选择不安全的代码,是因为它更易于处理指针,从而使数据结构的编码变得更加容易。

为了完整起见,我将详细介绍该工具如何处理您的示例以及如何计划处理多态类型。

  • 高阶函数

  • 导出高阶函数时,需要稍作更改。高阶参数需要成为FunPtr的元素。基本上,它们被视为显式函数指针(或C#中的委托(delegate)),这就是在命令式语言中通常如何实现更高的有序性。
    假设我们将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要求foobar具有相同的调用约定。
  • 用户数据类型

  • 实际上,导出用户数据类型非常简单。对于每个需要导出的数据类型,必须为此类型创建Storable实例。该实例指定了GHC能够导出/导入此类型所需的编码信息。除其他事项外,您还需要定义类型的sizealignment,以及如何读取/写入指针类型的值。我将Hsc2hs部分用于此任务(因此文件中的C宏)。

    仅带newtypesdatatypes很容易。这些成为平面结构,因为构造/销毁这些类型时只有一种可能的选择。具有多个构造函数的类型将成为一个并集(在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类型发出对IntMaybe类型的依赖关系。这意味着,当生成StorableMaybe Int实例时,头部看起来像
    instance Storable Int => Storable (Maybe Int) where
    

    也就是说,只要存在用于应用程序参数的Storable实例,类型本身也可以导出。

    由于Maybe a被定义为具有多态参数Just a,因此在创建结构时,会丢失一些类型信息。这些结构将包含void*参数,您必须手动将其转换为正确的类型。在我看来,另一种选择太麻烦了,那就是还要创建专门的结构。例如。 struct MaybeInt。但是,普通模块可能生成的专用结构数量会以这种方式 swift 爆炸。 (稍后可以将其添加为标志)。

    为了缓解这种信息丢失,我的工具将导出针对该功能找到的所有Haddock文档,作为生成的include中的注释。它还会将原始的Haskell类型签名也放入注释中。然后,IDE会将这些内容作为其Intellisense(代码互补)的一部分进行显示。

    与所有这些示例一样,我已经省略了.NET方面的代码,如果您对此感兴趣,则可以仅查看的输出Hs2lib

    还有一些其他类型需要特殊处理。特别是ListsTuples
  • 列表需要传递从其进行编码的数组的大小,因为我们正在与不隐式知道数组大小的非托管语言进行交互。相反,当我们返回列表时,我们还需要返回列表的大小。
  • 元组是特殊的内置类型,为了导出它们,我们必须首先将它们映射为“正常”数据类型,然后将其导出。在工具中,最多完成8个元组。
  • 多态类型

  • 多态类型e.g. map :: (a -> b) -> [a] -> [b]的问题是sizeab未知。也就是说,由于我们不知道它们是什么,因此无法为参数和返回值保留空间。我计划通过允许您为ab指定可能的值并为这些类型创建专门的包装函数来支持此功能。另一方面,在命令式语言中,我将使用overloading向用户展示您选择的类型。

    至于类,Haskell的开放世界假设通常是一个问题(例如可以随时添加一个实例)。但是,在编译时,仅提供静态已知的实例列表。我打算提供一个选项,该选项将使用这些列表自动导出尽可能多的专用实例。例如export (+)在编译时为所有已知的Num实例导出一个专用函数(例如IntDouble等)。

    该工具也相当值得信赖。由于我无法真正检查代码的纯度,因此我始终相信程序员是诚实的。例如。您不会将具有副作用的函数传递给需要纯函数的函数。坦白地说,将高阶论点标记为不避免问题是不正确的。

    我希望这会有所帮助,并且我希望这不会太久。

    更新:我最近发现了一些大陷阱。我们必须记住,.NET中的String类型是不可变的。因此,当编码器将其发送给Haskell代码时,我们得到的CWString就是原始副本。我们来释放它。在C#中执行GC时,它不会影响CWString,它是一个副本。

    但是问题是,当我们在Haskell代码中释放它时,我们不能使用freeCWString。指针未分配C(msvcrt.dll)的分配。有三种方法(我知道)可以解决此问题。
  • 在调用Haskell函数时在C#代码中使用char *而不是String。然后,您可以在调用return时使指针变为free,或者使用fixed初始化函数。
  • 在Haskell中导入CoTaskMemFree并在Haskell中释放指针
  • 使用StringBuilder代替String。我对此并不完全确定,但是想法是,由于StringBuilder是作为 native 指针实现的,因此Marshaller只是将此指针传递给了您的Haskell代码(它也可以同时对其进行更新)。在调用返回后执行GC时,应释放StringBuilder。
  • 关于c# - 在C#中使用高阶Haskell类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6562378/

    相关文章:

    Haskell - Collat​​z 猜想以及如何阅读我的错误消息

    带有 : 的 Haskell 列表语法

    ruby - 错误 : Error installing ffi: ERROR: Failed to build gem native extension

    c++ - 从 C++ 生成 C 包装器?

    io - 如何处理长时间运行的外部函数调用,例如 Rust 中的阻塞 I/O?

    c# - 从内部访问器反射(reflect)属性名称?

    javascript - 当 javascript 尝试解析包含从 C# 发送的列表的对象时,CefSharp 崩溃

    haskell - 在 Haskell 中实现序数

    c# - 从代码隐藏创建 html 元素并在 ASP.NET Web 窗体中显示到前端

    c# - Cosmos DB Azure 表 API o数据身份验证 REST/C#?