C++ class structure mapping

Daan Leijen daanleijen@xs4all.nl
Tue, 17 Jun 2003 11:30:51 +0200


Dear Abraham,

> Now comes the question of how to map a C++ class hierarchy 
> into haskell. 
> It seems natural to try to map C++ classes into haskell typeclasses;
> however, there are a few issues involved with that.  

You will quickly run into problems when doing that. The most 
important problem is that class instances are implicitly exported
(ie. have a global scope). When different C++ classes implement 
the same methods, you will get unresolvable name clashes in Haskell.

Fortunately, there exists a simple solution to model *single*
inheritance
object models in Haskell. It works with using phantom types where we
encode the inheritance tree in the phantom type variable.

Here is a quick example. Suppose we have the following classes:

 class Window { void Show(); };
 class Frame   : public Window { void SetTitle( const char* title ); };
 class Control : public Window { ... };
 class Button  : publice Control { void SetLabel( const char* label );
};

Every object in Haskell will be represented by a simple "Ptr a". The
phantom type "a" will encode the inheritance relation, that is, for
each class we will create a new "phantom" data type that represents
its class (or its part of the virtual method table).

  type Window a  = Ptr (CWindow a)
  data CWindow a = CWindow

Note that we will never use the "CWindow" constructor and that it
therefore
called a phantom data type. The  "CWindow" type represents the Window
class
by itself, the "a" type variable represents the classes that may extend
it.

  type Frame a  = Window (CFrame a)
  data CFrame a = CFrame

  type Control a = Window (CControl a)
  data CControl a = CControl

  type Button a = Control (CButton a)
  data CButton a = CButton

Ok, now that we have encoded the objects and inheritance relationships,
we
can give the signatures of all the methods:

  frameCreate :: IO (Frame ())
  buttonCreate :: IO (Button ())

  windowShow     :: Window a -> IO ()
  frameSetTitle  :: String -> Frame a -> IO ()
  buttonSetTitle :: String -> Button a -> IO ()

The creation function return objects where the type parameter is
instantiated
to unit () -- indeed such function creates exactly a Frame or Button,
but nothing
more. In contrast, a function like "windowShow" has its type parameter
uninstantiated
as it can receive any kind of window (any object that derives from a
window). This
corresponds to the contra-variance rule.

  do b <- buttonCreate
     windowShow b

We can see that the above code does indeed work, the buttonCreate
returns a "Button ()"
that can be passed whenever a "Window a" is expected:

  Button () == Control (CButton ()) == Window (CControl (CButton ())) ==
Window a


Pheew, quite a story. I hope it helps. You can read more about phantom 
types and inheritance in:

  Domain Specific Embedded Compilers, 
  Daan Leijen and Erik Meijer. 2nd USENIX Conference on Domain-Specific
Languages (DSL), Austin, USA, October 1999. 

  Calling Hell from Heaven and Heaven from Hell 
  Sigbjorn Finne, Daan Leijen, Erik Meijer, and Simon Peyton-Jones. 
  Proceedings of the International Conference on Functional Programming
(ICFP), Paris, France, 1999. 

and a recent paper with novel uses of phantom types to encode generic
type constraints:
	
  James Cheney and Ralf Hinze. Phantom types. (I think it is published
in "the fun of programming" ?)


> I'd like to make a haskell binding for a C++ library.  Most 

I have done this for a upcoming wxHaskell library: a binding to the 
wxWindows GUI library. Just as Alastair, I also think that it is better
to
create (or generate) a C interface to the C++ library first, ie.

extern "C" {
  void object_foo( Object* self, int arg )  
  {
    self->foo(arg);
  }
}

Furthermore, you probably need to generate dynamic link libraries
(or shared object files)  to link with GHC as the runtime systems
of C (=GHC) and C++ are incompatible on many platforms.

> First - it seems natural to use template haskell to do the code
> generation.  That way, the library could either write the 

I would advise against this: it is quite experimental and you would
lock yourself into a single implementation. (I have made that mistake
with haskellDB and TREX myself though :-).
Since you will generate code anyway, you probably have no need for
template stuff.

All the best, 
  Daan.




> A 
> separate datatype
> would have to be made for each C++ class to allow it to actually be
> instantiated, which isn't too bad.  However, to allow haskell 
> instances of
> the typeclass to call the old behavior it seems that there'd 
> have to be
> duplicate functions of the ones in the typeclass, i.e.
> 
> class A a where
> 	foo :: a -> IO ()
> 
> foo_cpp :: (A a) => a -> IO ()
> 
> That seems to be needed to allow haskell instances to call the old
> implementation in their definition, but it rubs me the wrong way.  Can
> anyone suggest an alternate method, or suggest a different direction
> entirely?
> 
> Abe
> 
> _______________________________________________
> Haskell mailing list
> Haskell@haskell.org
> http://www.haskell.org/mailman/listinfo/haskell
> 
>