<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="style.css" type="text/css"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" href="style.css" type="text/css" />
<script type="text/javascript" src="script.js"></script>
<title>Extending PureData with Haskell</title>
</head>
<body onload="initialize();">

<!-- ####################################################################### -->

<div id="menu">
<h2>Contents</h2>
<ul>
<li><a href="javascript:show('introduction');">Introduction</a></li>
<li><a href="javascript:show('externalsinc');">Externals In C</a></li>
<li><a href="javascript:show('buildingbridges');">Building Bridges</a></li>
<li><a href="javascript:show('entrypoint');">Entry Point</a></li>
<li><a href="javascript:show('setupflow');">Setup Flow</a></li>
<li><a href="javascript:show('createflow');">Create Flow</a></li>
<li><a href="javascript:show('inletflow');">Inlet Flow</a></li>
<li><a href="javascript:show('exampleswap');">hsext-0.1 Swap</a></li>
<li><a href="javascript:show('hsext01issues');">hsext-0.1 Issues</a></li>
<li><a href="javascript:show('hsext10plans');">hsext-1.0 Plans</a></li>
<li><a href="javascript:show('hsext10swap');">hsext-1.0 Swap</a></li>
<li><a href="javascript:show('hsext10issues');">hsext-1.0 Issues</a></li>
<li><a href="javascript:show('pipedreams');">Pipe Dreams</a></li>
<li><a href="javascript:show('reallyinpd');">Really In Pd</a></li>
<li><a href="javascript:show('hyperspace');">Hyperspace</a></li>
<li><a href="javascript:show('theend');">The End</a></li>
</ul>
</div>

<!-- ####################################################################### -->

<div id="side"><h2>Useful Links</h2>
<p>using TinyURL.com: <code>http://tinyurl.com/######</code></p>
<p><a href="http://puredata.info" class="tinyurl">2w7grx</a> --
PureData Portal<br />
<a href="http://haskell.org" class="tinyurl">2qmm6c</a> --
Haskell Portal<br />
<a href="http://iem.at/pd/externals-HOWTO/HOWTO-externals-en.html" class="tinyurl">37w28z</a> --
IEM HOWTO<br />
<a href="https://devel.goto10.org/listing.php?repname=maximus&amp;path=%2Fhsext%2F&amp;rev=0&amp;sc=0" class="tinyurl">yuvjlw</a> --
HsExt SVN<br />
<a href="http://www.archive.org/details/ClaudiusMaximus_-_Live_At_Shunt_2007-06-22" class="tinyurl">35r5p5</a> --
Live Video<br />
<a href="http://claudiusmaximus.goto10.org" class="tinyurl">32c6gv</a> --
My Website<br />
<a href="http://goto10.org" class="tinyurl">24f377</a> --
GOTO10
</p>
</div>

<!-- ####################################################################### -->

<div id="main"><h1>Extending PureData with Haskell</h1>

<div class="page" id="title">
<p style="text-align: center;"><br /><br /><img src="hsext.png" /></p>
</div>

<!-- ####################################################################### -->

<div class="page" id="introduction">

<h2>Introduction</h2>
<dl>

<dt>Pd (aka PureData)</dt>

