Kent Pitman, a Common Lisp standards contributor, writes papers and essays on Lisp design, condition handling, and language history.
HN commenters call him a good read on equality in dynamic languages, condition handling, and the Lisp-1 versus Lisp-2 tradeoffs.
It's hard (if not impossible) to pick a single attribute of 'Lisp' that's shared by all the languages that are part of the Lisp family of languages. There are Lisps without macros, there are Lisps without the simple syntax, there are Lisps without dynamic typing, or lexical scope, or any of a dozen other features that are present in at least on Lisp.... so I think reducing Lisp to a handful of features and calling that 'basically it' is a good way to miss the point. (Not to belabor the obvious
One of my favorite points about Lisp was made by Kent Pitman back in 1995: http://www.nhplace.com/kent/PS/Lambda.html
In it, he describes through a though experiment what it means for a language to be defined by its community and how that impacts its development and use. It isn't as much about the specific features as it is the approach that led the language to get to have those features in the first place.
Just to illustrate, the simple syntax is a good example. It's not immediately obvious why simple syntax is a good thing... in fact, given the fact people prefer infix and a big part of the backlash against Lisp is due to the syntax, you might argue that it'd be better for Lisp to drop the syntax entirely. (As was tried in Dylan and Logo) That, you'd lose a lot that's not necessarily obvious. There's a real value in having a concrete syntax for your language's core data types and then defining the language in terms of those data types. The simple syntax isn't the point as much as…
Actually, Common Lisp has a very nice (optional) type system.
It is not uncommon to declare types once everything is working to speed things up -- so with Lisp, you get the best of both worlds.
What sets Lisp's condition system apart is that it separates signaling a condition, handling it, and restarts. In other words, the decision about which handler to choose for an exception is usually made in much higher-level code, where it belongs.
And when no suitable handler is found you get dropped into the debugger. While in the debugger you can create the handler you need, and then call it.
And all this without unwinding the stack at all.
So while in another language I'll have to restart a long running process over and over again until the code covers all the unexpected cases -- in Lisp I can just start off assuming the happy path and refine as I go along, without ever having to start over because of an error.
I almost always feel that people talk about the wrong things regarding Lisp. A huge deal is made over macros, but nobody ever mentions the reader which is what makes macros possible. And when people do talk about macros they almost never mention reader macros.
And sometimes people do make a completely justified big deal out of the MOP (meta-object protocol) -- but frankly, I think generic functions and multiple dispatch are far more important. Or the fact that Lisp offers multiple-inheritance done right (multiple inheritance gets a bad rap because most other…
> I don't really know what "conditions+restarts" is but a few articles landed me into LISP which I find totally unreadable. So, can you point me to some "conditions+restarts" code that I can understand/appreciate easily?
You’ll have to read Lisp, I’m afraid; the best description I know is in the book Practical Common Lisp[1].
(Come on, Lisp syntax is quirky, but it’s not unreadable, and unlike APL or Forth or even Haskell it doesn’t require you to memorize a bunch of semi-meaningless punctuation before you can understand what is going on—it’s pretty wordy usually. I’m not saying you must bring yourself to love writing (f x y) instead of f(x, y), only that adjusting from one to the other should not be particularly hard.)
I mean, I have done a toy Forth implementation, but that is hardly more readable with no experience with the language.
One system that is almost conditions and restarts is 32-bit(!) Win32 SEH, but it is not particularly well-documented and the language bindings usually try rather hard to hide that (though, if you think about it, On Error Resume Next from classic VB is unimplementable on top of bare try/catch).
...
OK, you nerd-sniped me :) Here’s a toy (no subtyping! no introspection! no condition firewall[2]! no tracebacks! no support for native errors! etc.) condition system in Lua (sorry, nested functions in Python are painful):
-- save as cond.lua
local M =
{}
local error, unpack = error, unpack or table.unpack
local running =…
That might be more like what was called an FEXPR mechanism in some early Lisps or some niche Lisps, or even in languages like R.
https://en.wikipedia.org/wiki/Fexpr
When an FEXPR is called, it gets unevaluated args and can then at runtime decide what to do.
Macros OTOH are a different mechanism, where expressions get rewritten at macro expansion time, which can be for example at compile time. The macro then gets called with arbitrary expressions it encloses (those don't need to be valid code by themselves) and computes new source.
Thus macros are code generators from expressions. In a compiled implementation, macro expansion is done with compilation interleaved: each form gets expanded, even repeatedly until it no longer expands into a macro, and then the resulting non-macro form gets compiled.
Thus in a way a macro does not delay execution, it does the opposite: it actually shifts computation to compile time -> the computation of code from expressions and the computation of arbitrary side effects in the compile-time environment.
In an interpreter version of Lisp, the macro gets also expanded at runtime - but in its own macroexpansion during evaluation. There eval will call the macroexpander repeatedly until it gets a non-macro form.
Now, what can you do with arbitrary complex code generators at compile time? https://stackoverflow.com/a/2563308/69545
Actually Paul Graham wrote a classical Lisp book explaining a bunch of things around macros. Available here for…
Kent Pitman's idea of "languages as political parties" [1] seems to me to offer the best perspective on the surprising reliability of C. The core community shaping C over the years has been:
1. Unix-centric, so the community has a coherent view on the preferred sort of semantics offered to users of code;
2. Applies Kernighan--Ritchie--Pike coding style that gives a clear "code smell" [2] for C code; and
3. Is the natural home of "Worse is Better", which favours source-code simplicity over specification simplicity.
For example, the "C party" favours procedures "succeeding" with non-zero exit codes on failure where other language parties have language constructs to represent failure. This can be better from the point of view of source-code simplicity, both in code and compiler, than exceptions, and worse in terms of semantics. The LK coding style, among many other C coding styles, prefers the use of goto (or sometimes setjmp/longjmp) to handle exit status [3], which is frowned upon in nearly all non-C programming language communities; the restriction of goto/longjmp to handling errors in this way avoids the problems Dijkstra pointed out [4,5], and correct usage of goto/longjmp in properly checking exit status has good code smell if generally accepted coding conventions for the use of goto to handle failure are followed.
There's a nice fragment of code illustrating the idea on SO [6].
[1]: Pitman 1994, Lambda: The ultimate political party,…
Great concrete examples, a couple of things to add about them:
Handler, conditions and restarts are much more expressive than in other languages, you can signal a condition (the analogue of throw) with restarts attached to it which only if you desire to handle it in another part of the program where(/if) you decide to handle the conditions(the analogue to try). It is analogue to throwing an error with catch statements attached and when you write the try statemet the catch statements attach to the throw statement are made available to said catch. Kent Pitman explains it much more eloquently than me[1].
Also to add how well thought out CLOS dynamic redifining of classes is, when one redifines the change is garaunteed to happen between redefinition and the next time you access said class, as to give leeway to the implementation to decide the best strategy to do so (for example if redifining a class with 10K+ instances if you do it all at once the system may become unresponsive). Also if the redefinition isn't just adding or removing slots you can provide a function to handle the updating of slots for the redefinition.[2]
[1]: http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
[2]: http://www.lispworks.com/documentation/HyperSpec/Body/f_upda_1.htm