Functional Imperative Rosetta Stone

This page is an attempt to compare typical constructs in the functional style to more-or-less equivalent constructs in the imperative style. Some of the "equivalence" is glossed over. Naturally, both paradigms (and the languages used below) have capabilities that are under-represented by these examples.


HaskellLanguage: data type

    data Position = Position { px, py :: Double }

CeePlusPlus: struct or class

    struct Position
    {
        double _x, _y;
        Position(double x,double y) : _x(x), _y(y) { }
    }


HaskellLanguage: type alias

    type Line = (Position, Position)

CeePlusPlus: typedef

    typedef pair<Position,Position> Line;


HaskellLanguage: function

    dotProduct :: Vector -> Vector -> Double
    dotProduct vector1 vector2 
        = ((vx vector1) * (vx vector2)) + ((vy vector1) * (vy vector2))

CeePlusPlus: function

    double dotProduct(Vector vector1, Vector vector2)
    {
        return (vector1._x * vector2._x) + (vector1._y * vector2._y);
    }


HaskellLanguage: algebraic data type

    data Shape
        = Circle { circle_center :: Position, circle_radius :: Double }
        | Square { square_origin :: Position, square_size :: Double }

shapeCenter :: Shape -> Position shapeCenter (Circle center radius) = center shapeCenter (Square origin size) = positionMove origin offset where offset = Vector size/2 size/2

CeePlusPlus: abstract base class

    struct Shape
    {
        virtual Position center() = 0;
    }

struct Circle : public Shape { Position _center; double _radius; Circle(Position center, double radius) : _center(center), _radius(radius) { } virtual Position center() { return _center; } }

struct Square : public Shape { Position _origin; double _size; Square(Position origin, double size) : _origin(origin), _size(size) { } virtual Position center() { Vector offset(_size/2,_size/2); return _origin.move(offset); } }


HaskellLanguage: List data type

    data List a = EmptyList | ListNode { first :: a, rest :: List a }

headList :: List a -> a headList EmptyList = error "can't take the head of an empty list" headList ListNode first rest = first

tailList :: List a -> List a tailList EmptyList = error "can't take the tail of an empty list" tailList ListNode first rest = rest

lengthList : List a -> Int lengthList EmptyList = 0 lengthList ListNode first rest = 1 + lengthList rest

CeePlusPlus: translation of the List data type

    template<A> struct List
    {
        virtual A head() = 0;
        virtual List<A>& tail() = 0;
        virtual int length() = 0;
    }

template<A> struct EmptyList : public List<A> { virtual A head() { throw exception("can't take the head of an empty list"); } virtual List<A>& tail() { throw exception("can't take the tail of an empty list"); } virtual int length() { return 0; } }

template<A> struct ListNode : public List<A> { A _first; List<A>& _rest; ListNode(A first,List<A>& rest) : _first(first), _rest(rest) { } virtual A head() { return _first; } virtual List<A>& tail() { return _rest; } virtual int length() { return 1 + _rest.length(); } }


More to come (this page is a work in progress). Feel free to add your own examples.

I think that trying to compare algebraic data types to object-oriented classes is missing out on an important distinction. In languages like C++ and Java, objects contain both data and code, intermingled freely. By contrast, Haskell enforces a much stricter separation; algebraic data types may contain only data and no methods. (The accessor methods used above are merely syntactic sugar--they are, and could easily be defined as, standalone functions.) In this respect, they are much closer to Pascal's variant record types than they are to classes.

Similarly, features like abstract data types and advanced polymorphism are provided by typeclasses; in their simplest form, these play a role much like Java's interfaces. They are allowed to contain method signatures (and, unlike Java, default implementations) but no data.

Since in Haskell functions are data, data structures do contain code. This puts Haskell's records quite close to Java's interfaces, while Java doesn't really have an equivalent for typeclasses. Subtype relations are also different and hard to compare.

On that point - if you are going to make this comparison, then since Haskell functions are data, the C++ equivalent would more properly be defined as a FunctionObject (more specifically, see StlFunctionObjects), something like:

    struct DotProduct {
        double operator() (const Vector &vector1, const Vector &vector2) const {
            return (vector1._x * vector2._x) + (vector1._y * vector2._y);
        }
    };

Only if DotProduct is a friend of Vector, and then, why bother? Just write it as a normal function.

Remember, the distinction between ADTs and objects is only from which side of the (method,type) matrix you're looking at things.


CategoryFunctionalProgramming RosettaStone ProgrammingChrestomathy


EditText of this page (last edited June 6, 2011) or FindPage with title or text search