Here's one difference to keep in mind:
C# has subtyping, but Haskell does not, which means, for one thing, that you know more things by simply looking at a Haskell type.
id :: a -> a
This Haskell function takes a value of a type and returns that same value of that same type.
If you give it a Bool
, it will return a Bool
. Give it a Int
, it will return a Int
. Give it a Person
, it will return a Person
.
In C#, you can't be so sure. This is that 'function' in C#:
public T Id<T>(T x);
Now, because of subtyping you could call it like so:
var pers = Id<Person>(new Student());
While pers
is of type Person
, the argument to the Id
function is not. In fact pers
might have a more specific type than just Person
. Person
could even be an abstract type, guaranteeing that pers
will have a more specific type.
As you can see, even with a function as simple as id
the .NET type system already allows for a lot more than the stricter type system from Haskell
. While that might be useful to do some programming work, it also makes it harder to reason about a program by just looking a the types of things (which is a joy to do in Haskell).
And a second thing, there is ad hoc polymorphism (aka overloading) in Haskell, via a mechanism known as 'type classes'.
equals :: Eq a => a -> a -> Bool
This function checks if two values are equal. But not just any two values, just the values that have instances for the Eq
class. This is sort of like constraints on type parameters in C#:
public bool Equals<T>(T x, T y) where T : IComparable
There is a difference, however. For one thing, the subtyping: you could instantiate it with Person
and call it with Student
and Teacher
.
But there is also a difference in what this compiles to. The C# code compiles to almost exactly what its type says. The type checker makes sure the arguments implement the proper interface, and than you're good.
Whereas the Haskell code complies to something like this:
equals :: EqDict -> a -> a -> Bool
The function gets an extra argument, a dictionary of all the functions it needs to do the Eq
things. Here's how you could use this function, and what it compiles to:
b1 = equals 2 4 --> b1 = equals intEqFunctions 2 4
b2 = equals True False --> b2 = equals boolEqFunctions True False
This also shows what makes subtyping such a pain, imagine if this where possible.
b3 = equals someStudent someTeacher
--> b3 = equals personEqFunctions someStudent someTeacher
How is the personEqFunctions
dictionary supposed to figure out if a Student
is equal to a Teacher
? They don't even have the same fields.
In short, while Haskell type constraints at first sight might look like .NET type constraints, they are implemented completely differently and compile to two really different things.