根据我在StackOverflow上发现的一些建议,我正在深入研究Haskell.我很高兴看到Haskell的参数化类型与C#泛型非常相似.两种语言都建议使用单个字母作为类型参数(通常),并且两种语言似乎都遵循类似的过程来将实际类型替换为类型参数.因此,我很快就理解了这个概念.
这导致了这一点:Haskell的参数化类型与C#泛型类型的不同之处是什么?我从学习Ruby中了解到,您可能会遇到大麻烦,认为您熟悉的一种语言在您熟悉的另一种语言中是相同的.通常情况下,麻烦更糟糕的是,当功能其实是非常相似......因为它们通常不是 100%相同.那么,如果我假设我根据自己对C#泛型的了解理解参数化类型,那么我可能会被一些"陷阱"淹没?
谢谢.
请注意以下一个区别:
C#有子类型,但Haskell没有,这意味着,一方面,通过简单地查看Haskell类型就可以了解更多内容.
id :: a -> a
此Haskell函数采用类型的值并返回相同类型的相同值.如果你给它一个Bool
,它将返回一个Bool
.给它一个Int
,它会返回一个Int
.给它一个Person
,它会返回一个Person
.
在C#中,你不能这么肯定.这就是C#中的'功能':
public T Id(T x);
现在,因为子类型,你可以像这样调用它:
var pers = Id(new Student());
虽然pers
是类型Person
,但Id
函数的参数不是.事实上,pers
可能有一个更具体的类型而不仅仅是Person
.Person
甚至可以是抽象类型,保证pers
将具有更具体的类型.
正如您所看到的,即使使用像id
.NET类型系统这样简单的功能,也已经允许比更严格的类型系统更多的功能Haskell
.虽然这对于做一些编程工作可能是有用的,但它也使得通过查看事物的类型(在Haskell中这是一件令人高兴的事情)来推理程序变得更加困难.
第二件事,Haskell中存在ad hoc多态(也称为重载),通过称为"类型类"的机制.
equals :: Eq a => a -> a -> Bool
此函数检查两个值是否相等.但不仅仅是任何两个值,只是具有Eq
该类实例的值.这有点像C#中类型参数的约束:
public bool Equals(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
字典应该如何判断a Student
是否等于a Teacher
?他们甚至没有相同的领域.
简而言之,虽然Haskell类型的限制乍一看可能看起来像.NET类型约束,但它们的实现完全不同,并编译为两个非常不同的东西.
我们现在也可以用Haskell类型做其他事情.在Haskell中搜索"泛型"开辟了一个更高级别的多态泛型编程的整个领域,超出了大多数人认为是"泛型"的标准参数多态.
例如,GHC最近获得了类型系列,支持各种有趣的类型编程功能.一个非常简单的例子是任意多态容器的每类型数据表示决策.
我可以上课说,列表,
class Listy a where data List a -- this allows me to write a specific representation type for every particular 'a' I might store! empty :: List a cons :: a -> List a -> List a head :: List a -> a tail :: List a -> List a
我可以编写对实例化List的任何东西进行操作的函数:
map :: (Listy a, Listy b) => (a -> b) -> List a -> List b map f as = go as where go xs | null xs = empty | otherwise = f (head xs) `cons` go (tail xs)
然而,我们从未给出过特定的表示类型.
现在这是一个通用列表的类.我可以根据元素类型给出特定的狡猾表示.所以例如对于Int的列表,我可能会使用一个数组:
instance Listy Int where data List Int = UArray Int Int ...
所以你可以开始做一些非常强大的通用编程.
另一个很大的区别是C#泛型不允许对类型构造函数(即除了*之外的类型)进行抽象,而Haskell则是这样.尝试将以下数据类型转换为C#类:
newtype Fix f = In { out :: f (Fix f) }
要跟进,"你可能会遇到大麻烦,认为你用一种语言熟悉的概念在另一种语言[你是新的]中是相同的"这个问题的一部分:
这是使用Haskell类型类时需要了解的关键区别(比如说,Ruby).给定诸如的功能
add :: Num a => a -> a -> a add x y = x + y
但这并不意味着,x
和y
都是任何类型的类Num
.这意味着,x
和y
是完全相同的类型,其类型是类的Num
."好吧,当然你说; a和a一样." 我也是这样说的,但我花了好几个月的时间才停止思考如果x
是一个Int
并且y
是一个Integer
,那就像添加一个Fixnum
和Bignum
Ruby一样.而是:
*Main> add (2::Int) (3::Integer):1:14: Couldn't match expected type `Int' against inferred type `Integer' In the second argument of `add', namely `(3 :: Integer)' In the expression: add (2 :: Int) (3 :: Integer) In the definition of `it': it = add (2 :: Int) (3 :: Integer)
换句话说,子类化(虽然这两个Num
实例当然也是实例Eq
)和鸭子打字都没了,宝贝.
这听起来非常简单明了,但是需要花一些时间训练自己本能地理解这一点,而不仅仅是在智力上,至少如果你来自多年的Java和Ruby.
不,一旦我掌握了这一点,我就不会错过子类化.(好吧,也许有点不时,但我获得的收益远远超过我失去的.当我真的很想念它时,我可以尝试滥用存在类型.)