Next Previous Contents

6. Data Interface Schemes

A Data Interface Scheme, or DIS, tells Green Card how to translate from a Haskell data type to a C data type, and vice versa.

6.1 Forms of DISs

The syntax of DISs is given in Figure fig:dis-syntax . It is designed to be similar to the syntax of Haskell patterns. A DIS takes one of the following forms:

  1. The application of a DIS macro to zero or more arguments. Like Haskell functions, a DIS macro starts with a lower-case letter. DIS macros are described in Section sec:dis-macro . Standard DIS functions include int, float, double; the full set is given in Section sec:dis-std . For example:
      %fun foo :: This -> Int -> That
      %call (this x y) (int z)
      %code r = c_foo( x, y, z );
      %result (that r)
    
    In this example this and that are DIS functions defined elsewhere.
  2. The application of a Haskell data constructor to zero or more DISs. For example:
    newtype Age = Age Int
    %fun foo :: (Age,Age) -> Age
    %call (Age (int x), Age (int y))
    %code r = foo(x,y);
    %result (Age (int r))
    
    As the %call line of this example illustrates, tuples are understood as data constructors, including their special syntax. The labelled fields syntax is also supported, i.e.:
    data Point = Point { px,py::Int }
    
    %fun foo :: Point -> Point
    %call (Point { px = int x, py = int y })
    ...
    
    The use of records is also the reason for the restriction that simple C expressions can't contain assignment. Without this restriction examples like this would be ambiguous:
    %result Foo { a = bar x, b = bar y }
    
    Green Card does not attempt to perform type inference; it simply assumes that any DIS starting with an upper case letter is a data constructor, and that the number of argument DISs matches the arity of the constructor.
  3. The application of a user function to one or more DISs This form allows you to do user defined marshalling, using a pair Haskell functions. Since DISs are used to either pack/marshall a Haskell value into a form that can be passed to C and unpack values that come back, a pair of Haskell functions is required. For example
    data Nat = Zero | Succ Nat
    fromNat :: Nat -> Int
    toNat   :: Int -> Nat
    
    %fun square :: T -> T
    %call (< fromNat / toNat > (int x))
    %code r = square(x);
    %result (< fromNat / toNat > (int r))
    
    Here the function fromNat is applied to square's argument, converting it to an integer before it crosses the fence into C. Likewise, the result coming back from C is converted back to type Nat by the function toNat. The user functions can have any name at all: in fact, the <../..> syntax simply encloses two fragments of arbitrary Haskell to be applied to the succeeding arguments. One may specify a partially applied function, or anything else (excluding the use of the / and > symbols - so lambda abstractions are unfortunately not possible.) The user-defined DIS may of course also take more than one parameter. For example:
    data Point = P Dist Vector
    polarToCart :: Polar     -> (Int,Int)
    cartToPolar :: (Int,Int) -> Polar
    
    %fun mirror :: Polar -> Polar
    %call (< polarToCart / cartToPolar > (int x) (int y))
    %code y = -y;
    %     x = -x;
    %result (< polarToCart / cartToPolar > (int x) (int y))
    
    Notice that all the example marshalling functions have so far been pure functions, e.g., fromNat has type Nat -> Int rather than T -> IO Int.) Sometimes you need to write a marshalling function that is internally stateful. When you do, you'll need to inform Green Card of this, so that it can generate code that invokes the marshalling functions correctly. For example:
    marshallString   :: String -> IO Addr
    unmarshallString :: Addr   -> IO 
    
    %fun setWindowTitle :: String -> IO ()
    %call (<< marshallString / unmarshallString >> str)
    
    i.e., stateful marshalling functions are enclosed by double angle brackets.
  4. A C type cast Occasionally one wishes to declare and use a C variable at a type which slightly differs form the type produced by a standard DIS, although it shares the same machine representation. The declare {cexp} var in dis form can be used to do the necessary type conversion in C. Examples:
    %fun foo :: Int -> IO ()
    %call (declare {unsigned int} x in (int x))
    
    data T = MkT Int
    %fun faz :: T -> IO ()
    %call (declare {c_t} x in MkT (int x))
    
  5. The application of a base DIS to exactly one variable. This is the primitive form of a DIS -- the way all values actually get passed across the Haskell - C boundary. Base DISs denote a fixed set of primitive types known to both C and Haskell, such as int and Int respectively, and consist of the Haskell type name prefixed by %% (e.g., %%Int.) Because the exact set of base DISs may vary slightly between compilers, it is recommended that programmers use the standard DIS macros listed in Section sec:dis-std instead. The base form is noted here primarily for completeness.

