Motivated by my recent interest in Haskell, I thought it might be a good idea to share my experience and my thoughts about the programming language. So I decided to write a series of posts that represent my personal observations about why I think that Haskell can be a good fit for enterprise applications development. I’ll try in these posts to present my ideas on different levels (enterprise, architecture and software modules). The aim is not to criticize any of the mainstream programming language but rather to do an enterprisey introduction of why Haskell is good for enterprise development.
In this post I’ll try to dive a bit into abstraction tools available in programming languages. I’ll try to observe the effect that these abstraction constructs have on the overall design and architecture.
OOP Classes and Inheritance are useful but limited abstraction tools
In mainstream programming languages (like C# and Java), programmers mostly use classes as the main (if not the only) abstraction tool. Classes in OOP languages benefit from inheritance as a tool for generalization. Inheritance is really a useful concept for organizing generalizations and abstractions into a hierarchy in which each level introduces and adds more functionality building on the higher (parent) levels. However, Classes in these languages can be used to abstract data structure as well as functions and behavior, actually that is the whole idea behind their use here: To make a sort of cohesion of data structure with related behavior. This coupling between the data structure and the related behavior (so called methods) has a negative effect on the abstraction and generalization hierarchy and concepts reuse. What I mean by this is that in a hierarchy of classes built on these bases one can’t choose to reuse just behavior code or just data structure because they are bound to each other, and the behavior is built for its associated data structure and not for properties in it.
One can argue that Multi inheritance provides a solution to this problem. With multi inheritance you can choose to combine functionality in one class and represent your data type with another, then you can choose to inherit data structure only, or functionality only, or both. But you can not go much further with such an approach before discovering its limitation. We still have the same problem here, we can not base a behavior on properties of a data structure without combining both into one class bringing the same coupling and forcing extender to inherit both.
Ideally, what I want to reach is a system where data abstraction and function abstraction are not inherently coupled, and where one can freely build abstraction based on one another. By enabling data abstraction and function abstraction to vary independently, you enable more code reuse: Abstractions that are not necessarily bound to a particular data structure can be identified, and though inherited by any data type that satisfies certain conditions.
Take for instance Comparability property: We can compare different objects of a class if the class implements, say, the IComparable operations, let’s say lessThan(object theOther) greaterThan(object theOther) and equals.
Here, IComparable can be based on IEquatable which specifies operations that deal with equality, suppose they are Equals and NotEquals. This way one does not need to implement the Equals operation to satisfy the IComparable contract. Moreover, in this case we need only to implement the lessThan for the target class because greaterThan can be implemented in terms of IEquatable together with the lessThan implementation and this implementation can be generalized for any class that want to implement operations of IComparable.
In Java or C#, this can be implemented using an abstract class that implements the greaterThan in terms of the method lessThan that is left abstract, and the equals that it inherits from its base class (the IEquatable ). The problem with this is that after doing that you’ve used your only opportunity to extend another class. In other words, in languages that do not support multi inheritance, it is impossible to satisfy in the same manner (based on abstract classes) another property if it doesn’t belong to the same hierarchy.
What about interfaces? Well, interfaces do not provide any straightforward solution to this problem. Interfaces leave the whole implementation to the implementing classes, and so do not generalize any implementation code (do not promote implementation generalization).
Multi-inheritance in languages that support it resolves partially this limitation. However, both limit the extension not only to design time, but also to declaration site. This makes it so hard to extend classes or types that are coming from another API or package. So in our previous example, there is no way to make a type Comparable outside its declaration site without introducing another type that extends it.
Extension methods are a halfway
Extension methods is a new feature in C# 3.0. With extension method you can extend a type outside its declaration site. By extending I mean adding methods that operate on the type, and in the case of extension methods they are discoverable by using the dot “.” exactly as if they were instance methods ( explaining extension methods is out of the scope of this post). Extension methods allow extending classes outside their declaration scope which solves one of our problems. However extension methods have one main limitations: You can not build a definition of some extension methods on constrains based on other extension methods. They are not composable. (C# team had to alter the compiler to introduce the LinQ expression language that requires implementing few specific extension methods to enable it for your type).
Type Classes In Haskell
Type Classes are a perfect solution to our design problem. First of all, Type Classes provide a very clean separation between Data Abstraction and Function Abstraction. Type classes look a bit like java and C# generic interfaces with some differences:
1: Type Classes allow a default implementation which enables implementation generalization
2: Type Classes instance declaration (interface implementation for a specific type) can be done outside the type declaration site, or even outside the whole module that contains the type. This removes any constrains of extensibility.
So with Type Classes you can easily extend existing types to satisfy your defined properties, and build on that to promote a wide implementation generalization and code reuse.