Let's use circles and rectangels to motivate some analysis. In Haskell you might define: data Point = Point { x :: Float, y :: Float }
deriving(Generic)
data Shape =
Circle { center :: Point, radius :: Float }
| Rectangle { topLeft :: Point, bottomRight :: Point }
deriving(Generic)
In C# you could similarly define class Point {
public float _x, _y;
public Point(float x, float y) {
_x = x;
_y = y;
}
}
interface Shape {}
class Circle {
public Point _center;
public float _radius;
public Circle(Point center, float radius) {
_center = center
_radius = radius
}
}
// etc, for Rectangle ...
Let's start from the C# end of things. Let's say we want to do some tests on random Shapes and that we want to get an instance of a random shape by calling Generate.random<Shape>(). There are a couple of different ways to do this. We could explicitly define a RandomShapeFactory and a RandomPointFactory and then wire those up into the Generate class. Maybe we could use reflection to dynamically generate instances of Points and Shapes from their definitions at runtime. It would probably end up looking like some combination of the two.Haskell doesn't let you magically generate random instances of Shape. The cool thing it gives is the "deriving(Generic)" part. Effectively, this causes the compiler to generate a description of Point and the Shape-Circle-Rectangle set of types. In many ways, it's like C#'s reflection. The QuickCheck library uses this reflection-like definition to generate random instances. The core functionality isn't very different, but Haskell's approach has a number of advantages. Most importantly, it's type-checked at compile-time. In the C# version, I could call Generate.random<Cat> and the code would blow up at run-time, whereas we would just get a compiler error in Haskell. The other major advantage in this case is the reduction of boiler-plate. There's no need to register factory instances inside of some singleton object at startup, the type system can infer which Generic components are needed where. C# and Haskell are both turning complete, so you can do anything in either language. Haskell makes certain types of programming more enjoyable by reducing boiler-plate and preventing annoying runtime errors. EDIT - code formatting |
You would only need to do that if you wanted to define custom behavior, instead of automatically derived behavior, and registering a few delegates is the same amount of work as manually implementing an instance of Arbitrary and CoArbitrary.