Haskell"数据"等价的面向对象语言

时间:2016-04-02 23:29:50

标签: c# haskell

在Haskell中,您可以轻松地在几行中定义复杂的结构。一个小例子是:

data A = B Float
       | C Int
       | D [D]

data D = E [A]
       | F Int

要将其转换为面向对象的语言,您是否必须为A-F创建一个类,然后使用继承来定义相同的结构?还是还有其他一些简短的手?

我有一个在Haskell中指定数据类型的赋值,我(错误地)选择了C#但是现在回去已经太迟了。

2 个答案:

答案 0 :(得分:2)

您可以在C#中实现具有至少一个构造函数的任何ADT。我不知道data Void的任何实现。产品很简单,因为这些只是类或结构,但总和不是语言的原生,所以你必须做一些额外的工作。

data Example a =
    Foo { foo :: a }
  | Bar { bar :: a, qux :: Int }

这是我将使用通常的标记的联合实现的示例。您可以在C中定义一个联合,但这在C#中是不常见的。相反,我会使用额外的间接,但原则是相同的。从我给出的例子中应该清楚如何推断到任何ADT。

// Sealed because we don't want subtypes of our ADT.
public sealed class Example<A>
{
  // A class for each constructor.
  // Sealed because we cannot allow subtypes to be defined and used.
  public sealed class Foo
  {
    public readonly A foo;
    public Foo(A foo) { this.foo = foo; }
  }

  // A class for each constructor.
  // Sealed because we cannot allow subtypes to be defined and used.
  public sealed class Bar
  {
    public readonly A bar;
    public readonly int qux;
    public Bar(A bar, int qux) { this.bar = bar; this.qux = qux; }
  }

  // An enum of constructors.
  // Private because this is an implementation detail.
  private enum Tag : byte
  {
    Foo,
    Bar
  }

  // Store the constructor used.
  // Private because this is an implementation detail.
  private readonly Tag TheTag;

  // Store the term object.
  // Private because we will define safe case analysis to access
  // this value later.
  private readonly object Term;

  // The only constructor.
  // Private because we are going to define proper ways to
  // construct Example`1 later.
  private Example(Tag tag, object term)
  {
    TheTag = tag;
    Term = term;
  }

  // Case analysis. This is how you get the value back out.
  // This is like case/of or the functions "maybe", "either", etc.
  public B Cases<B>(Func<Foo,B> caseFoo, Func<Bar,B> caseBar)
  {
    // Because we defined an enum we can use an efficient switch
    // statement to jump directly to the correct branch.
    switch (TheTag)
    {
      // These casts are guaranteed to be safe because of the
      // functions we define to construct Example`1's.
      case Tag.Foo: return caseFoo((Foo)Term);
      case Tag.Bar: return caseBar((Bar)Term);
      // C# does not check the exhaustiveness of the switch statement
      // so we have to throw something here unfortunately.
      default: throw new Exception("missing case!");
    }
  }

  // This constructs an Example`1 with the Foo constructor.
  public static Example<A> Create(Foo term)
  {
    return new Example<A>(Tag.Foo, term);
  }

  // This constructs an Example`1 with the Bar constructor.
  public static Example<A> Create(Bar term)
  {
    return new Example<A>(Tag.Bar, term);
  }

  // You can define whatever other conveniences you want!

}

当我在C#中实现ADT时,我试图尽可能接近Haskell。如果Example<A>没有被密封,那么可以定义子类型,但这不是你可以用Haskell中的ADT做的事情。如果FooBar未被密封,则可以定义子类型,但Haskell不会对构造函数进行子类型化。

C#有null并且没有什么可以做的,所以我不得不忽略它作为一种可能性。如果您正在使用某些ADT(例如Maybe)或任何带有零参数构造函数的ADT,那么您可以使用结构并避免null问题。我稍后会举例说明。

构造Example<A>应该像在Haskell中一样工作。这就是为什么构建Example<A>的唯一公开方式是Create(Foo)Create(Bar)。您可以重构为此使用C#构造函数,但实际上在C#构造函数中不方便,因为它们不会从类型推断中受益,也不能将它们键入为委托。实际上,要推断类型参数A,您将定义一个名为Example的第二个静态类,并在那里定义两个Create方法,就像Tuple.Create一样。

案例分析也应该像Haskell一样工作。例如,与Nullable<T>不同,此解决方案无法正确使用(禁止反射,这可能会毁掉任何东西)。这可能会降低性能并且可能会有成本可读性,但这对于正确性来说是值得的权衡。

我们已经完成了构造函数和案例分析。

你不会逃避C#中Haskell的ADT的简洁。我建议使用代码生成来克服此问题。另外,如果你确实有时间制作这样的工具,请分享!

最后,我为Maybe<A>提供了一个实现。此实现很特殊,因为与Example<A>不同,它没有任何不需要的值(Example<A>不幸的是值null)。对于任何具有零参数构造函数的ADT,您都可以实现相同的目的。

// A struct instead of a sealed class. This means instead of
// null we have the implicit empty constructor. The empty
// constructor initializes all fields to their default values
// which is determined by their type.
//
// The trick here is that default(Maybe<A>) = Maybe<A>.Nothing().
//
public struct Maybe<A>
{
  // Same as before.
  private enum Tag : byte
  {
    // Must be 0, because this is the default value of any enum.
    Nothing = 0,
    Just = 1
  }

  // By default is Nothing
  private readonly Tag TheTag;

  // Can use type A instead of object. Saves a cast.
  private readonly A Value;

  // Same as before.
  private Maybe(Tag theTag, A value)
  {
    TheTag = theTag;
    Value = value;
  }

  // Same as before.
  public B Cases<B>(Func<B> caseNothing, Func<A,B> caseJust)
  {
    switch (TheTag)
    {
      case Tag.Nothing: return caseNothing();
      case Tag.Just: return caseJust(Term);
      default: throw new Exception("missing case!");
    }
  }

  // Same as before.
  public static Maybe<A> Nothing()
  {
    return new Maybe<A>(Tag.Nothing, default(A));
  }

  // Same as before.
  public static Maybe<A> Just(A value)
  {
    return new Maybe<A>(Tag.Just, value);
  }

}

答案 1 :(得分:1)

java等同于

data A = B Float
       | C Int
       | D [B]

可以这样写

interface A {};  // marker interface
class B implements A {
    final float value;
    B(float value) {this.value=value;}   // constructor
}
class C implements A {
    final int value;
    C(int value) {this.value=value;}     // constructor
}
class D implements A {
    final B[] values;
    D(B[] values) {this.values=values;}  // constructor
}