Updated 2016-03-27 11:15:55 by bapcyk

This page is to collect information on how to use Tcl and Haskell together.

Haskell bindings to Tk

[ TclHaskell ... HTk ... others? ...]

These are packages written by Haskell users to allow them to write Tk GUIs in Haskell. They are mainly interfaces to Tcl/Tk.

Tcl bindings to Haskell

This is the bit I'm interested in. How do you write a Tcl extension in Haskell? The short answer is you can't. There is a quite nice Foreign Function Interface defined for Haskell and implemented by GHC and Hugs (a compiler and interpreter resp.), which allows calling both ways - C to Haskell, and Haskell to C. It also provides methods for registering callbacks written in Haskell etc. On the face of it, this would seem to make writing a Tcl extension in Haskell easy, but there is a snag - none of the compilers will build dynamic libraries suitable for using with load. It also looks as if none of them are going to any time in the near future. It looks as though GHC can build dlls on Windows, so if you are on that platform, you are in luck, but everyone else isn't. :-( You can build a custom tclsh/wish with Haskell support compiled in, which might be a way to go for now. Creating an interface layer should be pretty easy as well, as you could map between Tcl types and Haskell types (eg. Tcl_Obj to TclObj), and the types of callbacks/functions could be defined as well.

An example

Here is a complete example of a custom tclsh with a Haskell command. This command takes an integer and returns it. Error checking removed for simplicity:

The Haskell code (Foo.hs):
 module Foo where
 foreign export ccall foo :: Int -> IO Int
 foo       :: Int -> IO Int
 foo n     = return n

The C code (htclsh.c):
 #include "Foo_stub.h"
 #include <stdio.h>
 #include <tcl.h>
 #include "RtsAPI.h"

 /* Foo_stub.h is generated by the compiler from Foo.hs.
  * RtsAPI.h contains the startupHaskell and shutdownHaskell
  * functions.
  */

 /* This function is the initialization function for the Foo
  * module (generated by the compiler).
  */
 extern void __stginit_Foo (void);
 /* The foo command Tcl interface */
 int TH_Foo(ClientData, Tcl_Interp *, int, Tcl_Obj * CONST []);
 /* Exit handler, to shutdown haskell */
 void TH_Quit(ClientData);

 int TH_AppInit(Tcl_Interp *interp)
 {
    /* This should all look familiar */
    if (Tcl_Init(interp) == TCL_ERROR)
        return TCL_ERROR;
    Tcl_SetVar(interp, "tcl_rcFileName", "~/.htclshrc", TCL_GLOBAL_ONLY);

    Tcl_CreateObjCommand(interp, "foo", TH_Foo, NULL, NULL);

    Tcl_CreateExitHandler(TH_Quit, NULL);

    return TCL_OK;
 }

 int main(int argc, char *argv[])
 {
    /* We need to initialize the Haskell runtime code */
    startupHaskell(argc, argv, __stginit_Foo);
    /* Now, into Tcl_Main as usual (the haskell call doesn't block) */
    Tcl_Main(argc, argv, TH_AppInit);
    return 0;
 }

 void TH_Quit(ClientData cdata)
 {
    shutdownHaskell();
 }

 int TH_Foo(ClientData cData, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[])
 {
    int val;
    Tcl_Obj *resultPtr;

    if (Tcl_GetIntFromObj(interp, objv[1], &val) != TCL_OK)
        return TCL_ERROR;

    resultPtr = Tcl_GetObjResult(interp);

    /* The haskell function appears as a normal C function */
    Tcl_SetIntObj(resultPtr, foo(val));

    return TCL_OK;
 }

To compile the executable (with ghc) use:

ghc -o htclsh Foo.hs htclsh.c -ltcl8.4 -fglasgow-exts

(note that you use ghc to compile the c code too).

Hope this is useful to some people - NEM 24/11/2002

[bapcyk]: Very simple way to communicate from Haskell to Tcl is usage of pipes. Haskell process starts wish interpreter, runs Tcl code on it and read/write via pipe to wish in any custom protocol. Example here: https://github.com/bapcyk/hfit , description here: http://bapcykware.blogspot.com/2016/02/hfit-fitting-algorithms-in-haskell.html

Starting wish server with running of UI code (ui.tcl):
 main = do
   mainTcl <- readFile "ui.tcl"
   (Just si, Just so, Just sx, ph) <- createProcess (proc wish []) {
     std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe, close_fds=False}
   hSetBuffering si NoBuffering
   hSetBuffering so NoBuffering
   hPutStrLn si mainTcl
   forkIO $ ioLoop si so
   waitForProcess ph
   putStrLn "bye."
   where
     ioLoop si so = forever $ do
         resp <- (hGetLine so) `catchIOError` (\_ -> return "eof")
         case resp of
           "eof" -> exitSuccess
           _|resp `startsWith` "calc " ->
             putStrLn ("-> " ++ resp) >>
             let res = calc resp in
             putStrLn ("<- " ++ res) >> hPutStrLn si res
           _ -> putStrLn resp >> return ()

String "calc ..." is example of string-based response processing (command with args). Haskell side processes this, parses args, calculates (with "calc' func) then calls Tcl side via usual Tcl command which is sent via pipe (example, "myFunc 1 2 3 4"). Tcl answers with simple "puts".