c#和Haskell之间的通信

本文关键字:通信 之间 Haskell | 更新日期: 2023-09-27 17:53:32

我有这个Haskell代码:

module RectangleMover where
data Point = Point Int Int deriving (Show)
movePoint :: Point -> Int -> Int -> Point
movePoint (Point x y) dx dy = Point (x + dx) (y + dy)
data Rectangle = Rectangle { corner :: Point, width :: Int, height :: Int } deriving (Show)
move :: Rectangle -> Int -> Int -> Rectangle
move r@(Rectangle {corner=c}) dx dy = r { corner = movePoint c dx dy }
p = Point 1 2
hsRec = Rectangle p 10 20

对应的c#代码是:

class Point
{
    private int x;
    private int y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public void Move(int dx, int dy)
    {
        this.x += dx;
        this.y += dy;
    }
}
class Rectangle
{
    private Point point;
    private int width;
    private int height;
    public Rectangle(Point p, int width, int height)
    {
        this.point = p;
        this.width = width;
        this.height = height;
    }
    public void Move(int dx, int dy)
    {
        this.point.Move(dx, dy);
    }
}
Point p = new Point(1,2);
Rectangle csRec = new Rectangle(p, 10, 20);

我现在的问题是如何将"实例"hsRec Haskell传递到c#和csRec从c#传递到Haskell。在这种情况下,常见的方法是使用FFI从Haskell代码创建一个DLL,然后从c#调用这个DLL。也可以反过来,从c#创建一个DLL,然后从Haskell调用这个DLL。

要从Haskell导出,这里有一个简单的整数示例:

{-# LANGUAGE ForeignFunctionInterface #-}
module Add () where
import Foreign.C.Types
add :: CInt -> CInt -> CInt
add x y = x + y
foreign export ccall add :: CInt -> CInt -> CInt

但是如何在这些语言之间传递更复杂的类型呢?在本例中,传递一个矩形类型的对象。是否有可能将对象转换为JSON或XML字符串并将其传递给另一种语言?

c#和Haskell之间的通信

你的选择是:

  • 手动将所有c#/Haskell结构转换为普通C结构。(高性能,编写复杂,调试困难)

  • 手动序列化/反序列化你的c#/Haskell结构为JSON/XML/YAML/CSV/任何。(较低的性能,大大容易调试)

  • 对于每个数据结构,手动编写并公开知道如何从中获取琐碎数据的函数。(例如,如果你有一个Haskell Map,导出函数一次获取一个键,以一种足以让C理解的方式,然后从c#端包装。)

老实说,没有办法"直接"或"自动"让c#理解Haskell对象的布局,反之亦然。

有效的方法是使用C FFI在Haskell和c#之间创建一个桥梁。那将是大量的工作。

另一种方法是使用通用的序列化协议,如MessagePack。这里有一个Haskell库,它可以很容易地通过MessagePack与其他语言进行通信:https://github.com/nh2/call-haskell-from-anything

显然,这种技术有很高的"调用"开销,但它比使用REST API和通过网络堆栈(当然这是另一种选择)要快得多。只要调用开销不是瓶颈,那么这是一个非常合理的方法。