<dd><p>A real-time graphical programming environment for audio, video, and
graphical processing.  Processing objects are connected together with patch
cords, much like a modular synthesizer from days of yore.</p><p><em>Externals</em>
(providing additional processing objects) can be written in several languages:</p>
<ul>
<li>C (Pd's native API)</li>
<li>C++</li>
<li>Python</li>
<li>Ruby</li>
<li>Scheme</li>
<li>and probably others...</li>
</ul></dd>

<dt>Haskell</dt>

<dd><p>A general purpose, purely functional programming language featuring
static typing, higher order functions, polymorphism, type classes, and
monadic effects.</p></dd>

<dt>HsExt</dt>

<dd><p>A Pd &#x021D4; Haskell bridge, and the topic of this talk.</p></dd>

</dl>
</div>

<!-- ####################################################################### -->

<div class="page" id="externalsinc">

<h2>Writing Externals In C</h2>

<h3>Pd's Object System</h3>

<ul>
<li>Class-based object-orientedness.</li>
<li>Classes are registered at runtime, with:<ul>
  <li>name</li>
  <li>constructor</li>
  <li>destructor</li>
  <li>and some other things...</li></ul></li>
<li>An attempt to create an object of an unregistered class triggers a search for files that are likely to contain
code that registers such a class.</li></ul>

<h3>Resources</h3>

<dl>

<dt>m_pd.h</dt>

<dd><p>The public header file for external authors to use.  642 lines of
sparsely commented definitions.  Very scary.</p></dd>

<dt>IEM External HOWTO</dt>

<dd><p>A document that aims to explain how to write externals in C.
Straightforward.</p></dd>

</dl>
</div>

<!-- ####################################################################### -->

<div class="page" id="buildingbridges">

<h2>Building Bridges</h2>

<h3>Problems</h3>

<ul>
<li>Haskell's RTS<ul>
  <li>must be started before any Haskell code can be used.</li></ul></li>
<li>Pd's awkward C API<ul>
  <li>typecasting C function pointers</li>
  <li>global mutable state (the constructor problem)</li></ul></li>
<li>Haskell's purity and strong typing<ul>
  <li>no typecasting</li>
  <li>no global mutable state</li></ul></li>
</ul>

<h3>Solutions</h3>

<ul>
<li>Single Entry Point<ul>
  <li>Pd guarantees to call only our setup function at most once</li>
  <li>we can start the Haskell RTS</li>
  <li>we can safely initialize a Data.IORef with our state</li>
  <li>we can provide this guarantee to user code too</li></ul></li>
<li><em>GIMME</em> Pd API<ul>
  <li>Pd has a rudimentary type checker</li>
  <li>we can tell Pd not to check our types: GIMME the raw data</li></ul></li>
</ul>

</div>

<!-- ####################################################################### -->

<div class="page" id="entrypoint">

<h2>Entry Point</h2>

<p>A Pd external is a shared library. Invoking <code>pd -path /path/to/hsext -lib hsext</code> will start Pd and load
the file <code>/path/to/hsext/hsext.pd_linux</code>, calling the function <code>hsext_setup()</code>
found within it.</p>

<p>Haskell's RTS must be started before any Haskell code can be used.  This is not a problem for standalone executables
as the startup code does this, but we need a shared library and thus we need a small amount of C code to do it
ourselves.</p>

<h3>bootstrap.c</h3>

<pre>#include "HsExt_stub.h"
extern void __stginit_HsExt(void);

<span class="comment">/* our "real" entry point, a foreign export from Haskell */</span>
extern void hsext_init(void);

<span class="comment">/* this function is called at most once (by Pd) */</span>
extern void hsext_setup(void) {

    <span class="comment">/* start Haskell RTS */</span>
    hs_init(0, 0);
    hs_add_root(__stginit_HsExt);
    
    <span class="comment">/* call our Haskell entry point */</span>
    hsext_init();
}</pre>

</div>

<!-- ####################################################################### -->

<div class="page" id="setupflow">

<h2>Setup Control Flow</h2>

<div>
<img class="left" src="setup.png" />

<div>
<p>libhsext is some utility C code to make things less painful.</p>

<h3>libhsext.h (excerpt)</h3>

<pre><span class="comment">/* function pointer types */</span>
typedef void (t_newf  )(t_symbol *, int, t_atom *);
typedef void (t_freef )(t_libhsext *);  <span class="comment">/* Pd object instance */</span>
typedef void (t_inletf)(int, int, t_symbol *, int, t_atom *);

<span class="comment">/* called by HsExt.hsext_init */</span>
extern void libhsext_setup(
  t_newf newf, t_freef freef, t_inletf inletf
);</pre>

<p><code>libhsext_setup()</code> stores Haskell function pointers for calling later when Pd requires it.</p>

<h3 style="clear: both;">HsExt.hs (excerpt)</h3>

<pre><span class="comment">-- constructor</span>
type NewMethod = Ptr Symbol -&gt; CInt -&gt; Ptr Atom -&gt; IO (Ptr Object)
foreign import ccall "wrapper" wrapNew :: NewMethod -&gt; IO (FunPtr NewMethod)
<span class="comment">-- destructor</span>
type FreeMethod = Ptr Object -&gt; IO ()
foreign import ccall "wrapper" wrapFree :: FreeMethod -&gt; IO (FunPtr FreeMethod)
<span class="comment">-- data flow</span>
type InletMethod = InstanceID -&gt; InletNumber -&gt; Ptr Symbol -&gt; CInt -&gt; Ptr Atom -&gt; IO ()
foreign import ccall "wrapper" wrapInlet :: InletMethod -&gt; IO (FunPtr InletMethod)

<span class="comment">-- entry point</span>
hsext_init = do
  sref &lt;- newIORef s0                               <span class="comment">-- initial state</span>
  newMethod &lt;- wrapNew (new sref)                   <span class="comment">-- embed state in functions</span>
  freeMethod &lt;- wrapFree (free sref)
  inletMethod &lt;- wrapInlet (inlet sref)
  libhsext_setup newMethod freeMethod inletMethod   <span class="comment">-- pass to the C code</span></pre>

</div>

</div>

</div>

<!-- ####################################################################### -->

<div class="page" id="createflow">

<h2>Create Control Flow</h2>

<div>
<img class="left" src="create.png" />

<div>
<h3>Plugins</h3>

<p>When an <code>hsext</code> object is instantiated in Pd, the first creation argument is used to form the name of a
Haskell source code file to compile and load.</p>

<p>hsext's PureData API defines a <em>Creator</em> resource type, which is passed the remaining creation arguments.</p>

<p>The creator may return an <em>Instance</em>, which contains some properties of the Pd object.</p>

<h3 style="clear: both;">PureData API (excerpt)</h3>

<pre><span class="comment">-- send a message from an outlet</span>
type Outlet = CInt -&gt; Ptr Symbol -&gt; [Atom] -&gt; IO ()

type Method =  Outlet        <span class="comment">-- allows the instance to output via its outlets</span>
            -&gt; CInt          <span class="comment">-- number of the inlet that received the message</span>
            -&gt; Ptr Symbol    <span class="comment">-- incoming message selector</span>
            -&gt; [Atom]        <span class="comment">-- incoming message arguments</span>
            -&gt; IO ()

data Instance = Instance {
  iInlets  :: CInt,          <span class="comment">-- number of inlets</span>
  iOutlets :: CInt,          <span class="comment">-- number of outlets</span>
  iMethod  :: Method }       <span class="comment">-- function to call when data arrives at an inlet</span>
  
type Creator = [Atom] -&gt; IO (Maybe Instance)</pre>

</div>

</div>

</div>

<!-- ####################################################################### -->

<div class="page" id="inletflow">

<h2>Inlet Control Flow</h2>

<div><img class="left" src="inlet.png" />

<div>

<p>When the Pd object receives a message at an inlet, Pd calls a method in libhsext.</p>
<p>libhsext looks into the inlet structure to find the ID of the owning Instance, and passes this information
into the Haskell core of hsext.</p>
<p>The core calls the one method defined in the Instance record, passing in an outlet method.</p>
<p>If the user code needs to output from its outlets, it calls that outlet method.</p>
<p>The outlet function calls a libhsext function which sends the message back to Pd.</p>

</div>

</div>

</div>

<!-- ####################################################################### -->

<div class="page" id="exampleswap">

<h2>Example: Swap</h2>

<p>Pd's internal <code>swap</code> object can only swap <code>float</code> messages.</p>

<p>We can write a more generic version that swaps arbitrary messages.</p>

<div>
<h3>Swap.hs</h3>
<img class="left" src="swap.png" />

<pre>module Swap (creator) where

import Data.IORef
import PureData

creator :: Creator
creator args = do
  bang &lt;- gensym "bang"
  state &lt;- newIORef (bang, [])
  return (Just Instance{
    iInlets = 2,
    iOutlets = 2,
    iMethod = inlet state})

inlet state outlet 0 s m = do
  (rs,rm) &lt;- readIORef state
  outlet 1 s m
  outlet 0 rs rm

inlet state outlet 1 s m = do
  writeIORef state (s,m)</pre>

</div>

</div>

<!-- ####################################################################### -->

<div class="page" id="hsext01issues">

<h2>Issue With hsext-0.1</h2>

<ul>
<li><em>Creator</em> resource<ul>
  <li>no state shared between instances</li>
  <li>only one class per source file</li>
  <li>solution: <em>Setup</em> resource</li></ul></li>
<li>loader functionality<ul>
  <li><code>[hsext RealClassName]</code> is ugly</li>
  <li>solution: Pd has <code>sys_register_loader</code></li></ul></li>
<li>one method for all inlets<ul>
  <li>no pattern matching on message selectors (Ptr Symbol)</li>
  <li>solution: registering methods for selectors (like Pd's C API)</li></ul></li>
<li>non-extensible<ul>
  <li>what if we want to pass more than an outlet function?</li>
  <li>solution: pass a record to the Setup resource</li></ul></li>
<li>incomplete mapping of Pd's API<ul>
  <li>clock callbacks</li>
  <li>receiver names</li>
  <li>table access</li>
  <li>digital signal processing</li></ul></li>
</ul>

</div>

<!-- ####################################################################### -->

<div class="page" id="hsext10plans">

<h2>Plans For hsext-1.0</h2>

<p>API record created by single entry point (with any required state references embedded) and passed to the Setup resource.</p>

<pre>module PureData where
<span class="comment">-- the master record of Pd API functions</span>
data PureData  =   Pd {
  <span class="comment">-- common symbols</span>
  bang            ::  Symbol,
  float           ::  Symbol,
  symbol          ::  Symbol,
  pointer         ::  Symbol,
  list            ::  Symbol,
  <span class="comment">-- symbol manipulation</span>
  gensym          ::  String -&gt; IO Symbol,
  peeksym         ::  Symbol -&gt; IO String,
  <span class="comment">-- class management</span>
  newClass        ::  Symbol -&gt; ((Symbol, [Atom]) -&gt; IO Object)
                             -&gt; (Object -&gt; IO ()) -&gt; IO Class,
  <span class="comment">-- object management</span>
  newObject       ::  Class -&gt; IO Object,
  freeObject      ::  Object -&gt; IO (),
  <span class="comment">-- inlet management</span>
  newInlet        ::  Object -&gt; IO Inlet,
  freeInlet       ::  Inlet -&gt; IO (),
  <span class="comment">-- add methods to inlets</span>
  addBang         ::                         IO ()   -&gt; Inlet -&gt; IO Inlet,
  addFloat        ::  (Float             -&gt;  IO ())  -&gt; Inlet -&gt; IO Inlet,
  addSymbol       ::  (Symbol            -&gt;  IO ())  -&gt; Inlet -&gt; IO Inlet,
  addPointer      ::  (Pointer           -&gt;  IO ())  -&gt; Inlet -&gt; IO Inlet,
  addList         ::  ([Atom]            -&gt;  IO ())  -&gt; Inlet -&gt; IO Inlet,
  addMethod       ::  Symbol -&gt; ([Atom]  -&gt;  IO ())  -&gt; Inlet -&gt; IO Inlet,
  addAnything     ::  ((Symbol, [Atom])  -&gt;  IO ())  -&gt; Inlet -&gt; IO Inlet,
  <span class="comment">-- outlet management</span>
  newOutlet       ::  Object -&gt; IO Outlet,
  freeOutlet      ::  Outlet -&gt; IO (),
  <span class="comment">-- send messages to outlets</span>
  outBang         ::  Outlet                      -&gt; IO (),
  outFloat        ::  Outlet -&gt; Float             -&gt; IO (),
  outSymbol       ::  Outlet -&gt; Symbol            -&gt; IO (),
  outPointer      ::  Outlet -&gt; Pointer           -&gt; IO (),
  outList         ::  Outlet -&gt; [Atom]            -&gt; IO (),
  outAnything     ::  Outlet -&gt; (Symbol, [Atom])  -&gt; IO ()
} <span class="comment">-- data PureData</span></pre>

</div>

<!-- ####################################################################### -->

<div class="page" id="hsext10swap">

<h2>Reimplementing Swap In hsext-1.0</h2>

<pre>module Swap (setup) where
import PureData
import Data.IORef
import qualified Data.Map as M              <span class="comment">-- need to map objects from C to Haskell</span>

setup pd = do                               <span class="comment">-- Setup resource</span>
  nameS &lt;- (gensym pd) "Swap"
  classR &lt;- newIORef Nothing                <span class="comment">-- class state to embed in de/constructors</span>
  classP &lt;- (newClass pd) (nameS) (new classR) (free classR)
  writeIORef classR (Just (pd, classP, M.empty))

new classR _ = do                           <span class="comment">-- Constructor</span>
  Just (pd, classP, m) &lt;- readIORef classR
  objectR &lt;- newIORef Nothing               <span class="comment">-- instance state reference</span>
  stateR &lt;- newIORef (bang pd, [])          <span class="comment">-- instance mutable state</span>
  objectP &lt;- (newObject pd) classP          <span class="comment">-- create object, inlets, methods, outlets</span>
  inlet0 &lt;- (newInlet pd) objectP &gt;&gt;= (addAnything pd) (swap0 objectR)
  inlet1 &lt;- (newInlet pd) objectP &gt;&gt;= (addAnything pd) (swap1 objectR)
  outlet0 &lt;- (newOutlet pd) objectP
  outlet1 &lt;- (newOutlet pd) objectP
  writeIORef objectR (Just (pd, stateR, inlet0, inlet1, outlet0, outlet1))
  writeIORef classR (Just (pd, classP, M.insert objectP objectR m))
  return objectP

free classR objectP = do                    <span class="comment">-- Destructor</span>
  Just (pd, classP, m) &lt;- readIORef classR  <span class="comment">-- Pd destructor is per-class not per-object</span>
  objectR &lt;- M.lookup objectP m             <span class="comment">-- so we need a Map to find stuff to free</span>
  writeIORef classR (Just (pd, classP, M.delete objectP m))
  Just (_, _, inlet0, inlet1, outlet0, outlet1) &lt;- readIORef objectR
  (freeOutlet pd) outlet1                   <span class="comment">-- Clean up</span>
  (freeOutlet pd) outlet0
  (freeInlet  pd) inlet1
  (freeInlet  pd) inlet0
  (freeObject pd) objectP

swap0 objectR msg = do                      <span class="comment">-- Left inlet method</span>
  Just (pd, stateR, _, _, outlet0, outlet1) &lt;- readIORef objectR
  state &lt;- readIORef stateR
  (outAnything pd) outlet1 msg
  (outAnything pd) outlet0 state

swap1 objectR msg = do                      <span class="comment">-- Right inlet method</span>
  Just (_, stateR, _, _, _, _) &lt;- readIORef objectR
  writeIORef stateR msg</pre>

</div>

<!-- ####################################################################### -->

<div class="page" id="hsext10issues">

<h2>Issue With hsext-1.0</h2>

<h3>Benefits</h3>
<ul>
<li>powerful</li>
<li>flexible</li>
<li>extensible</li>
<li>"idiomatically Pd"</li>
</ul>

<h3>Problems</h3>
<ul>
<li>verbose</li>
<li>tedious</li>
</ul>

<h3>Solutions</h3>
<ul>
<li>combinators for defining classes with similar properties<ul>
  <li>setup = floatBinOpClass (+)</li>
  <li>setup = stringUnOpClass reverse</li>
  <li>etc</li></ul></li>
<li>some hardcore type system voodoo could eliminate the need for a Map in every class?</li>
</ul>

</div>

<!-- ####################################################################### -->

<div class="page" id="pipedreams">

<h2>Haskell Data Through Pd Patch-Cords</h2>

<h3><em>StablePtr</em>s and Pd pointer atoms</h3>
<ul>
<li>space leak -- Pd objects might store it indefinitely, no way to know</li>
<li>crashes -- Pd objects expect pointer atoms to point to Pd structures</li>
</ul>
<p>Unacceptable.</p>

<h3>Internal stack and special symbol</h3>
<ul>
<li>depth first message passing =&gt; stack</li>
<li>send an unusual Pd symbol signifying "get the value from our stack"</li>
<li>storing the symbol in Pd and sending later =&gt; undefinedness<br />(but no crash or space leak)</li>
<li>strong static typing =&gt; <em>Data.Dynamic</em> for stack values<br />(which loses polymorphism)</li>
</ul>
<p>Acceptable compromise.</p>

<h3>Dynamic Method Dispatch By Type</h3>

<pre>data Dispatcher = Dispatcher (IntMap Dynamic) (Maybe (Dynamic -&gt; IO()))

dispatch :: Dispatcher -&gt; Dynamic -&gt; IO ()
dispatch (Dispatcher methods fallback) dyn = do
  key &lt;- typeRepKey (dynTypeRep dyn)
  case (IntMap.lookup key methods) of     <span class="comment">-- find a method for our type</span>
    Just method -&gt; do
      case (dynApply method dyn) of       <span class="comment">-- apply it</span>
        Just r   -&gt; case (fromDynamic r) of
          Just r'  -&gt; r'                  <span class="comment">-- run the action</span>
    Nothing -&gt; case fallback of
      Just f   -&gt; f dyn                   <span class="comment">-- unspecific fallback method</span></pre>


</div>

<!-- ####################################################################### -->

<div class="page" id="reallyinpd">

<h2>Haskell Really In Pd</h2>

<h3>Haskell Code Written Inline In Pd Object Boxes</h3>

<p>...would be awesome.  Especially once we can send complex Haskell structures through Pd patch cords, it
would get tedious to have to write verbose classes to deconstruct them.</p>

<h3>Haskell Types vs Pd Inlet Count vs Pd Text Handling</h3>

<p>How many inlets should <code>(+) :: Float -&gt; Float -&gt; Float</code> have?<br />
How many inlets should <code>(+) :: Float -&gt; (Float -&gt; Float)</code> have?</p>

<p>Keep in mind that in Haskell these two types are identical.</p>

<p>Pd is not very good at text, some characters are special (or forbidden):<br /><code>\ { } , ; $</code></p>

<p>Conclusion: we need to preprocess to solve these issues.</p>

<h3>Automatic Marshalling</h3>

<p>Say we want: <code>flip (:) :: [Float] -&gt; Float -&gt; [Float]</code></p>

<ul>
<li>for a candidate type of [Float] we need to check the message:<ul>
  <li>selector is list, float (1-element list) or bang (empty list)</li>
  <li>all atoms in the list are floats</li></ul></li>
<li>if these conditions are met, marshall the Pd message to a Haskell list</li>
<li>if Haskell types are not compatible to Pd types, use Dynamic values</li>
</ul>

</div>

<!-- ####################################################################### -->

<div class="page" id="hyperspace">

<h2>Hyperspace Exploration</h2>

<p><img src="hyperspace.png" /></p>

<p>Screenshot from a live performance using hsext to rotate and project a 4D cube to 3D (rendered with Gem, an OpenGL
external for Pd).</p>

</div>

<!-- ####################################################################### -->

<div class="page" id="theend">

<h2>The End</h2>

<h3>Useful Links</h3>
<dl>
<dt>PureData Community Portal</dt>
<dd><a href="http://puredata.info">http://puredata.info</a></dd>
<dt>Haskell Community Portal</dt>
<dd><a href="http://haskell.org">http://haskell.org</a></dd>
<dt>IEM PureData External HOWTO</dt>
<dd><a href="http://iem.at/pd/externals-HOWTO/HOWTO-externals-en.html">http://iem.at/pd/externals-HOWTO/HOWTO-externals-en.html</a></dd>
<dt>HsExt Subversion Repository</dt>
<dd><a href="https://devel.goto10.org/listing.php?repname=maximus&amp;path=%2Fhsext%2F&amp;rev=0&amp;sc=0">https://devel.goto10.org/listing.php?<br />repname=maximus&amp;path=%2Fhsext%2F&amp;rev=0&amp;sc=0</a><br />
svn co https://devel.goto10.org/svn/maximus/hsext hsext</dd>
<dt>Live A/V Performance At Shunt</dt>
<dd><a href="http://www.archive.org/details/ClaudiusMaximus_-_Live_At_Shunt_2007-06-22">http://www.archive.org/details/ClaudiusMaximus_-_Live_At_Shunt_2007-06-22</a></dd>
<dt>My Website</dt>
<dd><a href="http://claudiusmaximus.goto10.org">http://claudiusmaximus.goto10.org</a></dd>
<dt>GOTO10</dt>
<dd><a href="http://goto10.org">http://goto10.org</a></dd>
</dl>

</div>

<!-- ####################################################################### -->

</div>

<!-- ####################################################################### -->

<div id="foot">
<div style="float: left;">AngloHaskell 2007, Cambridge, UK</div>
<div style="float: right;">Claude Heiland-Allen
&lt;<a href="mailto:claudiusmaximus@goto10.org">claudiusmaximus@goto10.org</a>&gt;</div>
</div>

<!-- ####################################################################### -->

</body>
</html>
