<p> <center>
<a href="http://ro-che.info/ccc/17" title="Why static vs dynamic typing battles are rarely interesting."><img src="/img/typing.png" alt="Typing Comic from Cartesian Closed Comics" /></a> </center> </p> <p>All sarcasm aside, the above diagram has a kernel of truth. The important thing to note is that the intersection between "Proponents of dynamic typing" and "People familiar with type theory" is very small.</p> <p>In an effort to increase the size of that intersection, I decided to familiarize myself with a little more type theory. I developed an implementation of Hindley-Milner which operates on a simple Lisp-like λ-calculus with let polymorphism. Everything you need for Hindley-Milner Algorithm W.</p> <h3 id="background">Background</h3> <p>Hindley-Milner is a type system that is used by ML and Haskell. Algorithm W is a fast algorithm for inferencing Hindley-Milner which is syntax-directed (meaning it is based on the type of expression) and recursively defined. In this way, it is similar to <a href="Lisp's">http://www.paulgraham.com/rootsoflisp.html">Lisp's <code>eval</code></a>.</p> <h3 id="implementation">Implementation</h3> <p>I based my implementation on a paper called <a href="Algorithm">http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.65.7733klzzwxh:0004rep=rep1klzzwxh:0005type=pdf">Algorithm W Step by Step</em></a> which implemented it in Haskell. My implementation started very similar to that implementation and diverged as I refactored it into a more Clojure-esque style.</p> <p>This implementation uses no in-place mutation, instead using a "substitution style" which is slightly harder to read. It is, however, easier to write and prove correct.</p> <p>In addition to the type inferencer, I wrote an interpreter for the same language, just because. My original intent was to expose (to myself) the similarities between syntax-driven type inference and <code>eval</code>. There might be some, but the full clarity I desire is yet many refactorings away. Note that the interpreter and type inferencer are completely independent, except that both apply to the same set of expressions.</p> <p>I added a couple of minor luxuries to the language having to do with currying. Writing fully parentheisized function applications for curried functions is a pain, as is writing a hand-curried function with multiple arguments. I added two syntax transformations which transform them into a more Lispy style. For example:</p> <pre><code>(fn [a b c d e f] 1) => (fn a (fn b (fn c (fn d (fn e (fn f 1))))))</code></pre> <p>and</p> <pre><code>(+ 1 2) => ((+ 1) 2)</code></pre> <p>I'm pretty sure the syntactic transformation is completely safe. All of my tests still type check.</p> <p>The final luxury is that it is a lazily-evaluated language. That's not strictly necessary, but it is strictly cool. It builds up thunks (Clojure delays and promises) and a trampoline is used to get the values out. This lets me define <code>if</code> as a function. The only special forms are <code>let</code> and <code>fn</code>.</p> <h3 id="where-to-find-it">Where to find it</h3> <p>You can find the code in the <a href="ericnormand/hindley-milner">https://github.com/ericnormand/hindley-milner">ericnormand/hindley-milner Github repo. I don't promise that it has no bugs. But it does have a small and growing test suite. Pull requests are welcome and I will continue to expand and refactor it.</p> <h3 id="what-i-learned">What I learned</h3> <p>Type unification is why the error messages of most type inferencers are so bad. Unification by default only has local knowledge and is commutative (<code>unify a b == unify b a</code>). No preference is given to either argument. A lot of work must have gone into making the error messages of Haskell as good as they are.</p> <p>Let polymorphism is useful and I'm glad that Haskell has it.</p> <p>Hindley-Milner is powerful, but it does not by itself work magic on a languageq. A language still requires a lot of good design and a well-chosen set of types.</p> <h3 id="your-turn">Your turn</h3> <p>I think you should implement Hindley-Milner in the language of your choice for a small toy λ-calculus. There is a lot to learn from it, even if you never program using a Hindley-Milner language. At the very least, you'll know what the fuss is about.</p> <p>If you think it would be helpful, have a look at <a href="my">https://github.com/ericnormand/hindley-milner">my implementation</a>. Like I said, pull requests are welcome.</p>
Comments (0)
Sign in to post comments.