Est. 2002 Intermediate

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

Paradigm Functional, with imperative and generic-function object support; dynamically typed Lisp-1
Typing Dynamic, Strong - values are .NET System types with no conversion or wrapper layer
First Appeared 2002 - announced publicly on comp.lang.scheme on October 16, 2002
Latest Version Version 0.6, posted to SourceForge on July 11, 2003; a community-contributed internal update to .NET 2.0 collections was reported later

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. nil and false are the only false values; everything else is true. nil is 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:

FormMeaning
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:WriteLineStatic 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.

1
2
3
4
5
6
7
8
9
; a REPL session, in the manual's style
(Console:WriteLine "Hello World")

(def h (Hashtable. 1000))       ; construct via a type literal
(.Add h :a 1)                   ; instance method
(prn h.Count)                   ; sugar: expands to (prn (.Count h))

(into (ArrayList.) (map .ToString [1 2 3]))
; -> a new ArrayList holding "1", "2", "3"

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

2002
Rich Hickey announces DotLisp on the comp.lang.scheme newsgroup on October 16, 2002, making it freely available for personal evaluation and pointing readers to richhickey.com for documentation
2002
The implementation notes record the project's lineage: DotLisp began from Silk - the Scheme for Java later renamed JScheme - was ported from Java to C# and from the JVM to the .NET CLR, then moved away from Scheme compatibility until, as the notes put it, no Silk-based code was left
2003
The DotLisp project is registered on SourceForge on July 5, 2003, and the language is released as open source under the BSD license
2003
Version 0.6 is posted to SourceForge on July 11, 2003 as dotlispsrc.zip, roughly 48 kB of source. It remains the only release file on the project page, and the project has carried Beta status since.
2007
A news item added to the DotLisp documentation in October 2007 announces Clojure: Hickey writes that he has been working on a dynamic programming language and Lisp targeting the Java Virtual Machine, that it is substantially more sophisticated than DotLisp, and that he strongly recommends it unless you must target .NET
2011
In a Code Quarterly interview with Michael Fogus, Hickey describes DotLisp as the inevitable rite of passage write-a-Lisp-interpreter thing, adding that 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 CLR in this case
2015
A SourceForge review by user markhurd, edited September 17, 2015, reports updating DotLisp to use .NET 2.0 lists internally and releasing that update back to the community, alongside a note that they still use the language regularly
2020
Hickey publishes A History of Clojure at HOPL IV (Proceedings of the ACM on Programming Languages, June 2020), which describes DotLisp as an interpreted Lisp with host interop for the CLR and groups it with jfli, Foil, and Lisplets as experiments that informed Clojure's host syntax and its hosted design

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.

Language Influence

Influenced By

Influenced

Running Today

Run examples using the official Docker image:

docker pull
Last updated: