这里有一个需要记住的区别:
C#具有子类型,但Haskell没有,这意味着,一方面,通过简单地查看Haskell类型,您可以了解更多信息。
id :: a -> a
这个 Haskell 函数接受一个类型的值,并返回相同类型的同样值。如果你给它一个
Bool
,它将返回一个
Bool
。给它一个
Int
,它将返回一个
Int
。给它一个
Person
,它将返回一个
Person
。
在 C# 中,你不能那么确定。这是在 C# 中的 '函数':
public T Id<T>(T x);
现在,由于子类型化的存在,您可以这样调用它:
var pers = Id<Person>(new Student());
虽然
pers
是类型为
Person
的,但是
Id
函数的参数却不是。实际上,
pers
可能会有比只是
Person
更具体的类型。
Person
甚至可以是一个抽象类型,保证
pers
将具有更具体的类型。
如您所见,即使是像
id
这样简单的函数,.NET 类型系统已经允许比来自
Haskell
更严格的类型系统更多的东西。虽然这可能对一些编程工作有用,但它也使得通过查看事物的类型来推理程序变得更加困难(在 Haskell 中这是一种乐趣)。
另外一件事是,在 Haskell 中有一种称为“类型类”的机制,通过这种机制可以实现
特定场景下的 多态性(也称为重载)。
equals :: Eq a => a -> a -> Bool
这个函数检查两个值是否相等。但不是任意两个值,而是那些具有Eq
类实例的值。这有点像C#中类型参数的约束:
public bool Equals<T>(T x, T y) where T : IComparable
然而,它们之间有一个区别。首先是子类型:您可以使用Person
实例化它,并使用Student
和Teacher
调用它。
但是,编译后的代码也存在差异。C#代码几乎完全编译成其类型所表示的内容。类型检查器确保参数实现了适当的接口,然后您就可以使用它了。
而Haskell代码则编译成类似于以下内容:
equals :: EqDict -> a -> a -> Bool
这个函数会得到一个额外的参数,即一个字典,其中包含了它需要执行 Eq
操作所需的所有函数。下面是如何使用这个函数以及它编译成的代码:
b1 = equals 2 4 --> b1 = equals intEqFunctions 2 4
b2 = equals True False --> b2 = equals boolEqFunctions True False
这也展示了子类型化为什么如此麻烦,想象一下如果这是可能的。
b3 = equals someStudent someTeacher
--> b3 = equals personEqFunctions someStudent someTeacher
“personEqFunctions”字典如何判断一个“Student”是否与“Teacher”相等?它们甚至没有相同的字段。
简而言之,Haskell类型约束乍一看可能类似于.NET类型约束,但它们的实现完全不同,并编译为两个非常不同的东西。