Pure and impure Haskell bindings to C functions
Hi, I am writing a binding to GraphViz/AGraph. I've used C2HS (excellent tool) and everything has been working in a satisfying way so far. To test the usability of the binding, I translated a c program among the examples, to Haskell. And then, after some modifications, I began to hesitate : which Haskell functions should be made pure ? An example : static void nodeInduce(Agraph_t *g) { Agnode_t *n, *rootn; Agedge_t *e; for (n = agfstnode(g); n; n = agnxtnode(n)) { rootn = agsubnode(agroot(g),n,FALSE); for (e = agfstout(rootn); e; e = agnxtout(e)) { if (agsubnode(g,aghead(e),FALSE)) agsubedge(g,e,TRUE); else { if (getscc(aghead(e)) && getscc(agtail(e))) agedge(getrep(getscc(agtail(e))),getrep(getscc(aghead(e))), NIL(char*),TRUE); } } } } In C, many accessors functions are "inlined", but in my first attempt, every functions having a result in the IO Monad, the code is a little boring. So I decided to transform simple functions, those returning Int and Bool, to pure functions. For exemple 'agIsDirected' (is a graph directed) or 'agNNodes' (number of nodes in a graph) seem to be pure function. But what about a function returning a String, such as an object name ('agNameOf'). To make it pure, I will probably have to use unsafePerformIO, which I am reluctant to do. But if a node's seq number is a pure function (agSeq), why a node's name would not be pure to ? So where do I have to put the limit, between actions (create graph, impure) and state queries (number of nodes), or between functions returning Bool/Int and others ? Thank you for your thoughts on that, Alain
Alain Cremieux wrote:
Hi, I am writing a binding to GraphViz/AGraph. I've used C2HS (excellent tool) and everything has been working in a satisfying way so far. To test the usability of the binding, I translated a c program among the examples, to Haskell. And then, after some modifications, I began to hesitate : which Haskell functions should be made pure ? [...] So where do I have to put the limit, between actions (create graph, impure) and state queries (number of nodes), or between functions returning Bool/Int and others ?
State queries are not pure, as they depend on state, and state could change over time. Remember that, due to lazy evaluation, a pure functions can always be evaluated later than you expect. For every function in question, you need to ask yourself: Does the function always return the same value if you pass the same parameter? If no, it has to be in the IO monad. For example, if you have an action to change the object name, you can't have a pure function that gets the object name. If you have an action to free the graph (as opposed to using ForeignPtrs and the Garbage Collector to clean up after yourself), then you can't have any pure functions that operate on the graph; otherwise, the graph could already have been freed when the pure function is finally evaluated. I hope that helps a bit, Cheers, Wolfgang
Alain Cremieux wrote: [...] which Haskell functions should be made pure ? [...]
On Saturday 10 May 2003 8:14 am, Wolfgang Thaller wrote:
For every function in question, you need to ask yourself: Does the function always return the same value if you pass the same parameter? If no, it has to be in the IO monad.
This is an overly strong test. What matters is whether the result is observably different with different calls. This will depend on what other operations you include in the API since observational equivalence is all about what you can do with an object not how you built it. For example, if you only provide operations to create objects then you don't need to use the IO monad. But, if you provide an equality operation, if object creation is visible from outside the processs (e.g., if it displays a window on the screen), etc. then you need to use the IO monad. In this particular context (a graph API), side effects on the graph are likely to force you to use the IO monad. If you can arrange that graphs can't be modified after being built, and use ForeignPtrs you could probably drop most uses of IO. For example, you could pack all graph creation into a single operation buildGraph :: [(Node,Node)] -> Graph buildGraph _ = unsafePerformIO (...) and then operations which read/ compute properties of the graph need not be in the IO monad. See these mesages for a previous discussion: http://www.haskell.org/pipermail/glasgow-haskell-users/2002-July/003681.html http://www.haskell.org/pipermail/glasgow-haskell-users/2002-July/003683.html http://www.haskell.org/pipermail/glasgow-haskell-users/2002-July/003689.html -- Alastair Reid
participants (3)
-
Alain Cremieux -
Alastair Reid -
Wolfgang Thaller