2011-01-28

Do you like ad-hoc polymorphism like I do?..

Sometimes I feel that C++ is more about doing something that you want in a some weird way and do it on many pages of code.
With Haskell I have another feeling - when I do something with it I understand what I wanted by reading it in a slim snipet of code.
Part of vector-space package from Haskell may look like:
#include <iostream>
#include <complex.h>

namespace typeclasses
{
    using std::pair;
    /*
     * based on: http://hackage.haskell.org/package/vector-space
     */


    /* Concept:
     * struct additive_group_traits<v>
     * {
     *   static const v zero;
     *
     *   static v sum(v, v);
     *   static v negate(v);
     * };
     */

    template <typename v>
    struct additive_group_traits;

    template <typename v>
    v operator+(v a, v b)
    { return additive_group_traits<v>::sum(a, b); }

    template <typename v>
    v operator-(v a)
    { return additive_group_traits<v>::negate(a); }

    template <typename v>
    v operator-(v a, v b)
    { return additive_group_traits<v>::sum(a, -b); }


    /* some instances */
    template<>
    struct additive_group_traits<int>
    {
        static const int zero = 0;
        static int sum(int a, int b) { return (a+b); }
        static int negate(int a) { return (-a); }
        /* note: operator specialization should fire first */
    };

    template<>
    struct additive_group_traits<float>
    {
        static const float zero = 0.0f;
        static float sum(float a, float b) { return (a+b); }
        static float negate(float a) { return (-a); }
    };

    template<>
    struct additive_group_traits<double>
    {
        static const double zero = 0.0;
        static double sum(double a, double b) { return (a+b); }
        static double negate(double a) { return (-a); }
    };

    template<typename u, typename v>
    struct additive_group_traits<pair<u, v> >
    {
        typedef pair<u, v> data;
        typedef additive_group_traits<u> u_ag;
        typedef additive_group_traits<v> v_ag;

        //static const data zero = data(u_ag::zero, v_ag::zero);
        static const data zero;

        static data sum(data a, data b)
        { return data(a.first + b.first, a.second + b.second); }

        static data negate(data a)
        { return data(-a.first, -a.second); }
    };

    template<typename u, typename v>
    const pair<u, v> additive_group_traits<pair<u, v> >::zero =
        pair<u, v>(additive_group_traits<u>::zero,
                   additive_group_traits<v>::zero);


    /* Concepts:
     * struct vector_space_traits<v>
     * {
     *   typedef ... scalar_type;
     *
     *   static v scale(scalar_type, v);
     * };
     *
     * struct inner_space_traits<v>
     * {
     *   static vector_space_traits<v>::scalar_type product(v, v);
     * }
     */

    template <typename v>
    struct vector_space_traits;

    template <typename v>
    v operator*(typename vector_space_traits<v>::scalar_type k, v a)
    { return vector_space_traits<v>::scale(k, a); }

    template <typename v>
    v operator*(v a, typename vector_space_traits<v>::scalar_type k)
    { return vector_space_traits<v>::scale(k, a); }

    template <typename v>
    struct inner_space_traits;

    template <typename v>
    typename vector_space_traits<v>::scalar_type
    vprod(v a, v b)
    { return inner_space_traits<v>::product(a, b); }


    template <typename v>
    v operator/(v a, typename vector_space_traits<v>::scalar_type k)
    { return vector_space_traits<v>::scale(1/k, a); }

    // Linear interpolation between @a@ (when @t==0@) and @b@ (when @t==1@).
    template <typename v>
    v lerp(v a, v b,
           typename vector_space_traits<v>::scalar_type t)
    { return (a + t * (b - a)); }

    /* some instances */
    template<>
    struct vector_space_traits<double>
    {
        typedef double scalar_type;
        static double scale(scalar_type k, double a) { return (k*a); }
    };

    template<>
    struct vector_space_traits<float>
    {
        typedef float scalar_type;
        static float scale(scalar_type k, float a) { return (k*a); }
    };

    template<typename u, typename v>
    struct vector_space_traits<pair<u, v> >
    {
        typedef pair<u, v> data;
        typedef vector_space_traits<u> u_vs;
        typedef vector_space_traits<v> v_vs;

        // TODO: assert that scalar types are equeal
        typedef typename u_vs::scalar_type scalar_type;

        static data scale(scalar_type k, data a)
        { return data(k * a.first, k * a.second); }
    };

}

using namespace std;
using namespace typeclasses;

template <typename u, typename v>
ostream &operator<<(ostream &s, pair<u, v> x)
{ return (s << "(" << x.first << ", " << x.second << ")"); }

ostream &operator<<(ostream &s, double complex z)
{ return (s << "(" << creal(z) << " + " << cimag(z) << "*i)"); }

namespace typeclasses
{
    template<>
    struct additive_group_traits<double complex>
    {
        typedef double complex data;
        static const data zero = 0.0;
        static data sum(data a, data b) { return (a+b); }
        static data negate(data a) { return (-a); }
    };

    template<>
    struct vector_space_traits<double complex>
    {
        typedef double scalar_type;
        static double complex scale(scalar_type k, double complex a)
        { return (k*a); }
    };
}

int main()
{
    pair<float, float> a = pair<float, float>(1, 1);
    pair<float, float> b = pair<float, float>(13, 7);
    pair<float, float> c = 3*(lerp(a, b, 1/3.f) - a);

    cout << c << endl;

    double complex za = 1 + 3*I;
    double complex zb = 6 + 3*za;
    cout << za << lerp(za, zb, 0.5) << zb << endl;

    return 0;
}