[Haskell-cafe] Very silly

Juan Carlos Arevalo Baeza jcab.lists at JCABs-Rumblings.com
Thu Oct 16 12:54:20 EDT 2008


Andrew Coppin wrote:
> Some guy told me that templates are "the best feature in the 
> language", and proceeded to show me a huge chunk of highly 
> complex-looking code which is approximately equivilent to
>
>  join :: Array x -> Array x -> Array x
>
> I was unimpressed.

  Now I'm curious to know what chunk of code he showed you. FWIW, that 
declaration you put there is done in C++ like so:

template < typename X >
Array<X> join(Array<X> const&, Array<X> const&);

  Nothing more. I like both languages for very different reasons. C++ 
for its insane amount of flexibility, and Haskell for its 
expressiveness. And I've encountered equally harsh roadblocks in both 
whenever I try to get "too crazy" while using them.

> Actually, that's a lie. I was impressed that such a low-level language 
> could manage even that much abstraction. But I still prefer the 
> Haskell way...

  The dynamic range of the C++ language is probably unparalleled. In 
particular, templates enable implementing high level abstractions in a 
way such that we're still discovering how far it can go. But of course, 
it's not just templates: it's templates when united to all other 
language features like overloading.

  Maybe I can take a crack at my own comparison of simple polymorphism 
between C++ and Haskell.

C++:
   template < typename A > struct B { A m; }; // Polymorphic type 
definition
   template <> struct B<bool> { int m; }; // Polymorphic type 
specialization
   template < typename A > struct B<B<A> > { A m; }; // Polymorphic type 
partial specialization
   template < int N > struct B<int[N]> { int m[N]; }; // Non-type 
polymorphic parameters
   template < typename A > struct C { typedef B<B<A> > type; }; // 
Polymorphic type aliases (type functions) must use trickery and bad syntax
   B<int> const b = { 8 }; // Declare object of polymorphic type
   template < typename A > A get(B<A> const& b) { return b.m; } // 
Polymorphic function using polymorphic type
   int get(B<int> const& b) { return b.m + 1; } // Specialization of 
polymorphic function (actually, it is an overload - functions can't be 
specialized, but this is nearly equivalent)
Haskell:
   data B a = B { m :: a } -- Polymorphic type definition
   // Polymorphic type specializations not available
   // Non-type polymorphic parameters not available (must emulate using 
Peano and such)
   type C a = B (B a) -- Polymorphic type aliases (type functions)
   let b = B 8 :: B Int in -- Declare object of polymorphic type
   get :: B a -> a; get b = m b -- Polymorphic function using 
polymorphic type (declaration and definition separate)
   // Specialization of polymorphic function not available without 
putting the function in a type class

  C++ templates are clearly more flexible, but they are also a lot 
clunkier to use (and both things are very much related). If you look at 
the specializations, and understand them, you'll see that it's easy to 
make a specialization that is incompatible with the main parametric 
(template) type definition. Sometimes, that's a good thing. I've created 
main templates that purposefully fail to compile when used, as a means 
to detect types that need their own particular specialization. But it's 
easy to see how they can have... unexpected consequences. For instance:

B<B<int> > const bint = { 7 };
B<int> const bintresult = get(bint); // Compiler error! Can you see why? 
A specialization is to blame.

  But the main hurdle is that a template's definition is partially 
untyped. Type-checking of the innards of a template is finalized at the 
time the template is invoked, at which point it is too late for the 
compiler to root-cause any failures it encounters. Thus, using complex 
template-based libraries can be quite inconvenient if you don't get 
things right on your first try. For instance:

C++:
   template < typename A > bool compare(B<A> const& b1, B<A> const& b2) 
{ return b1.m == b2.m; }; // Another polymorphic function
   B<SomeType> val;
   compare(val, val); // This gives out horrible errors if SomeType 
doesn't have the equality operator defined
Haskell:
   compare :: Eq a => B a -> B a -> Bool; compare b1 b2 = m b1 == m b2 
-- Another polymorphic function
   let val = ... :: B SomeType in
   compare val val // This Complains if SomeType is not an instance of Eq

  This is a silly example, and the C++ errors you get are not quite so 
bad. But they can get really, really bad. The new C++ standard they are 
finalizing will "fix" this by adding concepts (akin to type classes) and 
concept_maps (akin to instances). It is interesting to realize, though, 
that in C++, templates must all be resolved at compile time,  whether 
they use concepts or not. There's never any dictionary-passing happening 
anywhere, and all polymorphic functions must be specialized for all the 
different types that are used with them. For runtime polymorphism, C++ 
uses OO classes which are completely separate. OO classes are in some 
way like Haskell classes, but the dictionaries must be embedded in the 
objects themselves, whereas Haskell classes pass the dirctionaries into 
polymorphic functions separately. If a class is a salad, and the 
dictionary is the dressing, then C++ serves the dressing in the salad, 
and Haskell serves it on the side. The main consequence for this is 
that, in C++, class instances must be attached at the point of declaring 
the type, whereas in Haskell, you can do it after the fact.

  Maybe this is not quite so clear or useful after all. One-liners can 
be quite obtuse. And the craziness with templates shines when you 
combine them with various other features of the language. You can even 
make things like Boost.Proto 
(http://boost-sandbox.sourceforge.net/libs/proto/doc/html/index.html), 
which is a library for creating arbitrary DSLs in C++.

JCAB




More information about the Haskell-Cafe mailing list