Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can traits in D be used for type classes?

I'm new to D, and I'm looking for a good way to program with Haskell-like type classes e.g. Functors, Monoids, etc. in D.

Is something like this implemented in Tango or Phobos?

I've heard about traits which enable compile-time type checking for certain properties. Can they be used for type classes?

I've tried a little bit with template specialization and come up with this:

// Monoid.d
// generic Monoid gets called when there is no instance of Monoid for Type T
class Monoid(T) {
    pragma(msg, "Type is not a Monoid");
}

// Monoid instance for double
class Monoid(T:double) {
    static T mzero() { return 0; }
    static T mappend(T a, T b ) { return a + b;}
}

// Monoid instance for int
class Monoid(T:int) {
    static T mzero() { return 0; }
    static T mappend(T a, T b ) { return a + b;}
}

A generic algorithm whose type parameter needs to be a Monoid could then be expressed as:

template genericfunctions() {
    T TestMonoid(T,N = Monoid!T)(T a) {
        return N.mappend(N.mzero(),a);
    }
}

However, if you want to omit the template parameters, you have to import all needed Monoid instances and mixin the genericfunctions template.

import Monoid;
import std.stdio;
import std.conv;
mixin genericfunctions;

void main() {
    writefln(to!string(TestMonoid(3))); 
    writefln(to!string(TestMonoid(3.3243))); 
}

You can now use ints and doubles as Monoids.

However things get more complex when you have a type class like Functor whose instances are itself generic:

module Functors;

// generic Functor like generic Monoid
class Functor(alias T, A) {
    pragma(msg,"Not an instance of Functor");
}

// very simple container to demonstrate functors behavior
class FunctorTest(A) {
    public A a; 
    this(A a) {
        this.a = a; 
    }
}

// instance of Functor for FunctorTest!A 
class Functor(alias T:FunctorTest,A) {
    static T!B fmap(B)(T!A a, B delegate(A) fn) {
        return new T!B(fn(a.a));
    }
}

One algorithm would look like this:

template genericfunctions() {
    T TestMonoid(T,N = Monoid!T)(T a) {
        return N.mappend(N.mzero(),a);
    }

    // F is the Functor, A the functors type before,
    // B the functors Type after, N is the instance of Functor
    F!B fmap(alias F,A,B,N=Functor!(F,A))(F!A a, B delegate(A) fn) {
        return N.fmap!B(a,fn);
    }
}

Luckily, you can omit the four template parameters when you use it:

mixin genericfunctions;

void main() {
    auto a = new FunctorTest!int(3);
    auto b = fmap(a,(int b) {return b+ 0.5;});
    writefln(to!string(b.a));
}

But when you want to use another Functor instance for the Type you have to specify all 4 type parameters of fmap. Is there a way in which you only need to specify the Instance and the other parameters could be deduced from this?

Is there an alternative to the clumsy mixin workaround?

Are there other disadvantages of this approach which I don't see?

What about other ways?

Thanks for reading this far and for taking the time to think and answer :)


Edit:

Is it possible to define constraints like the functor laws with unittest in D? That would be very nice.

like image 253
KIMA Avatar asked Jun 13 '11 09:06

KIMA


People also ask

Are rust traits type classes?

Rust's “traits” are analogous to Haskell's type classes. The main difference with Haskell is that traits only intervene for expressions with dot notation, i.e. of the form a. foo(b). Traits in Rust do not "only intervene for expressions with dot notation".

What are type classes used for?

Type classes are a powerful tool used in functional programming to enable ad-hoc polymorphism, more commonly known as overloading.

What is a type class dictionary?

Type classes define interfaces that in Haskell's terms are called dictionaries. For instance, from the Ord class definition the compiler will create a dictionary that stores all the class methods.

What is a Typeclass in Haskell?

Type Classes are a language mechanism in Haskell designed to support general overloading in a principled way. They address each of the concerns raised above. They provide concise types to describe overloaded functions, so there is no expo- nential blow-up in the number of versions of an overloaded function.


2 Answers

template genericfunctions() {
  T TestMonoid(T,N = Monoid!T)(T a) {
    return N.mappend(N.mzero(),a);
  }
}

No need for that:

T TestMonoid(T,N = Monoid!T)(T a) {
  return N.mappend(N.mzero(),a);
}

That should suffice. With this, there's no need for the mixin either.

Is it possible to define constraints like the functor laws with unittest in D?

Not entirely sure I understand what you are asking for, but you can define contraints with template functions/classes:

void isEven(T)(T x) if (isIntegral!T) { return x % 2 == 0; }

This template will only then instantiate if T is an integral type.

See the 'Template Constraints' section at the bottom of the Templates page.

like image 93
Peter Alexander Avatar answered Sep 25 '22 20:09

Peter Alexander


Rather than answer your question, as that would require understanding what you have said. I'm just going to ramble on about features of D that you are using and those that might be of use to you.

D doesn't have Type Classes (as you know). Instead it has type specialization (which you are using) and template constraints. Type specialization came prior to template constraints and can in fact be used there.

A template constraint allows you to require certain properties of a type. You will find this is heavily used in std.range and there are templates which help write such constraints in std.traits. I may do a more complicated example but for now, this accepts types which convert to int:

void myFunction(T)(T param) if(is(T:int)) {
}
like image 24
he_the_great Avatar answered Sep 22 '22 20:09

he_the_great