[I performed a fairly straightforward import of @StreamLisp tweets. I deleted the original tweets to avoid duplication. There's much expanding to be done on this now that I'm free of the tweet limitations. There's a lot of stuff here that are not really "principles" nor "intents" and would belong on other pages (such as "ideas" or whatnot). There's lots of cleanup and expanding to do. This page looks like crap right now because I haven't yet invested in its presentation.]

[Don't worry about the lack of new stuff on this page if you were already familiar with my content, my vision for Stream Lisp is much more elaborate than everything I've published about it so far. It's just that in this first phase I'm starting by consolidating the old stuff all in one place.]

(Originally @StreamLisp tweets)

Like most Lisp dialects, Stream Lisp will start out as a toy. But I do intend to make it a viable language within the next 10 to 15 years.

Stream Lisp will initially compile to bytecodes only. Great mix of portability, easy enough impl/tools, reasonable performance, flexibility.

Stream Lisp will only specify a compilation model. I feel interpreter model is added complexity for no gain. 0th order compiler easy.

Stream Lisp will be entirely Public Domain and will have an annotatable spec with "views" for: libraries, impl-specific extensions, tips...

Stream Lisp needs a great documentation system from the start, making writing documentation enjoyable and fostering a documentation culture.

Stream Lisp will be a "conceptual superset" of Common Lisp, and favor greater power, concistency, etc. over actual backwards-compatibility.

For those who would prefer better backwards-compatibility with Common Lisp, there's already a great language for this, called "Common Lisp".

Hopefully, it will be easy to make libraries portable (or ported) between Stream Lisp and Common Lisp, because of conceptual similarity.

Stream Lisp will remove many "historical" constructs. I believe their presence in Common Lisp have a significant negative impact on perception by newbies.

Many Stream Lisp improvements will be available to Common Lisp as libraries, but I anticipate low adoption there because of legitimate cultural and political reasons.

I plan to implement Stream Lisp in Common Lisp (SBCL) (hard), then Stream Lisp in itself (hard), then Common Lisp in Stream Lisp (easy), then compile Stream Lisp with Common-Lisp-in-Stream-Lisp (easy).

Stream Lisp will systematically give just as much importance to (multiple) return values as to arguments, as they are such close duals.

Stream Lisp will feature an interface for the definition of in-memory filesystems ("hosts"), to facilitate deployment of stand-alone applications.

Stream Lisp should support concurrency eventually, but maybe not threads, because they're too screwy. I do want specified facilities for operating system process creation and control.

Stream Lisp will specify non-blocking sockets and extensible streams and sequences as standard. Built-in support makes for better marketing than widely adopted implementation support and portability libraries.

Stream Lisp will also specify a Meta Object Protocol (essentially Common Lisp's) as standard. It's important for its adoption to treat it as a "first class" feature. Better integration will result.

Stream Lisp will support regexes (including literals), with an s-expressions syntax. Text regex syntaxes are for end-users, to be provided by libraries.

I won't repeat perfectionism-related mistakes of the past. I'm developing Stream Lisp in the open from Day One.

Stream Lisp won't be monolithic. Its standard library will really just be a big bunch of blessed interfaces with reference implementations.

Like the CLHS, Stream Lisp's documentation will assume multiple implementations, on the principle that this style is beneficial even if there's just one.

Stream Lisp will specify a native application delivery mechanism. It's crucial for adoption: users should not have to care that an application is written in Stream Lisp.

I'm not against exposing powerful Stream-Lisp-enabled features to end-users. But if they can use an application without even knowing it's written in Stream Lisp, that's a success!

Stream Lisp will be a lisp-2, like Common Lisp, but will still have better, lisp-1-like support for functional programming.

Stream Lisp will treat any form being a list starting with a non-symbol as an implicit funcall. Lambda form applications will no longer be a special case as a side-effect.

"So the CL'er who codes like a bi-polar hermit doesn't care about Clojure.

You want to win the Lisp-community-at-large? Give them more power. It doesn't have to be the trivialized imprecision of expression that comes with something like Ruby. It doesn't have to be the minimalism of Io and Scheme. It doesn't have to be native continuations.

It doesn't even have to be Shen or Haskell-style static typing.

What it does have to be...is more powerful for the lone cowboy. It's going to be hard to convince a lot of them without really hitting a home-run on that one metric. Especially in a post-CLOS/Art of the Metaobject Protocol world."

-- codewright on Hacker News

Stream Lisp might be able to compete with high-performance applications written in low-level languages like C by supporting Domain-Specific Runtimes.

I like Arc's idea of vectors (and hash-tables, etc.) being callable (though not functions): (mapcar #("1st" "2nd") '(1 0)) => ("2nd" "1st")

Stream Lisp will systematically document a mapping from all 978 Common Lisp symbols to their equivalent(s) in Stream Lisp and how they differ, with code examples and tips.

"foo:" will resolve to the "default symbol" (if defined) of package foo at read-time. Useful for some small libraries.

(destructure '((&whole t t . t) &optional t &key (key "def" t)) '((a b c) d))
=> (a b c), a, (b c), d, "def", nil

nil is sometimes used in Common Lisp to denote an ignored var, but then it can't be used in that position to signify an empty list. Maybe t would be better? Also, it can't name a variable either, and it's shorter.

(lambda ((&ignored a) (&ignorable b) (&dynamic-extent c) (&type integer d) (&destructure (e f)))) It's easier to read, write and generate.

I'd like a structure editor for Stream Lisp, for easy implementation of advanced editor aids that are accurate and responsive (synchronous).

Many great opportunities for constrained/efficient MULTIPLE-VALUE-CALL alternatives: shift/mask/transform/cherry-pick.

(let ((c -1)) (defun id () (incf c))) loses toplevelness; (defun id (let ((c -1)) (lambda () (incf c)))) preserves it.

A curated/comprehensive/smart search engine would favor independent learning, avoiding early dropout due to unfortunate social experiences.

Stream Lisp should support "full program optimization" of trees of closures, through dynamic recompilation. This implies keeping symbolic "source" around.

Stream Lisp should support dynamic creation of specialized closures derived from existing closures with function types that would specify the arguments, return values, types, and arity.

When definitions are removed from a Stream Lisp file and recompiled, the corresponding definitions will be automatically removed from the image.

Stream Lisp will put class options more in evidence. (defmacro defclass (name (direct-superclasses &rest options) &rest direct-slots))

(defmacro defmethod (n/q (specialized-lambda-list &rest options)) &body body)
n/q: name or (name &rest qualifiers). Ends madness.

(defclass my-struct (a-struct) ())
(defclass my-struct (a-struct) () (:metaclass structure-class))
Same thing for condition.

(lambda (&key+ a b) [...])
(lambda (&key a b &allow-other-keys) [...])
is more concise, convenient and readable.

(defmacro lambda (lambda (form env) (declare (ignore env)) `(function ,form))) and bring your own destructuring. Maybe.

Such a macro definition syntax, while inconvenient in some ways, would help demystify the nature of macros for newbies. Also more "liberal".

Stream Lisp will make quote a non-terminating macro character. (symbol-name 'aujourd'hui) => "AUJOURD'HUI" Who ever omits space anyway?

Any programming language that would give up a little power to gain a little maintainability will deserve neither and lose both.

There's a constant struggle between the brevity and convenience of built-ins and first-classness of user definitions.

Any exclusive rights for built-ins automatically demeans and devalues user definitions and reduces consistency.

I'd pretend Stream Lisp has nothing to do with Lisp if I could, just to skip the whole ordeal of bullshit preconceptions associated with the term.

By default, Stream Lisp's reader will throw an error if it encounters a TAB character in code, except if it's inside of a string. The TAB nonsense has to stop.

Stream Lisp will definitely have more parentheses than Common Lisp.

(Originally "future lisp dialect ideas" in now-deleted "tweets" section of my main website.)

Lambda lists

(defun mapcar (function &rest+ lists) ...) is more direct, informative and convenient than 2 current &rest workarounds.

(defmacro defun (name LL &doc docstring &decl declarations &body body) ...) is more direct, informative and convenient.

(defun foo (bar &rest all-keyword-args &key baz &rest all-keyword-args-except-baz another-key &allow-other-keys) ...)

(defun foo ((&transform #'string-upcase s) &optional (first (aref s 0))) (values s first)) (foo "hi") => "HI", #\H

&transform: Less cognitive load while reading than post-update/new binding by immediately leaving old value inaccessible, clarifying intent.

(defmacro cond (&rest (clauses (condition &body body))) [CONDITION/BODY advisory]) = better code self-doc/editor hints.

Distinguish external interface for lambda lists. Internal: &whole, &environment, &aux, &optional/&key default forms.

First-class lambda-list objects would be a better representation than lists for inspection and manipulation, certainly.

(lambda (a &rest) a) == (lambda (a &rest rest) (declare (ignore rest)) a)

I oppose usage of &LL-keyword-like symbols as variables, especially in lambda lists. Needless potential for confusion and undetected errors.


FLET could promote functional programming with one simple upgrade: (flet ((f (compose #'list #'1+))) (values (f 42) (f 24))) => (43), (25)

(define #'f), (define (macro m)), (define (variable *v*)), (define (class c)), etc. Reduces symbol "duplication".

Why does default LET bind in parallel (not seq)? Because it was originally a macro: (let ((a 'a) (b 'b)) ...) == ((lambda (a b) ...) 'a 'b)

Sequential binding might be simpler and more intuitive default, because it arises naturally when nesting multiple heterogeneous constructs.

Scrap SPECIAL decl, make *earmuffed* variables always refer to dynamic bindings. Very convenient; enforces convention.

Make lexical bindings immutable unless marked otherwise, to reduce cognitive load while reading. &transform desirable.

I object to every difference between bindings in the variable and function namespaces, except for the latter requiring function designators.

(declare (type funtype #'my-fun my-var)) Consistent with IGNORE/IGNORABLE/DYNAMIC-EXTENT. One less var/fun dichotomy.

(lambda ((ignore i) (the function f)) ...) == (lambda (i f) (declare (ignore i) (type function f)) ...)

(defun functional-lisp-2 (#'transform datum) (values datum (transform datum))) (functional-lisp-2 #'- 42) => 42, -42

Lambda lists

MULTIPLE-VALUE-CALL also showed me how funargs and retvals are such close duals of each other. Future dialect: take retvals more seriously.

Make DEFUN take a retval list after arglist = better code self-doc and editor hints, promote careful retvals design.

Dynamic return-values count+map without reification = novel possibilities like efficient multiple-value "transformers".


The concept of structs is useful for performance, but DEFSTRUCT has to go. It should be reformulated in a strongly DEFCLASS-inspired way.

Rename SETF to ASSIGN (suggested by Zhivago). We'd then have "assignable places", GET-ASSIGN-EXPANSION. Makes sense!

multi-phased generic-functions could dispatch on (cons (eql operator)), alleviating frequent need for ad-hoc solutions.

Beyond native fractions: native square roots? Same rationale: absolute precision, unlike floating-point. √(1/2) => √2/2

Having an agreed-upon short name for "cadr-valued alist" would be convenient. I propose the term "blist". BSSOC/RBSSOC weird but pragmatic!

Make IF's "else" required. Eliminates "is there 'else'?" when big "then". No "else" bad style anyway: use WHEN/UNLESS.

(eval-when t ...) == (eval-when (:compile-toplevel :load-toplevel :execute) ...) would help preserve everyone's sanity.

((find :key k :test t) seq): k/t are part of "operator", not "data". Still lisp-2: "implicit funcall" enabled for FIND.

More about ((fun op-args...) data-args...): Varargs data-args! Old #'find now #'(find). Old (lambda (s) (find s :key k)) now #'(find :key k)

(defun (member item &key (key #'identity) (test #'eql)) (list) (cl:member item list :key key :test test)) or (d name ops datas &b)

(lambda (&rest keys &key (class 'default-class) &allow-other-keys) (apply #'make-instance class :class +absent+ keys))

Make (intern "") => error, as zero-length symbols have no particular utility and are thus not worth the special cases.

(Originally "Common Lisp rants" in now-deleted "tweets" section of my main website.)

(I'm putting this stuff here temporarily to help with the migration of ideas from "rants about Common Lisp" to "vision for Stream Lisp".)


Don't get me wrong, I like CL. Just can't help but dream of even better dialect. Rants/suggestions are meant to be inspiring, not negative!

When I rant about CL, I do it from an idealist's perspective. X3J13 did a great job! Had to seriously consider compatibility and politics.

Rants about Common Lisp (the language itself)

CL rant: (destructuring-bind x 42 (1+ x)) => 43 instead of undefined. Would be more useful and simpler conceptually and implementationally.

CL rant: MULTIPLE-VALUE-BIND should accept lambda keywords. As it is, dealing with &rest and &key multival returns is unnecessarily awkward.

enhanced-multiple-value-bind fixes this problem.

CL rant: I don't like that (let (foo)) == (let ((foo nil))), as explicitness is good and this "feature" often complicates macro code.

CL rant: (destructuring-bind (&rest r) 1 r) should return 1, not undef, for consistency with (d-b (f &rest r) '(0 . 1) (values f r)) => 0, 1

CL rant: A few legacy dead-ends for newbs: SET, SETQ, RPLAC[AD], GENTEMP, PROG, "modules", symbol properties. Smart CL search could advise.

CL rant: Iteration macros don't guarantee a fresh binding for each iteration: problem for closures, trap for newbies, annoyance for masters.

Nomination for WORST CL feature: (declare (my-type foo)) ~= (declare (type my-type foo)) No portable way to tell if my-type is type or decl!

CL rant: My nomination for most glaring trivial omission from standard: (defun boolean (generalized-boolean) (if generalized-boolean t nil))

My #1 grief with CL: Ceaseless refactoring due to excessive indentation introduced by nested binding constructs in (not-that-)complex code.

I expect to release a library that thoroughly fixes the problem of excessive indentation due to nested binding constructs within 2 months.


CL rant: Macro lambda-lists allow (f s . r) == (f s &rest r), so almost any usage of built-in list functions to process them is "incorrect".

CL rant: I object to NIL == #'identity in :key arguments, as the extra conceptual and impl complexity are clear while advantages are not.

CL rant: CL's lackluster support for alists/blists/plists is quite surprising given the major role these play in most code. I blame LOOP!

CL rant: LOOP: Surely something's dead wrong when you have to adjust code in THREE places just to switch between alist and plist processing.