Pre-Conj Interview: Jeanine Adkisson

Talk: Design and Prototype a Language in Clojure

Jeanine Adkisson's talk at Clojure/West is about designing and prototyping a language using Clojure.


Lisps have been used for a long time to prototype other languages. Scheme was designed and used for just that purpose. The most popular books on compiler design often start with Lex and Yacc, which are just really old tools built when memory was so restricted you had to do your parsing in stages. Instaparse is way better for quickly exploring syntax. And since lambda calculus (which Clojure is based on) is computationally complete, you're in good hands exploring the entire range of computational semantics.

However, these are just generalities. Jeanine Adkisson will likely bring wit and depth to the talk, as she did in her Clojure/conj talk. She's a language designer, so she's got plenty of experience to draw from.

As an interesting background to language prototyping in Clojure, check out Bodil Stokke talking about her language Bodol.

About Jeanine Adkisson

Homepage - GitHub - Twitter


Jeanine Adkisson was kind enough to answer some questions. Her talk at Clojure/conj will be about variants in Clojure. Read the background to her talk.


LispCast: How did you get into Clojure?

Jeanine Adkisson: Through work, actually. I'd seen clojure around and studied some of its features from a language design perspective, but I've never really been a lisper, so it hadn't occurred to me to actually use it until we started a migration to Datomic at GoodGuide. I admit to being a bit skeptical - I often find that lisps are a bit oversold - but a coworker of mine (@bonkydog) started really gushing about generative testing, core.typed, and the awesomeness that is leiningen, so I decided to give it a closer look. Your talk is about Variants. Can you explain Variants and how they are useful?

JA: Sure! Variants are a functional design pattern that should be familiar to anyone who's worked in the Haskell/ML family of languages (and to a lesser extent, Erlang). Their main purpose is to provide a structured way to manage polymorphism. In lisps and other single-type languages, they're not that widespread because we don't have to convince a type-checker that our polymorphism works out in the end. But we still have to convice users of our libraries and our doc writers that our functions make sense!

The method of representing variants I'll be talking about came out of some frustrations I was having at porting part of a language over to Clojure, and finding it difficult to represent and destructure the trees I needed to process. But I was really impressed at how quickly David Nolen and Ambrose BS got back to me and shipped patches that made this approach possible (and typeable!). You mean David Nolen patched ClojureScript and Ambrose BS patched core.typed?

I wonder what it means about dynamic languages like Clojure that you can add Variants as a library instead of making it an integral part like in Haskell. What do you think about that?

JA: Yeah, actually it was a bugfix to the match macro and the way that core.typed handled vectors that were key. What's even more interesting about variants is that there is really no library to write - all the machinery is already there. The solution isn't even particularly clever or novel - it's already in use in, for example, instaparse. It's just not on people's radars, or they have misconceptions about the approach from their OO training.

I love the fact that you can backport this pattern into a language that's not explicitly built for it. I wouldn't necessarily count that as a strength for dynamic languages in general though - I find a language's strengths often come from ways in which semantics are restricted. Clojure is already a great example of this because it explicitly rejects the use of willy-nilly mutable structures, to great benefit. Sure, you can program that way in Ruby, but the tools are already there in Clojure.

What I find coolest about the Clojure case is the extent to which core.typed has been able to backport types onto a mostly untyped ecosystem. The general problems with dynamic languages are still there, but they have to be made explicit with union types. How many functions surprised you by outputting (U <thing> (Value nil))? But because it's powerful enough to support variants in a lightweight and usable way, I think we now have a tool to deal with one of the most common culprits of surprise nils. I think the approach is worthy of study. I flipflop between statically typed vs dynamically typed because there are obvious problems with both. How close does your core.match and core.typed solution come to a full-on ML/Haskell-style, integrated solution?

JA: It's pretty comparable, and actually has some advantages. Since it's just vectors and keywords, it's trivial to handle in a generic way, and as long as you're not putting functions or host objects in them it's trivial to serialize and send over the wire. (Haskell and ML can do most of this programming for you with various generic programming extensions, but it's not straightforward or obvious). But yeah, core.typed is capable of refining types based on the tag matching code generated by core.match - a little roundabout, but it gets the job done. The match macro can also match against more than one object at a time, something OO languages constantly struggle with. There's a little more macro work to be done in core.typed to make it really straightforward to do variants, but it's a relatively trivial syntactic transformation.

In terms of typed vs. untyped, I think there's lessons to be learned from both. My instinct is to say that we just haven't really figured out how to make typed systems all that usable. Haskell and ML went a long way towards this, and I think they kind of hit a wall because of their restriction that compilation be decidable. In other words, the compiler is not allowed to infinite-loop - which sounds like a good idea until you line up all the restrictions required to make that happen. In dynamic languages, we don't have a semantic separation between "compile-time" and "run-time", but we still talk about "load-time" vs "run-time". And at load-time you can wield the full turing-complete power of the language. That's why I'm also excited about projects like Agda and Idris, which, while still statically typed and compiled, blur the lines between runtime and compile time - moving away from strict decidability guarantees in exchange for a more expressive system. A lot of the audience is going to be new to Clojure or they have never heard of variants. Is there a resource that you think would help make the most of their time? Just a little pre-reading or pre-watching so they feel confident with the material?

JA: Yeah, there's unfortunately not a whole lot targeting Clojure or other lisps, which is one of the reasons I wanted to give this talk. There's a great section in the ocaml tutorial about them, and Learn You a Haskell covers them as well (although they call them ADTs). The approach I'll be talking about is most like what's used in Erlang, as in the temperature example here. Where can people follow your adventures online?

JA: Probably the best way to keep up with me is on twitter at @jneen_ (note the final underscore), or on github as @jneen. One last question: If Clojure were a comic book superhero, what would its superpower be?

JA: Hahaha, I'd probably say a shapeshifter, like most lisps I suppose. Thanks for an excellent interview. I had fun.

JA: Awesome, thanks!