在Haskell中,您可以轻松地在几行中定义复杂的结构。一个小例子是:
data A = B Float
| C Int
| D [D]
data D = E [A]
| F Int
要将其转换为面向对象的语言,您是否必须为A-F
创建一个类,然后使用继承来定义相同的结构?还是还有其他一些简短的手?
我有一个在Haskell中指定数据类型的赋值,我(错误地)选择了C#但是现在回去已经太迟了。
答案 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做的事情。如果Foo
或Bar
未被密封,则可以定义子类型,但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
}
等