Why didn't Common Lisp fix the world?
quora.com
quora.com
Worse is Better: https://www.jwz.org/doc/worse-is-better.html
The Bipolar Lisp Programmer: https://groups.google.com/forum/#!topic/comp.lang.lisp/eicqv...
The Lisp Curse: http://winestockwebdesign.com/Essays/Lisp_Curse.html
Where Lisp Fails: http://www.loper-os.org/?p=69
Lisp is still my language of choice. I'm more productive in it than other languages (C, Java), not because I know the other languages less well, but because they're less expressive. So, for me, Lisp fixes the world.
Why aren't people using it? There are three things often missed.
(1) People's choice of programming languages perhaps isn't as rational as they might like to believe. There's a large amount of personal taste involved. For a personal project, that's no barrier, you can use your favourite language, but if you're working with a dozen other people, Lisp is unlikely to be adopted because more people on the team will prefer Java, C++, or Python. Multiply over all the commercial projects and you can see why you can't build a career around Lisp programming.
(2) There are accidents of history. When DEC decided to discontinue the PDP-10, the days of the hacker community built around it were numbered. Those involved then went on to build Lisp machines, but they were undercut by cheaper, commodity hardware which ran Unix. Then, the AI winter arrived. It was bad for Lisp, but Prolog fared far worse.
(3) Linguistic relativity. The Sapir-Whorf hypothesis might be dubious for natural language, but for programming languages it certainly does apply. If you're uncomfortable with Lisp, you won't be tackling problems at which Lisp excels, or (at best) you'll do them in a completely different way, and it will take you longer. The result is that people think their language of choice is fine for everything that matters, and the problems they don't tackle don't matter to them.
Nit on this bit of history: the PDP-6/10/Decsystem 20 architecture was a dead end due to its maximum address space of 1 MiB (of 9 bit bytes), but was still a very healthy line of computers when the Lisp Machine effort started circa 1974, about a decade before DEC dumped the whole line, then a 100 million a year business (a lot of us looked at that and decided DEC's business sense was so poor we weren't going to invest any more then we could in their systems, which of course turned out to be absolutely right).
Programming languages are about providing a useful way for humans to formulate and implement their ideas. Starting by turning things on their head reverse polish notation style is bad start as it is counter intuitive to nearly everyone.
Have you met a Lisp advocate?
More seriously, programming is as you say a way of turning thoughts into mechanical implementations. But not everyone thinks the same way or finds the same things intuitive. So some people stumble across Lisp or Forth and find it matches their way of thinking so perfectly that they can't understand why not everyone agrees.
Pragmatism counts for a lot in programming language deployment too. There was no big platform that required or encouraged Lisp in the way that Sun did for Java, the browser does for Javascript, or web backend did for PHP and Perl.
Lisp (especially compared to C) indeed helps to reduce incidental complexity to some extent. So it is easy fall into almost religious belief that Lisp is the way to pure programming bliss. Of course the promise is never fulfilled because domain complexity and even incidental complexity is here to stay.
This extend stays untapped. You cannot even imagine how far this ability to eliminate complexity extends.
Of course, it's not just Lisp, it's Lisp (or any other meta-language) combined with a certain design methodology.
A methodology which pretty much boils down to a notion that "everything is a compiler". And, since compilers are trivial and there are well known techniques for eliminating any complexity you can find in a chain of compiler transforms, this way you can eliminate all possible complexity, no matter what your domain is.
Yet, I find it amusing that only a handful of people are even aware of this ultimate methodology.
No.
and there are well known techniques for eliminating any complexity you can find in a chain of compiler transforms
Any complexity? Really?
Why would you say so? There is nothing simpler than compilers. They can be broken down into pieces as small as you like which are still independent and fully sequential. Most of the rewrites you'll ever need to do can be done in a total language - i.e., you don't even need Turing-completeness for most of your code.
> Any complexity? Really?
I have not met a kind of complexity that cannot be decomposed into trivial pieces mechanically using this technique. And I've seen a lot of very different things.
P.S. And try not to confuse a size with a complexity. E.g., C++ spec is huge, and implementing a compiler for the full language is a daunting task. Long, but not complex.
The examples are legion; to go to another domain, look at one of the reasons the VLIW Itanium failed, except for some numeric code the compiler(s) couldn't schedule enough operations for those Very Long Instruction Words.
(Perhaps a more primary reason was that x86 code ran too slow, denying customers an easy upgrade path that due to volume sales might have resulted in more effort being put into the VLIW compilers.)
We're talking about the programming paradigm where everything is a compiler. Network protocol parsing is a form of compilation, all file formats parsing is a form of compilation, reacting to the UI events is a form of a compilation, processing data in whatever ways is a form of a compilation.
Yet, you can only invent any optimisations at all for a small subset of compiler transformations, so for most of the cases optimisation is just totally irrelevant.
As for the minimally acceptable optimisations, they're all trivial anyway. What you'd normally expect to see in most of the IRs is: constant propagation/folding (trivial), ADCE (trivial), algebraic simplification (trivial, no matter how ugly it is in, say, instcombine pass in LLVM - they're just doing it wrong), partial evaluation (trivial), inlining (trivial), backtracking for the latter two (trivial, but rarely seen, most would resort to awfully complex heuristics instead), CSE (trivial), escape analysis (trivial).
What is not trivial is the stuff you'd rarely need in the high level languages, and this paradigm is all about the high level languages indeed.
Not trivial: loop fusion, polyhedral analysis, vectorisation, and, finally, everything related to the code generation: instruction selection, register allocation, instruction scheduling, VLIW packing, all that.
You'll never see this stuff in any high level domain specific language - you already have a common low level backend to take care of it for you anyway.
Before we go any further you're going to have to define complexity.
This complexity defines how well the labour can be split between team members - i.e., defines the development scalability. This complexity defines how easy it is to understand any individual module and to change anything.
Of course we can start nitpicking, go into a definition of the Kolmogorov complexity and all that (but, remember, Chaitin defines this complexity with Lisp, btw.), and it will all be irrelevant to the problems of the software engineering.
But, for all things practical, yes, it's just a handful of Lisp macros. Because in most cases it worth designing your DSLs as total languages, and totality proof is totally trivial, as well as a proof of an opposite.
> there are well known techniques for eliminating any complexity you can find in a chain of compiler transforms,
Can you imagine finding a working termination proof in a compiler pipeline and finding yourself in a dire need of simplifying this complexity down to something manageable?
Exactly. Haskell took until 1998 to really come together! ;-)
(And cabal hell lasts forever.)
Obligatory: "have you tried Stack?"
The hardware and the long standardisation process didn't help either. That, combined with it being both an old and a new language, meant that when the spec was done there wasn't the excitement you get with a new language like Python or Clojure. It missed the boat with building a web community and missed again in building hype.
However, Norvig is completely correct in saying that just because the CL language itself may not have taken off, doesn't mean the ideas didn't make it everywhere.
Or so goes one version of the Official Story, I wasn't really a witness to more than the beginning of that, when they were grossly over-hyped. Or solved the wrong problem, my favorite example there being DEC's configuration -> part numbers to order expert system. Sun made it simple, at the lowest level of a workstation, the major choices of the sort DEC was optimizing came down to which keyboard and power cord you ordered to fix your country's standards.
Where first there was discipline and hardly passed on conventions, there now would be statically enforced rule coerction.
I deeply believe that the utility of language is not in what it enables, but in what it forbids. For example Clojure and Rust have neat mechanisms to deal with mutability, where you need something extra to mutate a value. Contrast this to C where you need to go extra lenghts to make things const (ie nobody'll do it).
Is this even possible without writing your own tools?
It's built as a platform for defining arbitrarily simple or complex languages and integrating them together. You should take a look at it. See http://www.ccs.neu.edu/home/matthias/Thoughts/Racket_is____.... :
> To a language designer, Racket is a programming language laboratory. This does not mean that the language is unstable. The designers do not change the language in a whimsical manner. That is, Racket comes with a unique collection of linguistic mechanisms that enable the quick construction of reliable languages, language fragments, and their composition. These tools are so easy to use that plain programmers can design a language after a little bit of instruction. So when a well-trained programmer decides that none of the available dialects is well-suited for a task, he designs a new dialect and writes his program in it. As Paul Hudak said, “the ultimate abstraction is a domain specific language.”
The more interesting and honest reply in my opinion is ranked in second place by Robert Smith - sounds like he did some proper reflecting on what it is to be a Lisp programmer nowadays and what the language's (or implementation, since this question is for CL specifically) shortcomings are
http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...
I do not understand how it has not taken over the world.
Background: I have ~15 years of experience in programming. In my spare time I learn new languages.
> In my spare time I learn new languages.
A moment's reflection on the latter would explain the former.
edit: and not only me it seems: https://www.google.com/trends/explore#q=%2Fm%2F03yb8hb%2C%20...
I think you are speaking out of ignorance here. "everything is a list" is just a metaphor. Everything is either list or atom and almost everything is atom.
how can you both say this and that i'm talking out of ignorance? you're basically confirming my words.
larger point i was making is that "everything is a list" abstraction/metaphor influenced the decision to not include native representation of other data structures into the language. because parens are enough to represent everything. well they aren't enough for people looking for convenient, practical and simple languages.
In list everything is either atom or a list. `atom` type is equivalent to `(not cons)`.
CL has plenty of native data structures including hash tables, streams, files, functions, arrays, strings, numbers, characters, structures, ...
lisp stretches the meaning of "native" by power of macros, but something being possible still doesn't make it practical or convenient.
struct foo {
int x;
}
Do you care how the structure definition is represented in your
compiler? is it a list, a vector or a custom data-structure? You don't
know, and it doens not matter to you. Besides, it is totally different
from what you manipulate at runtime (for type definitions, not
much). Consider the second snippet: for(int i = 0; i < n; ++i) {
// something
}
Should the internal representation for the block delimited by braces
be necessary the same as the one used to represent the fields in the
previous structure? I mean, do the brace characters always map to the
same internal data-structure? Probably not. In C, the concrete syntax
has generally nothing to do with the abstract syntax. This is not the
case in Lisp, because each syntax is used to parse a specific type of
data.In Clojure, you write #{...} and the reader (LispReader.java) sees the sharpsign followed by brace and builds a set containing the values inside the braces. That set is contained inside the AST, and acts as a literal value. That value is shared among all invocations of your code, meaning that if you call the enclosing function at different places, the same literal object is shared. Since this data is immutable, this is not really a problem because when you add more elements at runtime, the original literal is not modified. OTOH, you can still invoke EvalReader, #=(), which evaluates code at read-time. I believe that you can put mutable data-structures into the AST and obtain funny results by mutating it during execution.
How is it different from Common Lisp? Not much.
Common Lisp is less restrictive because the readtable can be customized. Also, the standard data-structures are mutable (however, the behavior is undefined if you try to mutate literal data; some compilers warn you about that). I don't think I ever needed to have hash-table as a literal, it seems practically useless because if you want a hash-table in your source code, odds are that it contains few elements. If so, an association list is simpler (and more efficient).
If you really really need a complex data-structure in your source code, you are more likely to use LOAD-TIME-VALUE anyway, because it works better with a compiler: not all data-structures are (nor should be) automatically serialisable in the object file (they could containt transient data, for example). In other words, your original code has little to do with the machine code that is produced and eventually loaded (possibly in another environment). LOAD-TIME-VALUE is a way to execute code at load-time to produce such data-structures in your code: I have a parser which pre-computes some regexes, and this is not something that has an equivalent in Clojure, as far as I know.
So my first point is: you can have literals of any type in your source code in Common Lisp (like in Clojure). All of them have dedicated syntax, like vectors of arbitrary dimensions #2A((1 2)(3 4)), bit vectors #*1111, complex numbers #C(0 1), pathnames #P"/tmp/foo", and of course strings. Likewise, you have a generic syntax for structures. Suppose you define a structure FOO with a single slot X, then #S(foo :x 10) is a literal of that type.
However, not all data-structures have such syntax: maybe it was not deemed necessary to represent literal hash-tables in source code, or maybe it did not make it to the standard, I don't know. I personally don't miss it and I am happy to use auto-complete the few times I need a hash-table. However, if you want you can customize your Lisp easily; for example, load the FSET library and provide a custom syntax over immutable data-structures. If you look at Maxima, it has a lot of custom syntax. And I am not talking about macros, but about changing the readtable which reads Lisp objects from a stream. An example of this is RUTILS[0], which provides the #h(equal "k1" v1 "k2" v2) syntax for hash-table. But note that this notation is only used to produce a list, namely the code required to produce a hash-table at runtime and populate it.
Which brings me to my second point: there is a big difference with Clojure in that Common Lisp doesn't use different types to provide syntactic sugar. For example, in Clojure [] is for vectors, {} is for maps and #{} is for sets. That literally means that when you have a binding, you allocate a vector. When you destructure a map with map-notation, you use internally a map. So the language is mixing two concepts: syntactic sugar and actual internal representation. I find this rather hackish but I can live with it because it probably has little incidence. I think it adds complexity to the compiler which has to walk different kinds of code. Besides, the usage of such syntax is not regular: sometimes vectors are for unevaluated data, sometimes for bindings or destructuring, so you still need context to know what you are doing. Those are minor points, and I understand that Clojure can look more convenient to use. However Common Lisp being a little more wordy is not a problem for me.
[0] http://lisp-univ-etc.blogspot.fr/2016/05/improving-lisp-ux-o...
i mostly agree with all you've said, just few comments:
> I think it adds complexity to the compiler which has to walk different kinds of code.
either you deal with complexity or your users will have to. it's not always clear where the boundary should be though i like where clojure puts it.
> I understand that Clojure can look more convenient to use. However Common Lisp being a little more wordy is not a problem for me.
s/me/experienced commonlisper/
aaand that's the whole point.
If I am an experienced Common Lisper, it is because I was once a noob Common Lisper and already not bothered by syntax. Maybe that's because I already had experience with several other languages, which have all some superficial negative aspects. That being said, I prefer to use CL but I don't mind using Clojure.
> either you deal with complexity or your users will have to.
I did not say the complexity was necessary present in one case or another. Biased as I am, I see no complexity with a simple, regular usage of parenthesis. Brackets and braces are good in the eye of other kind of people, sure.
> that is the whole point of the thread - why hasn't lisp caught on, and i'm an example of both failure and success in that regard.
Well, should we generalize from your example then, when it is not appropriate to do so in my case?
Besides, I was mostly responding to your posts about literal values.
> If I am an experienced Common Lisper, it is because I was once a noob Common Lisper
> should we generalize from your example then, when it is not appropriate to do so in my case
if we do so carefully? obviously there is some spectrum of how mentally exhausting various aspects of language's syntax are for people, and how prepared/willing different people are to wrap their heads around those aspects.
crude analogy would be saying unary number system is better than decimal because it's simpler (via having less "things"), all operations are more intuitive and there is no complexity in dealing with all those digits.
just as we use decimal instead because we have physiological reasons to do so, it's not outlandish to suppose that we have psychological reasons to prefer syntax that is more rich than what CL provides out of the box but also not too rich because it's mentally exhausting. clojure seems to be hitting the sweet spot(at least within lisps family) and while many factors are at work here, the evidence is that clojure quickly became more popular than cl.
to be clear, i'm not saying here that popular = better, of course.
(Which makes me think about my project for managing the world's knowledge, http://onemodel.org and how to be more practical like fortran or cobol were in their prime, and not primarily idealistic like Lisp in its prime. Hmm.)
There are still a few problem domains (like Computer Algebra) where Lisp provides enough benefits to be worth bridging the gap between its world and modern operating system environments, but by and large the inability to be a complete solution I think serves as a disincentive to use Lisp.
Lisp is like arithmetic, without more concepts or libraries it doesn't work, you have to reinvent the wheel.
R provides near 8000 useful packages, that's what makes a language a useful tool to solve a problem, and it can call routines in C, fortran or use C with armadillo. To sum up, a language without a great community and specific libraries is not a way to fix the world, on top of that there are few jobs using Lisp. Clojure is trying to take advantage of all those java libraries, that is a good step in the right direction. Clasp is another idea trying to use C++.
http://jordi.platinum.linux.pl/piccies/lisp.png
It's the most frequent complaint uttered against Lisp for a reason. All of the other things mentioned in the top responses are addressed by modern Lisp variants. If Rust or Swift were implemented with sexps and everything else about them were equal, they would have a lot of difficulty winning hearts and minds.
BUT, they hang around because of old code, and they're composable, e.g. the 2nd Lisp Machine design and first to be semi-mass produced was the CADR, as in (car (cdr list)), or (first (rest *list)), is in the 2nd item in the list.
So, yeah, if you don't want to have to learn a lot of arcane stuff that's incidental complexity purely because of history, go for newer dialects like Scheme and Clojure which had the opportunity to rationalize a lot of this (but Scheme at least still has cadr and company because they're too useful).
print(args) => (print args)
if (cond) { exp1} else {exp2 } => (if (cond) (exp1) (exp2))
array[index] => (index array)
And so on. On average there is probably the same amount, but visually it kind of appears to be more. if (a && !b) {expr} => (if (and (a) (not b)) (expr))
That's a simple expression and I'm not even sure I managed to match the () correctly--
I installed Parinfer it on my project: CLJSFiddle[2], and it's not what I'm used to and doesn't have everything but for a novice it is highly usable - check it out!
[1] https://shaunlebron.github.io/parinfer/ [2] http://cljsfiddle.com
(when (and a (not b))
expr
...
expr) if (a && !b) { expr } => (if (and a (not b)) expr)
Or if (a() && !b) { expr() } => (if (and (a) (not b)) (expr))
Now the amount of parentheses is either 4/6 or 8/10 which is not a very big difference (and caused only by the syntactic sugar for `not`), especially considering that the left example uses `!`, `&&` and two kinds of parentheses (perhaps a `;` as well).My point was more about the two extra parentheses for each && or ||. Imagine converting
if (a && b && c && d || e)
to a LISP. if (a && b && c && d || e) {
doSomething();
}
becomes (if (or (and a b c d) e)
(doSomething))
It's just a little more explicit because operator associativity and precedence aren't worked out for you. (For better or for worse.)EDIT: I think you were imagining something like this:
(if (or (and a (and b (and c (and d)))) e)
(doSomething))
..which is definitely not the art. (if (or (and foobar
(some-predicate-p quux)
(= qwerty 432))
(a-long-function-call-with-two-args bar ytr))
...)
Compared to something like: if (foobar &&
somePredicate(quux) &&
(qwerty == 432) ||
aLongFunctionCallWithTwoArgs(bar, ytr)) {
...
}I know that at the end there's no distinction (lisp syntax is AST, and every other source code ends up as AST), and the idea that someone would accept syntax restrictions as a good thing is probably strange to the true lisper, but people care about the syntax. By eliminating syntax and forcing the programmer to write AST directly lisp gave enormous power to the user, but at the end not enough programmers were willing to give up on comfort to acquire all that power.
})
})
});
...trailing almost every function definition is equally absurd. At least with Lisp I only have to deal with one delimiter type and just count the number instead of the })})}); mess of javascript and some other languages.Although I do have to say I think Python got it right in trying to do away with as much of it as feasible.
I think the reason people find it hard to adjust to the new pattern is that they have been trained all their lives to recognise the pattern where the name of something is distinct and outside of the thing itself. In mathematics obviously people have been used to expressing functions using f(x) since an early age (10?). But physically also. Think of these things:
- The name of a shop or a village tends to be on a sign on the outside.
- The name of a book is on the cover.
- The title of a folder is on the outside.
- The name of an OS file etc.
Naming things is very important. Its the basis of a lot of communication, understanding and abstraction and people are used to the name of something being fundamentally distinct and outside of the thing itself.
In general, the reliance of syntax on random punctuation instead of meaningful symbols (a'la APL) or clear, human-parsable text is maddening - LISP and its derivatives do it far less than other modern languages, but still too much for my taste.
I'd also prefer clearly spelled-out syntax in this age of autocomplete rather than a jumble of weird abbreviations while I'm wishing for unicorns, and languages with code editors that do a good job of visually representing the flow of code rather than rely on punctuation, white-space or some unholy combination.
Programming seems kinda primitive compared to other aspects of computer-enabled content creation at times.
Lisp doesn't really have a syntax. If you want something which looks more like Python or C, you can either rewrite read, or use read macros. This has been attempted several times (Lisp 2, CGOL, Dylan), but in the end, the parentheses won.
I also read "Successful Lisp" briefly.
Hoping to give constructive feedback and not to start a flame, here are some opinions/impressions.
Please note that :
1. These are mostly impressions, not complete opinions.
2. I am only talking about Common Lisp. I am not talking about Scheme, Clojure or other stuff.
Ok then:
1) Lisp feels... Messy. Expecially when you're learning it.
2) Maybe... Too many implementations? This is not a problem per se, but sometime it happens that not all the implementation implement all of the features, or implement them differently enough that you have to write the same code for different interpreters.
Example: https://github.com/stumpwm/stumpwm/blob/master/make-image.li... see the various #+clisp, #+sbcl and stuff.
3) Batteries not included. The ANSI standard defines the language and pretty much nothing more. Yeah you have libraries and quicklisp, but it feels fragile and quite "opaque". Also, I always used SBCL and I am not 100% sure all the libraries work with all of the interpreters/compilers.
I feel it would be nice to have a new standard that defines a set of additional API that implementors should implement.
Ideally, it would be nice to have common lisp to be "batteries included" like Python. But please let's avoid the Scheme SRFI mess.
Consider this: it is a fact that MIT introductory programming class switched from Scheme to Python because among other things, students were spending too much time reading libraries manuals and implementation references instead of writing code.
5) The Common Lisp HyperSpec. Let's say I find it to have a poor usability.
4) Stuff is generally poorly documented, and fragmented. While things like quicklisp work, it's visible that those are basically one-man projects. This isn't reassuring.
5) Given the heritage, there is not such thing as a "Lisp community" and the lack of governance is quite evident in the sense of lack of direction and lack of uniformity in pretty much everything.
6) Emacs. "It's lisp!". Well actually emacs lisp and common lisp are different languages, and differences will come up.
Emacs is my editor of choice, but I' starting to grow worried about it.
These are some of the reasons why I decided not to "invest" in Common Lisp.
Nope, the decision was entirely political, driven by panic when post-dot.com crash enrollment dropped by more than half after being a steady 40% of the undergraduates for decades.
And I believe your specifics are entirely wrong, but I don't know exactly how the course evolved, e.g. they threw in a couple of weeks of OO that isn't in SICP as that became popular. But at least in the beginning of the course, it's all self-contained in the book.
Much of the rest of what you say is spot on, and are some of the reasons I gave up on mainline/Common Lisp 30+ years ago.
DSLs are an ultimate solution. And DSLs are best implemented with macros.
> where they don't make the code harder to understand
Macros are there to make code easier to understand.
I just explained it in detail elsewhere: https://news.ycombinator.com/item?id=11705170
I cannot imagine not using macros for pretty much everything I do.
Maybe I just need to start slinging more lisp, but I don't see how they're particularly different from functions.
Macro is a function that is executed in compile time. Think of it as a way of extending your compiler with new functionality. For example, your language does not have support for pattern matching originally. It's not a problem - you can define a macro that adds such a functionality to a language. You cannot do it with functions - it must be compiled and optimised before execution, and functions cannot introduce new identifiers into a scope.
Another simple example would be something like list comprehensions - also introducing variables and also requiring optimisations in compile time.
And at extreme level, macros can be used to turn your host language into something totally different. Not just add few new features, but turn it into another language. Lisp can become an ML or Haskell, or Prolog, or Java, or whatever else you can imagine. Macros can change syntax, can implement new semantics. None of it can be done with just functions.
I always assumed that were dressed up for the occasion and not really real; like someone doing a role playing game or maybe being Santa for the kids during Christmas.
But seriously; if you meet people who really understand programming language design they will have full understanding for other ways of designing a language than whatever is their specialty. After all there is a reason why PL is still a developing field.
This assumes that weavie actually has a beard.
Most programmers in the world don't have to invent/implement a radical new idea in five minutes.
This means (in principle) that there is no guarantee that a Lisp compile will ever terminate.
I don't know why the OP is been down voted. Lisp is not for everyone and every application; I think that is a perfectly reasonable point of view.
I don't think that failure to terminate compiles is too big a problem in practice; treat it as just another form of compile-time error and fix your program.
Common Lisp does have strong and optional static typing. It does have namespaces.
As for CL having `eval` - well, this is true at least. But I fail to see how in the world could that be a bad thing...
Lisp eval is not the same as other languages, you use it implicitly all the time. Any occurence of macros or something like `(1 ,(+ 2 3) 4) is playing around with evaluation behind the scenes.
What is important is an incremental compilation, one top level statement at a time.
I only did it a couple of times.
What do you mean with “well defined namespace”?
Common Lisp of course has defined name spaces. They are called packages.
Yes it has eval, but I have not seen a Lisp program in years which used it. And it would be easy to lint programs so it is not used, if you had a reason to be afraid of it.
One of the most important use cases for static typing is safe modification of existing code, like during refactoring. So a typical example: I have some struct with a field of one type, say a string. I use it in a few functions, reading and writing it. Now I change the field to a different type, say an array of strings instead. I want the compiler to tell me about all the code that's now invalid. How do I do that?
I quickly wrote up a simple example of the (statically) untyped code I mentioned: http://pastebin.com/f7KWETvT (I can also write a matching example in some common static language (like C or ML), but I think it's clear what I mean.)
What type annotations do I add to make that basic use case work? So what do I add such that it first type-checks when name is just a string, then change the annotation to make name an array of strings, and get two compile-time errors because the functions are now wrong.
(Unfortunately, I only know a little bit of CL (and even then mostly due to familiarity with Emacs Lisp), so my example might be a bit un-idiomatic. Feel free to turn it into idiomatic CL if something is too weird!)
(defstruct person (age 0 :type fixnum) (name "" :type string))
Now every access to that person structure should be checked by the compiler. You can create a person like:
(setf p (make-person :age 7 :name "fred")) => #S(PERSON :AGE 7 :NAME "fred")
But not with: (setf p (make-person :age 7 :name 8))
which causes SBCL to error with: The value 8 is not of type STRING. [Condition of type TYPE-ERROR]
Equally, if you tried wrongly to define
(defun print-hello (person) (let ((name (string-upcase (person-age person)))) (format t "Hallo ~a!~%" name)))
You get at least a warning you should heed: ; caught WARNING: ; Asserted type (OR (VECTOR CHARACTER) (VECTOR NIL) BASE-STRING SYMBOL CHARACTER) ; conflicts with derived type (VALUES FIXNUM &OPTIONAL).
So, for refactoring, you would change the type signatures of your struct and then recompile all code to see whether the compiler generates errors/warnings.