I\'m pretty new a Haskell programming. I\'m trying to deal with its classes, data, instances and newtype. Here is what I\'ve understood:
data NewData = Const
The data types of Haskell are not exactly the same as any particular C# construct. The best you can hope for is to get a simulation of some features. It's really best to understand Haskell types on their own terms. But I'll take a stab at it.
I don't have a C# compiler handy, but I am referring to the documentation to hopefully produce something close to correct. I'll edit later to fix errors if they're pointed out to me.
First of all, an algebraic data type in Haskell is closest to a family of OO classes rather than a single class. The parent class is completely abstract aside from a single field that discriminates the concrete subclasses. All public users of the type must accept only the parent class, and then perform case analysis via the discriminator field and do a type-cast to the more specific subclass indicated by the discriminator.
class NewData {
// every piece of NewData may take one of two forms:
static enum Constructor { C1, C2 }
// each piece of data has a discriminator tag; this is the only structure
// they all have in common.
Constructor discriminator;
// can't construct a NewData directly
private NewData() {}
// private nested subclasses for the derived types
private class Constr1Class : NewData {
int a, b;
Constr1Class(int a, int b) {
this.discriminator = NewData.C1;
this.a = a;
this.b = b;
}
}
private class Constr2Class : NewData {
string c;
float d;
Constr2Class(string c, float d) {
this.discriminator = NewData.C2;
this.c = c;
this.d = d;
}
}
// A bunch of static functions for creating and extracting
// I'm not sure C# will be happy with these, but hopefully it is clear
// that they construct one of the derived private class objects and
// return it as a parent class object
public static NewData Constr1(int a, int b) {
return new Constr1Class(a, b);
}
public static NewData Constr2(string c, float d) {
return new Constr2Class(c, d);
}
// We can't directly get at the members since they don't exist
// in the parent class; we could define abstract methods to get them,
// but I think that obscures what's really happening. You are expected
// to check the discriminator field first to ensure you won't get a
// runtime type cast error.
public static int getA(NewData data) {
Constr1Class d1 = (Constr1Class)data;
return d1.a;
}
public static int getB(NewData data) {
Constr1Class d1 = (Constr1Class)data;
return d1.b;
}
public static string getC(NewData data) {
Constr2Class d2 = (Constr2Class)data;
return d2.c;
}
public static float getD(NewData data) {
Constr2Class d2 = (Constr2Class)data;
return d2.d;
}
}
No doubt you will criticize this as terrible OO code. It certainly is! Haskell's algebraic data types do not claim to be Objects in the Object-Oriented sense. But it should at least give you a sense of how ADTs work.
As for type classes, they do not have anything to do with object-oriented classes. If you squint, they look kind of like a C# Interface, but they are not! For one, type classes can provide default implementations. Type class resolution is also purely static; it has nothing to do with run-time dispatch, as the functions that will be called have all been determined at compile time. Sometimes, the instance of the type class that will be used depends on the return type of a function call rather than any of the parameters. You are best off not even trying to translate it into OO terminology, because they're not the same thing.
GHC's implementation of type classes actually works by creating a dictionary that is passed as an implicit parameter to a function that has a type class constraint in its signature. I.e., if the type looks like Num a => a -> a -> a
, the compiler will pass an extra parameter with the dictionary of the Num
-specific functions used for the actual type used as a
at that call site. So, if the function was called with Int
parameters, it would get an extra dictionary parameter with functions from the Int
instance of Num
.
In essence, the signature is saying "This function is polymorphic as long as you can supply the operations in the Num type class for the type you want to use" and the compiler does provide them as an extra parameter to the function.
That being said, GHC is sometimes able to optimize away the whole extra dictionary parameter entirely and just inline the necessary functions.