For comparison purposes, I was rewriting the silly little e-mail digest program in Haskell, using Python libraries for the IMAP interface (it’s available on Github). It’s hard to beat Python’s amazing collection of libraries. Cabal / hackage isn’t bad, but it doesn’t yet approach Python’s “batteries included” philosophy and ease of use. Anyway, I couldn’t find an IMAP library for Haskell, so decided instead to try MissingPy to interface into the Python IMAP library. A quick overview/tutorial:
I had an issue using Cabal to install MissingPy into my GHC 6.10 installation (Mac OS X). I went into the directory that had been unpacked and modified Setup.hs:
import Distribution.PackageDescription import Distribution.PackageDescription.Parse -- added this line import Distribution.Simple
A cabal configure, build, and install of the package then installed everything.
It’s easiest to play around with MissingPy in ghci. Before you do anything you’ll need to initialize the interpreter with py_initialize and import any necessary modules with pyImport. You can test by using pyRun_SimpleString:
# :m Python.Interpreter Python.Objects Python.Utils Python.Exceptions # py_initialize # pyRun_SimpleString "print 'hello'" hello
The simplest useful way to execute python code is to use pyRun_String. For example:
-- add 1 and 2 in Python -- can also use Py_file_input or Py_single_input for different handling of the context addem = pyRun_String "1+2" Py_eval_input  >>= fromPyObject :: IO Integer
There are also the callByName and pyObject_Call methods for more complicated scenarios.
callByName :: String -- ^ Object\/function name -> [PyObject] -- ^ List of non-keyword parameters -> [(String, PyObject)] -- ^ List of keyword parameters -> IO PyObject pyObject_Call :: PyObject -- ^ Object to call -> [PyObject] -- ^ List of non-keyword parameters (may be empty) -> [(String, PyObject)] -- ^ List of keyword parameters (may be empty) -> IO PyObject -- ^ Return value -- example 1: create an IMAP instance imap <- callByName "imaplib.IMAP4_SSL" [imap_hostname]  -- call "foo.bar(p1,p2)" bar <- getattr foo "bar" result <- pyObject_Call bar [p1,p2] 
The fromPyObject and toPyObject handle marshaling of objects from Haskell to Python and vice versa. There are some handy functions for doing common operations on Python objects that are especially useful from the REPL: reprOf (like Python repr), strOf (Python str), showPyObject (shows the type and repr), and dirPyObject (like the Python dir()).
# g <- pyRun_String "(1,2,'hello')" Py_eval_input  # showPyObject g ": (1, 2, 'hello')" # reprOf g "(1, 2, 'hello')" # strOf g "(1, 2, 'hello')" # dirPyObject g ["__add__","__class__","__contains__","__delattr__","__doc__","__eq__","__ge__", "__getattribute__","__getitem__","__getnewargs__","__getslice__","__gt__", "__hash__","__init__","__iter__","__le__","__len__","__lt__","__mul__","__ne__", "__new__","__reduce__","__reduce_ex__","__repr__","__rmul__","__setattr__","__str__"]
Tuples look like lists when they are returned. There is no conversion of tuples in the toPyObject call, so you’ll need to just convert a list and then convert it to a tuple with pyList_AsTuple. Passing “None,” “True,” or “False” is trickier; the only way I found to do it was create an instance of the value and use it in subsequent calls.
# a [1,2] # b <- toPyObject a # reprOf b "[1L, 2L]" # pyList_AsTuple b >>= reprOf "(1L, 2L)" -- get "True," "False," and "None" none <- pyRun_String "None" Py_eval_input  pyTrue <- pyRun_String "True" Py_eval_input  pyFalse <- pyRun_String "False" Py_eval_input  -- try it out # r <- pyRun_String "1==1" Py_eval_input  # r == pyTrue True
If you want to call into functions without having to deal with Python objects, there are a couple of calls that automatically marshal the objects back and forth for you (all suffixed with “Hs”), although they turn out to be less than useful in most situations, since a) the parameters passed in must all be the same type, and b) extracting most return objects from Python is painful.
call invoked upon returns
callByNameHs Object/function name Haskell pyObject_CallHs PyObject Haskell pyObject_Hs PyObject Python pyObject_RunHs PyObject nothing callMethodHs PyObject + method name Haskell runMethodHs PyObject + method name nothing
Python exceptions can be caught and handled; probably the easiest default way to do this is to use handlePy in conjunction with the ex2ioerr handler, which will automatically print the exception and fail.
addem = handlePy exc2ioerror $ do r <- pyRun_String "1/0" Py_eval_input  fromPyObject r :: IO Integer # addem *** Exception: user error (Python : integer division or modulo by zero)
Comments are moderated whenever I remember that I have a blog.
There are no comments on this article.