6.2 DIS macros

It would be unbearably tedious to have to write out complete DISs in every procedure specification, so Green Card supports DIS functions in much the same way that Haskell provides functions. (The big difference is that DIS functions can be used in ``patterns'' -- such as %call statements -- whereas Haskell functions cannot.)

DIS macros allow the programmer to define abbreviations for commonly-occurring DISs. For example:

newtype This = MkThis Int (Float, Float)
%dis this x y z = MkThis (int x) (float y, float z)

Along with the newtype declaration the programmer can write a %dis function definition that defines the DIS function this in the obvious manner.

DIS macros are simply expanded out by Green Card before it generates code. So for example, if we write:

%fun f :: This -> This
%call (this p q r)
...

Green Card will expand the call to this:

%fun f :: This -> This
%call (MkThis (int p) (float q, float r))
...

(In fact, int and float are also DIS macros defined in Green Card's standard DIS prelude, so the %call line is further expanded to:

The expansion shown here assumes the GHC version of the standard DIS' are used.

%fun f :: This -> This
%call (MkThis (I# ({int} p)) (F# ({float} q), F# ({float} r)))
...

The fully expanded calls describe the marshalling code in full detail; you can see why it would be inconvenient to write them out literally on each occasion!)

Notice that DIS macros are automatically bidirectional; that is, they can be used to convert Haskell values to C and vice versa. For example, we can write:

%fun f :: This -> This
%call (MkThis (int p) (float q, float r))
%code int a, b, c;
%     f( p, q, r, &a, &b, &c);
%result (this a b c)

The form of DIS macro definitions, given in Figure fig:dis-syntax , is very simple. The formal parameters can only be variables (not patterns), and the right hand side is simply another DIS. Only first-order DIS macros are permitted.

6.3 Marshalling complex structures

THe full power of DIS macros becomes apparent when mapping between a structured Haskell type a C struct. For example, to interface the Haskell ColourPoint type with the outside world:

data ColourPoint = CP Int Int Colour
data Colour = Red | Green | Blue | ... deriving ( Enum )

for which we want to map it onto the following C structure:

typedef struct CPoint {
   int x;
   int y;
   enum colour c;
} CPoint;

It requires just two DIS macros to capture the mapping between the two:

%dis colourPoint cp = 
%   declare {CPoint} cp in
%   CP (int {%cp.x}) (int {%cp.y}) (colour {%cp.c})

%dis colour c =
%   declare {enum colour} c in
%   < fromEnum / toEnum > (int x)

Using these, it is then very easy to implement the required interfaces to foreign functions that manipulate coloured points:

%fun translate :: Int -> Int -> ColourPoint -> IO ColourPoint
%call (int xrel) (int yrel) (colourPoint p)
%code p.x += xrel;
%     p.y += yrel;
%     render(&p);
%result (colourPoint {p})

Note that in this example, the return value is actually the same structure as the argument value (destructively updated.) It is for this reason that the p on the %result line is quoted as a C literal - this prevents the declare clause of the DIS macro from generating a second (overlapping) declaration of the variable in C.

6.4 Semantics of DISs

How does Green Card use these DISs to convert between Haskell values and C values? We give an informal algorithm here, although most programmers should hopefully be able to manage without knowing the details.

To convert from Haskell values to C values, guided by a DIS, Green Card does the following:

Much the same happens in the other direction, except that Green Card calls the unmarshall function in the user-defined DIS case.




DIS           Haskell type  C type         Comments
==============================================================

int x         Int           int
char c        Char          char
float f       Float         float
double d      Double        double
bool b        Bool          int             0 for False, 1 for True
addr a        Addr          (void *)        An (immovable) external address

string s      String        (char *)        Persistence not required
                                            in either direction.

foreign x f   ForeignObj    (void *x),
                            ((void (*)())f) f is the free routine; it
                                            takes one parameter, namely
                                            x, the thing to be freed.

stable x      any           unsigned int    Makes it possible to pass a
                                            Haskell pointer to C, and
                                            perhaps get it back later,
                                            without breaking the
                                            garbage collector.

maybe dis     Maybe dis     type of dis     Converts to and from
                                            Maybe's, with 0 as 'Nothing'

maybeT ce d   Maybe dis     type of dis     Converts to and from 'Maybe's

Standard DISs


Next Previous Contents