gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 19:38:40 27 4
gpt4 key购买 nike

如何使用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/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com