DotLisp
Rich Hickey's Lisp for the .NET CLR - an interpreted, Lisp-1 dialect that gave up Scheme and Common Lisp compatibility in exchange for using the host runtime's types, collections, and garbage collector directly, and that prefigured much of Clojure.
Created by Rich Hickey
DotLisp is a small, interpreted Lisp dialect for the .NET Common Language Runtime, written by Rich Hickey and announced in October 2002 - five years before he released Clojure. Its premise reads today like a first draft of the idea that made Clojure work: rather than build a Lisp runtime and then bolt a foreign-function interface onto the platform, hand the platform everything the platform already does well. The DotLisp documentation states the philosophy directly - the idea was to build a Lisp for .NET that yielded to the CLR those things provided by the CLR that languages normally have to provide themselves, namely a type system, a memory management system, and a library, while retaining the essence of Lisp as a language.
The consequences of that choice are visible everywhere in the language. A DotLisp string is a System.String, and because System.String is immutable, DotLisp strings are immutable too - and the manual lists compatibility with Scheme and Common Lisp under Non-Objectives for exactly that reason. Integers are System.Int32, floats are System.Double, arrays are real CLR arrays. There are no conversions and no mapping tables, because there is nothing to convert between.
History and Origins
DotLisp did not start as a .NET project at all. The implementation notes trace an unusual path: Hickey began with Silk, a Scheme for Java later renamed JScheme, ported it from Java to C# and from the JVM to the CLR, then progressively abandoned Scheme compatibility as .NET semantics pulled the design away from the standard. The notes close that section with a one-line summary of how far the drift went: no Silk-based code left.
The public announcement went out to the comp.lang.scheme newsgroup on October 16, 2002, offering the language free for personal evaluation and pointing to Hickey’s own site for documentation. Open-sourcing came the following summer - the SourceForge project was registered on July 5, 2003 under the BSD license, and version 0.6 was posted six days later, on July 11, 2003. That single archive, around 48 kB of source, is still the only release file on the project page, and the project has never advanced past Beta status.
Hickey has been consistently unsentimental about what the project was. In a 2011 Code Quarterly interview he called it “the inevitable rite of passage write-a-Lisp-interpreter thing,” and said the only interesting thing about it was that, like Clojure, it was designed to be hosted and to provide convenient access to the host. The HOPL IV history of Clojure gives it the same brisk treatment, listing it among four attempts - DotLisp, jfli, Foil, and Lisplets - that produced no production-level solution but that informed Clojure’s host syntax and the idea of a hosted Lisp.
Design Philosophy
Four objectives are stated at the top of the manual: an interactive Lisp-like language for .NET scripting and development, a framework for language experimentation, deep .NET integration sharing the type system and GC with transparent access and no FFI or wrappers, and - listed as an objective in its own right - to have fun. Two non-objectives sit directly below: compatibility with Scheme or Common Lisp, and speed, though the manual adds that the interpreter is “quite useable.” No benchmark figures were ever published, and none should be inferred; this was a tree-walking interpreter with no compiler, and the To Do list ends with a question mark next to “Compiler?”
Three decisions do most of the work in shaping the language:
- Lisp-1 with lexical scope. One namespace for functions and values, closures throughout - Scheme’s model rather than Common Lisp’s, retained even after Scheme compatibility was dropped.
- Generalized truth.
nilandfalseare the only false values; everything else is true.nilis simultaneously the empty list and the .NET null reference, and it matches any type. - Everything is applicable. The head position of a form is evaluated like any other expression, and many things can then be applied: functions, member accessors, properties, types acting as constructors, and any object with a default indexer acting as an index.
Key Features
The manual’s feature list is short and specific: a command-line REPL, an embeddable interpreter in a DLL assembly, lexical scoping and Lisp-1 evaluation, &key/&opt/&rest parameters, Common Lisp-style macros, generic functions with single and binary dispatch, .NET types used natively, and transparent framework access.
Interop syntax
The interop notation is the part that most obviously survived into Clojure:
| Form | Meaning |
|---|---|
Hashtable. | A type literal - the framework name followed by a dot. Applicable as a constructor: (Hashtable. 1000) |
(.foo x) | Instance member - field, property, or method, all generalized to functions on the target |
(.foo x 1 2 3) | Instance method call with arguments |
(set (.foo x) 5) | Assignment; (.foo x 5) is equivalent to x.foo = 5 in C# |
Console:WriteLine | Static member, written Type:member |
(.IEnumerable:GetEnumerator obj) | Explicit interface qualification, .type:member, for explicitly implemented interface members |
There is also a reader-level sugar: x.foo is rewritten at read time to (.foo x) everywhere except in head position, where it becomes .foo x. The manual makes the sales pitch plainly - this lets you do all of the expected things with no more parentheses than C#, just in different places.
| |
Note the last line: .ToString is being passed as a value to map. Member accessors are first-class applicable objects, so mapping a property, a method, a constructor, or an indexer over a sequence all work the same way.
Literals and data
true and false are System.Boolean. Keywords are prefixed with : and evaluate to themselves. Dynamic variables must be named with a leading *. Lists are DotLisp.Cons objects and are proper lists only. Square brackets build arrays - [1 2 3] yields an Int32[], ['a 'b 12] yields an Object[] - though the manual is careful to note these are not true literals, merely shorthand for (vector ...). Character literals were never finished; the manual asks the reader for suggestions.
A handful of REPL conveniences round it out: $, $$, and $$$ hold the last three values evaluated, ! holds the last exception thrown, _ abbreviates System.Reflection.Missing.Value, and the symbol interpreter is bound to the current interpreter object.
Lazy sequences
The sequence library is the most Clojure-like corner of the language, and it predates Clojure by years. A sequence is defined structurally: any object for which the get-enum generic function is defined. Methods ship for IEnumerator and IEnumerable, so every .NET collection is a sequence for free. make-enum builds a lazy IEnumerator from supplied initialization, current-value, and advance expressions, and range is defined in terms of it. map, map1, filter, find, and concat all return lazy sequences built on make-enum; map->list and reduce are the eager exceptions. The into generic function then dumps a sequence into a collection of your choice - an IList, a Cons, or nil to cons up a fresh list.
The manual is honest about the limits: the lazy enumerators support no Reset() and no off-the-ends protection.
Embedding
DotLisp.Interpreter is the public embedding surface, exposing Read, Eval, Eof, Load, LoadFile, Intern, InternType, InternTypesFrom, Str, Trace, UnTrace, UnTraceAll, and TraceList. Intern publishes an application object under a symbol; InternTypesFrom publishes every type in an assembly. Going the other direction, the Function delegate and the IFunction interface let code written in any CLR language be made applicable from DotLisp, and DotLisp closures themselves implement IFunction so host code can call back into them.
Evolution
There is very little evolution to report, and that is the honest shape of the story. Version 0.6 in July 2003 was the last release Hickey published. The To Do list in the manual reads as a snapshot of where the work stopped: better debugging and tracing, character literals, finishing the math primitives, thread support, reader extensibility, non-local exit with return/break/continue, some sort of namespace or module system, and - with the question mark intact - a compiler.
Nearly all of those items were eventually addressed, but in a different language on a different runtime. The October 2007 news item at the top of the DotLisp documentation is where the project formally hands off: Hickey announces Clojure, calls it substantially more sophisticated than DotLisp, and recommends it “unless you must target .Net.”
Activity after that is community activity. The SourceForge project page reportedly records a last update in March 2013, and a review by user markhurd, edited September 17, 2015, reports having updated DotLisp internally to use .NET 2.0 lists and released that update back, along with a note that they still used the language regularly.
Current Relevance
DotLisp is dormant. There is no maintained distribution, no package ecosystem, no Docker image, and no supported runtime story for .NET Core or later - the documentation targets the .NET Framework SDK of the Visual Studio .NET era, and nothing published claims support beyond it. Anyone wanting a Lisp on .NET today has better-maintained options, including ClojureCLR, which is Hickey’s own port of Clojure to the CLR.
What remains is archival and genealogical value. The manual is short, complete, and unusually readable, and it documents a fully worked answer to a question language designers keep re-asking: how much of a runtime should a new language bring with it? DotLisp’s answer was “as little as possible,” and it is worth reading precisely because it commits to that answer without the sophistication that would later smooth Clojure’s edges.
Why It Matters
Read the DotLisp manual after using Clojure and the effect is uncanny. Truthiness where only nil and false are false. Self-evaluating keywords with a leading colon. def, fn, let, letfn, cond without the extra parentheses Common Lisp requires. Dot-prefixed instance members and colon-separated statics. Lazy map, filter, and concat over a sequence abstraction defined by a single generic function. into, dumping a sequence into a collection. Types as applicable constructors. Nearly every one of those ideas shows up again in 2007, ported to a different host and hardened by a compiler, persistent data structures, and a concurrency model.
The differences matter just as much. DotLisp is mutable and imperative where Clojure is built on immutability and value semantics; it has set, +=, while, and until, and its interop story is about convenience rather than about a coherent theory of state. Clojure did not simply add features to DotLisp - it kept the hosted design and the interop notation and rebuilt the semantic core around different commitments.
That makes DotLisp genuinely useful to look at. Most language lineages are reconstructed by historians from circumstantial evidence; here the earlier draft is preserved intact, under a BSD license, with its own author’s assessment of it attached at the top of the page. It is a rare chance to see which ideas a designer carried forward, which he discarded, and which he had already gotten right the first time.
Timeline
Notable Uses & Legacy
Precursor to Clojure
Clojure is the reason DotLisp is remembered. Hickey has said in print - both in the HOPL IV history of Clojure and in a 2011 interview - that it and his other host-interop experiments fed Clojure's eventual host syntax and the decision that Clojure would be a hosted language rather than a self-contained runtime. Readers of the DotLisp manual will recognize true/false/nil truthiness, keywords, def, fn, let, dot-prefixed member access, and lazy sequences years before Clojure shipped.
Embedded .NET scripting
The documented deployment model was embedding rather than standalone programs: a DotLisp.Interpreter object shipped in a DLL assembly, with Read, Eval, Load, LoadFile, Intern, and InternTypesFrom methods so a host application could expose its own objects and types to script code. The manual walks through building a REPL against that interface in about fifteen lines of C#.
Interactive REPL over the .NET framework
The DotLispREPL command-line program, started with boot.lisp, gave .NET developers something the framework did not otherwise ship with in 2002: an interactive prompt for poking at framework classes directly. Constructing a Hashtable, mapping ToString over an array, or calling Console:WriteLine all work at the prompt with no wrapper layer.
CLR language surveys and archives
DotLisp shows up in catalogs of languages targeting the Common Language Runtime and in developer weblog write-ups from the mid-2000s, and its documentation and source have been mirrored in community archives of Hickey's pre-Clojure work. Its afterlife is as a documented artifact rather than a deployed toolchain - no production users are publicly recorded.