Brool brool (n.) : a low roar; a deep murmur or humming

Using MissingPy

 |  missingpy python coding haskell imap tutorial

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:

Installing MissingPy

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.

Basic Usage

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'"

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 ",p2)"
bar <- getattr foo "bar"
result <- pyObject_Call bar [p1,p2] []

Manipulating Python Objects

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

Dealing with Data

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
# 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

Passing Haskell Objects

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.

Add a comment