On 01-Dec-2000, Marcin 'Qrczak' Kowalczyk <qrczak@knm.org.pl> wrote:
There is a fundamental difference between functions and variables on one side, and macros, enum constants and struct fields on the other.
Using objects of the first kind does not require cooperation with a C compiler. ... Current foreign declarations support only the first kind. They are implementable in a native code generator. They are even independent of the language, provided that names are not mangled, types are compatible and the calling convention is supported.
Approaching the second kind in the core language is tricky. First of all, it must be somehow defined which C headers to include to find a definition, and what preprocessor symbols to #define to make it available.
Mercury supports this using `foreign_decls' and `foreign_code' pragmas. Originally we just supported C, and so these were named `c_header_code' and `c_code' respectively, but we're currently in the process of generalizing this to support arbitrary languages. The `foreign_decls' pragma lets you include any foreign language declarations that the foreign language code might need, e.g. :- pragma foreign_decls(c, " #define _POSIX_SOURCE 1 #include "conf.h" #ifdef HAVE_UNISTD_H #include <unistd.h> #else #include <stdio.h> #endif extern int my_global; "). For the `foreign_code' pragma, there are two forms. The first form just specifies a block of foreign code that will be compiled with the foreign language compiler and linked into the program. E.g. :- pragma foreign_code(c, " int my_global; "). This form is no strictly necessary, since you could always write such code in the foreign language itself, rather than in Mercury code inside `pragma foreign_code', but it is often very convenient. The other form is for when you are implementing a Mercury procedure using foreign code. You give the declaration of the Mercury procedure, e.g. :- func sin(float) = float. :- mode sin(in) = out is det. % this line is optional and then you give a `pragma foreign_code' declaration to implement it: :- pragma foreign_code( % specify which language c, % specify which function this is for, % and which mode of the function, and % also introduce variable names for the arguments sin(X::in) = (Y::out), % a list of flags; these can give the Mercury % compiler additional information to help it % generate more efficient code [will_not_call_mercury, thread_safe], % The foreign language code to implement the procedure % This uses the variable names introduced earlier "Y = sin(X);" ). For the Mercury -> MS IL back-end (and soon for the Mercury -> Java back-end), the Mercury compiler extracts the foreign_decl and foreign_code stuff, wrapping code in the second form of foreign_code up inside appropriate function definitions, and puts them all in a separate file that it then invokes the foreign language compiler on. This does require a bit of language-specific knowledge in the Mercury compiler, because it needs to know the foreign language syntax for function definitions, so that it can wrap up the code from a `foreign_code' declaration in an externally visible function definition. Currently we only support C, but adding support for other languages would not be hard. For example, for Haskell I suppose if you wrote :- module example. ... :- pragma foreign_decl(haskell, " import Prelude -- this is not a good example ;-) "). :- func haskell_sin(float) = float. :- pragma foreign_code(haskell, [], haskell_sin(x::in) = (y::out), "y = sin x" ). we would generate a Haskell file containing something like this: import Prelude -- this is not a good example ;-) example__haskell_sin_1_f_0 x = y where y = sin x The name `example__haskell_sin_1_f_0' is a mangled name corresponding to the name of the Mercury procedure that this code is for; the "1_f_0" bit means that this is mode number zero of a function with one argument.
Don't forget about specifying which C compiler to use and with which options, e.g. which include paths.
Those are not part of the language. Those are specified by options which you pass to the Mercury compiler, or more likely put in your makefile. E.g. to compile the C example you could use mmc --cc gcc --cflags "-I/foo/bar" example.m (this is currently supported) and to compile the Haskell one you could use mmc --haskell-compiler ghc --haskell-flags \ "-fallow-undecidable-instances" example.m Or you could put both examples in the same module and pass both sets of flags to mmc.
There are two separate issues:
1. Is it possible to have the second kind from the level of the Haskell compiler at all (done in an sufficiently elegant way to think about it as a candidate proposal for the official FFI)?
I think so. I certainly consider the mechanism that Mercury uses to be elegant enough to be the official Mercury FFI, and I hope our standards are not so much lower than yours ;-)
2. If so, which features are worth supporting?
See above. Cheers, Fergus. -- Fergus Henderson <fjh@cs.mu.oz.au> | "I have always known that the pursuit | of excellence is a lethal habit" WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.