21

Is it possible to emulate the type class functionality of Haskell with C++ (or C#) templates?

Does it make sense or is there any payoff in doing that?

I was trying to make a Functor class in C++ and I wasn't able. I tried something like this:

#include <iostream>
using namespace std;

//A function class to make types more readable
template <class input, class output> class Function {
private:
  output (*ptrfunc )(input);
public:
  Function(output (* ptr)(input)) {
    ptrfunc = ptr;
  }
  output call(input x) {return  (*ptrfunc)(x);}
  output operator() (input x) { return call(x);}
};


//the functor "typeclass"
template <class a> class Functor{
public:
  template <class b> Functor<b> fmap(Function<a,b> func);
};

// an container type to be declared "instance" of functor:
template <class a> class List : public Functor<a> { 
private:
  a * ptrList;
  int size;
public:
  List(int n) {  //constructor;
    ptrList = new a[n]; 
    size = n;
  }
  List(List<a> const& other) { //copy constructor
    size = other.size;
    ptrList = new a[size];
    for(int i = 0; i<size; i++)
      (*this)[i] = other[i];
  }
  ~List() { delete ptrList;} //destructor
  a& operator[](int i) { return ptrList[i];} // subscript operator just for easy notation
  const a& operator[](int i) const { return ptrList[i];}// subscript operator just for easy notation

  template <class b> List<b> fmap(Function<a,b> func) { //"instance" version of fmap
    List<b> temp(size);
    for(int i = 0; i < size; i++)
      temp[i] = func((*this)[i]);
    return temp;
  }
};


int test(int k) { return 2 * k;}

int main(void) {
  Function<int, int> func(&test);
  List<int> lista(10);
  for(int i = 0; i < 10; i++)
    lista[i] = i;
  List<int> lista2(lista.fmap(func));
  for(int i = 0; i < 10; i++)
    cout << lista2[i] << " ";
  cout << endl;
  return 0;
}

It does what it's supposed to do, but does it make any sense to use this pattern in C++? Is it really the same pattern as in haskell:

data List a = -- some stuff 

class  Functor f  where
fmap :: (a -> b) -> f a -> f b

instance (Functor List) where
-- some stuff    

It doesn't seem to be the same thing to me, cause in Functor f, f is a kind * -> * type constructor, and in my definition above in Functor<a>, a is not a template a<something>, but the "contained" data type itself.

Is there a way out to this? And more importantly: it makes sense to try to copy this kinds of patterns to C++? It seems to me that C# is more akin to functional programming style than C++. Is there a way to do this in C#?

Rafael S. Calsaverini
  • 13,582
  • 19
  • 75
  • 132
  • 8
    A good rule of thumb is that "no, it is not worth it to pretend to program in another language than you're using". If you want to write Haskell code, write it for a Haskell compiler. As long as you're writing C++ code, you're better off writing idiomatic C++ – jalf Dec 10 '10 at 17:09
  • 1
    perhaps http://osl.iu.edu/~kyross/pub/20040929-type-class-slides.pdf or http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.78.2151&rep=rep1&type=pdf will help. however, in your `Functor` case, the base class is absolutely pointless (because `fmap` cannot be declared `virtual`). – lijie Dec 10 '10 at 17:21
  • But yes you can write generic functors using templates. Though the style of doing so will be slightly different than in Haskal. – Martin York Dec 10 '10 at 20:03
  • 1
    I just want to point out that you might confuse C++ programmers with the term Functor because the term is abused in C++ to mean a functional object (which in some sense is 1 specific type of Functor). In haskell a Functor means a categorical Functor not C++ Functors (functional objects). – snk_kid Dec 10 '10 at 20:49
  • You might also want to read C++ concepts vs boost BCCL: http://stackoverflow.com/questions/1352571/whats-the-difference-between-c0x-concepts-and-the-boost-concept-check-library/1352630#1352630 . It's nowhere an exhaustive answer, but I believe it helps clarifying the matter of removed C++0x concepts. – Johannes Schaub - litb Dec 11 '10 at 11:42
  • The answer appears to be yes for C++: http://stackoverflow.com/questions/2565097/higher-kinded-types-with-c – Erik Kaplun Mar 02 '14 at 04:04

5 Answers5

32

Is it possible to emulate the type class functionality of Haskell with C++ (or C#) templates?

I am not sufficiently versed in C++ templates to answer the question. I can talk about C# generic types a bit though.

The short answer is, no. The Haskell system of "higher" type classes is more powerful than the generic type system in C#.

A brief discussion of what we mean by "higher types" might be useful at this point for any readers that are still reading this who are not familiar with Haskell. In C# you can do this:

interface IEnumerable<T> { ... }

The "generic" type "IEnumerable of one type parameter" is not really a "type" per se, it is a pattern from which you can construct infinitely many new types, by substituting type arguments ("int") for type parameters ("T"). In that sense it is "higher" than a normal type.

You can put constraints on type parameters of generic types:

class C<T> where T : IEnumerable<int>

Generic type C can be constructed with any type argument, so long as the type argument is a type that is implicitly convertible via reference or boxing conversion to IEnumerable<int>.

But the Haskell type system goes one step further than that. It supports "type classes" where the constraints you can put on the T are things like "T has an equality operator defined on it". In C#, operators are defined as static methods, and there is no analogue of an interface for static methods. We have no way in C# of generalizing across many types based on arbitrary static methods.

An example usually given for Haskell is the "monad" pattern. In C# notation, suppose we have a type:

class MyMonad<T>
{
    public static MyMonad<TOut> Bind<TIn, TOut>(MyMonad<TIn>, Func<TIn, MyMonad<TOut>>) { ... }
    public MyMonad(T t) { ... }
}

A monad is just a pattern; a monadic type is any generic type such that it has a static generic method Bind and a constructor that matches the pattern above. In Haskell you can use higher types to describe that pattern; in C#, we have no facility in the type system for generalizing on things like static methods and constructors.

Or, you might say that it would be more idiomatic to use an instance binder:

class MyMonad<T>
{
    public MyMonad<TOut> Bind<TOut>(MyMonad<T>, Func<T, MyMonad<TOut>>) { ... }
    public MyMonad(T t) { ... }
}

Does that help? No. Even leaving the problem with the constructor aside, we cannot come up with an interface that captures this pattern. We can try:

interface IMonad<T>
{
    public IMonad<TOut> Bind<TOut>(IMonad<T>, Func<T, IMonad<TOut>>);
}

But that is not right. That says that a monad is something that takes a monad and a function that returns a monad in, and returns a monad. That means that you could have two implementations of IMonad<T>, say Maybe<T> and Sequence<T> and then have a binder that takes a sequence and returns a maybe! That doesn't make any sense; the pattern we want to capture is

highertype Monad makes a pattern with TheImplementingType<T> like
{
    public TheImplementingType<TOut> Bind<TOut>(TheImplementingType<T>, Func<T, TheImplementingType<TOut>>);
}

but we have no way of expressing that in C#.

Let's consider your Functor example. In C# we might have a type

class List<T> 
{
    public static List<TOut> Map<TIn, TOut>(Func<TIn, TOut> mapper, List<TIn> list) 
    { ... }

Or, perhaps more idiomatically, an instance method:

class List<T> 
{
    public List<TOut> Map<TOut>(Func<T, TOut> mapper) 
    { ... }

Or, again more idiomatically, we might have the static method as an extension method. (In fact, this method does exist in the sequence operator library in C#; it can be built by composing "Select" on IEnumerable<T> with "ToList").

Whatever. Doesn't matter. The point is that in your code in Haskell:

class  Functor f  where 
fmap :: (a -> b) -> f a -> f b 

You can say that "any generic type which exposes a mapping operation that meets the pattern above is said to be a 'Functor'", and then you can make methods that take Functors. We don't have any way of generalizing "all the types that provide mapping operations" at the user level in C#.

To get around this limitation of the type system, what we've done is chosen a few of the most powerful higher types and built them directly into the language. The language itself recognizes higher types like the sequence pattern (in the foreach loop processing), the generalized monad pattern (in query comprehensions; "SelectMany" is "Bind" on an arbitrary monad), the continuation pattern (in "await" coming in C# 5), the "Maybe" monad (in nullable value types) and so on.

So to solve your specific problem, yes, no problem, the notion of making a projection is not captured in the type system but it is captured in the language with LINQ query comprehensions. If you say

from x in y select z

then any expression y of a type that has a Select method that takes y and a mapping from x to z will work. That pattern has been built into the C# language. But if you want to describe some other "higher" pattern, you're out of luck.

It would be nice to have a facility in the type system to describe higher types, but what is more likely is that we will keep the type system as-is, and bake more patterns into the language as-needed.

A place where I commonly see an attempt to emulate higher-order types in C# is described in this question:

Why does this generic constraint compile when it seems to have a circular reference

The idea here is that the developer wishes to develop a type "Animal" such that:

abstract class Animal
{
    public abstract void MakeFriends<T>(T newFriend)
    where T : THISTYPE;
}

The fictional "where T : THISTYPE" is attempting to get across the idea that a Cat can only make friends with another Cat, a Dog can only make friends with another Dog, and so on. (Ignore for the moment the fact that such a pattern, which implies that MakeFriends has virtual covariance on formal parameter types, would not be typesafe and would probably thereby violate the Liskov Substitution Principle.) That concept is expressible in higher types, but not in the C# type system. People sometimes use a pattern like:

abstract class Animal<T> where T : Animal<T>
{
    public abstract void MakeFriends(T newFriend);
}
class Cat : Animal<Cat>
{
    public override void MakeFriends(Cat newFriend){ ... }
}

However, this fails to actually enforce the desired constraint, because of course there is nothing whatsoever stopping you from saying:

class Dog : Animal<Cat>
{
    public override void MakeFriends(Cat newFriend){ ... }
}

And now a Dog can be friends with a Cat, violating the intention of the author of Animal. The C# type system is simply not powerful enough to represent all the sorts of constraints that you might want. You'd have to use higher types to make this work properly.

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • +1 overall, but: '...the "Maybe" monad (in nullable value types)...' <-- Really irked me. Nullable is such a step backwards and I don't really see how it "fits" higher. –  Dec 10 '10 at 19:35
  • @pst: Well, how would *you* add "maybe" types to a language that already had nullable reference types? It's all very well and good to be an ivory tower Haskell purist about things, but we have an installed base of millions and cannot arbitrarily introduce whole new concepts in the type system at will. Nullable value types are far from perfect; they are not intended to be perfect. They are intended to be a reasonable compromise given the existence of a previous version of the language and runtime. – Eric Lippert Dec 10 '10 at 19:44
  • @Eric Lippert See Scala and `Option`. Scala supports nullable types. One thing I dislike with `Nullable` (and why I find it does not "replace" Maybe) is that T is constrained to a value-type and does not have appropriate LINQ/extension support -- it is too specific. The dichotomy is not removed -- just some syntax to increase the number of nulls floating about. Yay. –  Dec 10 '10 at 19:54
  • @pst: Sure, Scala is a great language. But you didn't answer my question: the problem we faced with Nullable was not "how do we add maybe types to the type system?" It was "how do we add maybe types to the type system when there is already an installed base of millions of users who know how to use nullable reference types and want nullable value types?" That is a hard problem to solve. You can argue that this is evidence that nullable values types and non-nullable reference types should have been built in from v1.0, but we cannot alter the past, we can only live with it. – Eric Lippert Dec 10 '10 at 20:01
  • @Eric Lippert: `Maybe`, where T has no restrictions and has appropriate LINQ support -- perhaps I am missing some inner workings of `Nullable`, I do not know. `Option` in Scala is a normal type (no compiler magic whatsoever) deriving from AnyRef. It won't protect against someone using `null` where an `Option` was expected, nor does it care. Yet it fulfills the two short-comings I find with `Nullable`. The trick to `Option` being useful against NPEs (e.g. useful over using null values) is through [consistent] usage. There could be plugins or analysis to fill in the null->Option "gaps". –  Dec 10 '10 at 20:19
  • 3
    I don't understand the point of going on about .NET Generics when he is asking about C++ & Haskell, do not assume that because .net generics do not support higher-kinded polymorphism that this applies to C++ as well. C++ does support some kind of higher-kinded polymorphism using template-template parameters (yes I said template-template) but it is almost useless feature in C++ because it's quite different from normal template type parameters and lack of type inference, almost no support for type deduction of them. – snk_kid Dec 10 '10 at 20:24
  • 6
    @snk_kid: read the first paragraph of the question again, including the bit in parentheses. The question is about both the C++ and the C# type systems. I note that I am making no assumptions or statements whatsoever about the C++ template type system. – Eric Lippert Dec 10 '10 at 20:28
  • GREAT answer btw, @Eric Lippert. Thanks a lot. – Rafael S. Calsaverini Dec 10 '10 at 21:46
11

C++0x was going to introduce concepts which are quite similar to Haskell's type classes, but the feature got voted out. Be patient for another ten years, and they may finally make their way into the language.

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 3
    In addition, I'd like to mention that concepts/type classes are "just" a type system for types. C++0x is almost as powerful without them. Without concepts, some type checking is simply delayed (which may cause the infamous template error messages). – sellibitze Dec 10 '10 at 21:11
  • 2
    It's *much* easier to verify correctness of templates when using concepts. "some type checking is simply delayed" is not really true. It's more like "some type checking is simply not done". For example `vector< auto_ptr > x;` might silently "work" and cause undefined behavior. A proper concept constraint on the template parameter would have required `auto_ptr` to be CopyAssignable or whatever would be needed. – Johannes Schaub - litb Dec 11 '10 at 11:28
  • 2
    What you fundamentally *cannot* do is checking the template definition itself in C++ without concepts. Like, a common example, `template ForwardIterator find(ForwardIterator begin, ForwardIterator end) { ... begin + 1 ... }`. Suppose you have such a body. If all users pass vector iterators to `find` (which they are allowed), you won't ever notice your template is wrong. But in fact it *is* wrong because ForwardIterators don't have an `op+` conceptually. – Johannes Schaub - litb Dec 11 '10 at 11:33
  • 1
    Just dropping in to say - it is 10 years later, and concepts are here. – wesanyer Jul 10 '21 at 00:16
9

From the author of this blog post on Haskell and C++:

The ability to do compile-time calculations in C++ was discovered rather than built into the language.

While it's possible to emulate many Haskell features in C++ templates, the translated syntax is indeed ugly. That's mostly because metaprogramming in C++ was an accident rather than an originally conceived feature.

chrisaycock
  • 36,470
  • 14
  • 88
  • 125
  • 2
    This article is great, the same author also has another article which is titled [understanding c++ concepts through haskell type classes](https://bartoszmilewski.wordpress.com/2010/11/29/understanding-c-concepts-through-haskell-type-classes/) – Fabio Fracassi Dec 10 '10 at 21:43
  • 2
    And you might also like [C++ Next](http://cpp-next.com/), which shows a few real world use cases where this technique brings a real benefit – Fabio Fracassi Dec 10 '10 at 21:50
1

Even though C# and C++ were singled out in the post, it may be interesting to compare with Scala (2.8, target is JVM) instead. Scala is quite similar to C# as it is a strong/static single-dispatch "OO" language -- but it has a more powerful type-system than C#3/4 (the features aren't a full overlap, however).

I am not familiar with Haskell, but I have read various articles and heard a number of arguments over the years, and I believe that the typeclass functionality you are looking for can be derived with implicits (an example), even if not nearly as idiomatic as in Haskell.

On the other hand, F#, a "functional language", has poor (or non-existent? once again, not my area, but I believe it's not possible to create a Monad type in F#) support for typeclasses constructs. It definitely pays to play off the strengths of the language.

Happy coding.

  • F# does not have type-classes and does not support higher-kinded polymorphism as is the case with .NET in general however it does not mean you can not write monads without them it just means you can not make an abstraction for all kinds of Monads and thus write generic code over them. You can however write specific monad types and F# does have some syntactic support for that. – snk_kid Dec 10 '10 at 20:37
0

Maybe I'm missing some Haskell meta level here, but your actual example, without the declarations would look like this using the STL:

struct Test : public std::unary_function<Foo,SomeResult> {
    SomeResult operator()( const Foo& foo ) const;
};

std::vector<Foo> foos;
...

std::vector<SomeResult> results;
std::transform( foos.begin(), foos.end(), results.begin(), Test() );
....
Frank Osterfeld
  • 24,815
  • 5
  • 58
  • 70
  • 3
    As I've mentioned in another post, Functor means something quite different than the abused term used in C++, it is suppose to be a functor from category theory not C++ Functors. – snk_kid Dec 10 '10 at 20:39