-
Notifications
You must be signed in to change notification settings - Fork 29
Motivation
Our goal during the design of the Haskell Foreign Function Interface (FFI) was simplicity: to provide a minimal, policy-free set of mechanisms to call non-Haskell code via C calling conventions from Haskell and vice versa. Then, on top of these mechanisms, developers can experiment with more convenient higher-level tools and frameworks.
At the time, my personal experiment was C->Haskell aka c2hs
(Chakravarty 2000). The premise of C->Haskell was that the Haskell community needs Haskell binding libraries that wrap foreign-language libraries into a layer of Haskell code; if possible, they should also provide a more functional interface to the library than the original foreign library. To this end, C->Haskell introduces the notion of binding modules. A binding module is a Haskell module with annotations, called binding hooks, which may refer to entities defined in the C API of the library, such as enumerations, structs, and functions. By reading and analysing the C headers of the foreign library, C->Haskell generates plain Haskell code (using the Haskell FFI) that represents C data types, calls C functions, and marshals data between its Haskell and C representations. This is definitely much more convenient than coding directly against the Haskell FFI as it generates much of the tedious boilerplate. I wrote C->Haskell to implement the original Haskell binding for the GTK+ toolkit, a fork of which is still Haskell's most mature GUI toolkit, used in applications, such as Threadscope.
Over time, it turned out that this approach, in general, and C->Haskell, in particular, has three serious shortcomings:
- it is quite complex,
- you need to actually write code for all the types and functions in the libraries you want to bind, and
- it raises the question of how to represent the foreign APIs nicely and in a functional manner in Haskell.
Regarding (1), whenever we want to automatically generate boilerplate including FFI declarations and marshalling code, a certain amount of complexity is inevitable. As many interesting C libraries are very large, there was a strong pressure on C->Haskell to minimise the amount of work required to provide a binding for a single entity of a library by being smart and automate much of the binding process. This required an analysis of the C declarations in header files, tracking cross-module type and marshalling information, introducing complex binding hooks to wrap foreign functions, and many other features.
Regarding (2), many people are busily writing, extending, and modifying foreign libraries. The effort required to write and maintain binding modules for all of them is staggering. Moreover, the binding libraries get very big and contribute to code bloat.
Regarding (3), every type, every function, every method of the foreign language needs to be represented in Haskell. This immediately leads to API design questions, which increases the effort required to write a binding, and requires documentation. If the Haskell binding mirrors the foreign library very closely (which is sometimes possible for C libraries, but would be much harder for Objective-C libraries), Haskell programmers may refer to the foreign library documentation; otherwise, we need not only write code, but also documentation.
For a set of frameworks as big as Cocoa, it is virtually impossible to provide comprehensive binding libraries. With Language.C.Inline
we avoid the need for binding libraries. Foreign libraries are used directly by embedding foreign code in Haskell. This promises to solve the above problems of C->Haskell as follows:
- A true comparison of the complexity can only happen once
Language.C.Inline
is as mature as C->Haskell, but the simple fact that no binding modules are involved as well as the implementation via meta-programming (instead of being a standalone tool) promise a simpler system. I also suspect that addressing the next two points means that we will not need the same level of automation as with C->Haskell. - With
Language.C.Inline
, the use of the foreign library goes hand in hand with application development — there is no need for an a priori binding. For a platform the size of Cocoa, that is the only feasible approach. - With
Language.C.Inline
, you use the original foreign library using the language it was designed for. All the concepts, principles, and documentation apply without change.
From (3) follows the big disadvantage of Language.C.Inline
: Haskell application developers need to understand and be able to use Objective-C. In contrast, C->Haskell bindings provide a pure Haskell interface. But I like to argue that this is only a theoretical advantage of C->Haskell's approach that isn't born out in practice for big frameworks. If Haskell programmers need to refer to the original library's documentation, and especially, if the Haskell API closely reflects the foreign API, application programmers need to be able to understand the foreign language and its concepts anyway.
Just as I wrote C->Haskell to use GTK+ from Haskell, Language.C.Inline
is to use Cocoa from Haskell.