Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon. Entire thread

Lisp Considered Harmful

Name: Anonymous 2013-04-07 16:35

Some of this criticism is related to Common Lisp and was fixed in Scheme or Racket. So it is mostly about why you should prefer Racket to Common Lisp or Clojure.

1. CONS-pairs simplicity is deceiving: CONS introduces mutability, together with silly notions of proper and improper lists, while impeding advanced and optimized list representations, so you can't have lists with O(log2(N)) random access, catenation and insertion, like Haskell's finger trees.
2. NIL punning (treatment of NIL of as empty list, false and void) breaks strong-typing and goes against lambda calculus, which hints us that IF should accept only TRUE and FALSE, which should be functions (Church Booleans), so all objects must be explicitly coerced to boolean for use with IF. Common Lisp has a confusing lot of NIL/CONS related predicates: there is CONSP, LISTP, ATOM, ENDP and NULL, while TYPECASE discerns between LIST, CONS, BOOLEAN and NULL. The ATOM predicate considers NIL an atom, so if you have some code that should be invoked on every list, you can miss some empty lists. Hash-table access returns NIL, when key isn't present, making it harder to store empty lists. Some Lisps, half-broken behavior: for example, Clojure, discerning between nil and empty-list, still allows using nil in-place of empty-list; moreover, Clojure introduces true and false, duplicating nil and confusing semantics further. NIL punning is the single worst Lisp wart, reminiscent of PHP and JavaScript horrors.
3. Non-Lispy behavior: FORMAT function competes with regular expressions in providing cryptic and unreadable DSL, indulging obfuscated code like "~{~#[<empty>~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~:}", which could have been easily replaced with SEXP based format. On the other hand, Lisp misses string construction, like "Name=$name, Age=$age", so you have to carry FORMAT everywhere. LOOP macro employs overly complex Pascal-like baroque syntax with a lot of clauses, having complex interplay with each other, which would be confuse newbies and annoy Lispers, who love simpler solutions, playing nicely with lambda calculus, like letrec.
4. Over-engineered OOP: despite Lambda Calculus prescribing using lambdas to construct objects, most Lisps implement full blown class-based OOP, like CLOS, conflicting with Lisp's minimalism and complicating semantics. Moreover, lists/conses and booleans don't have first class citizen rights in these ad-hoc object systems, meaning you can't overload CAR or CDR, so you can't implement your own lists, like, for example, a list-like interface to filesystem, where a directory could behave as a list of files and every file as a list of bytes. Despite CLOS generics looking like functions and cluttering global scope, you can't pass them to MAP or other high-order functions. Package symbols have dynamic scope, which is argumentably bad and shouldn't be indulged, while packages are global and first-class, so you can't easily get a sandboxed environment (like Unix's chroot) by making a package with only safe functions. Instead of providing viable encapsulation, some Lisps treat the symptoms by introducing second namespace for variables, so that identifiers would have less chances to collide. Other Lisps, like Clojure, disapprove OOP, using some ad-hoc package systems and kludges, like "protocols".
5. Duplication is not the right thing: CONS duplicate arrays and lists; QUOTE duplicates QUASIQUOTE, which for some reason implemented as reader macro, so you can't overload it for you own use; LOOP duplicates DO; symbols duplicate strings; chars duplicate single-letter symbols. Package encapsulation duplicates OOP, which in turn duplicates encapsulation features provided by lexical scope. Growing from broken OOP encapsulation, there is an explosion of comparison functions, just to compare for equality we have: =, char=, string=, eq, eql, equal, equalp, tree-equal, string-equal, char-equal; worser, `eq` compares pointers and has undefined behavior, so it should be part FFI, instead of being exposed with normal interface. Analogously, AREF, SVREF, ELT, NTH, CHAR, SCHAR, BIT, SBIT - all do exactly the same.
6. Verbosity: define and lambda are most used keyword, yet take 6 character each; then we have monstrosities like destructuring-bind and remove-if-not, which could be named just bind and keep. Verbosities like MAKE-HASH-TABLE, MAKE-ARRAY and (DECLARE (INTEGER X)) ensure that you will avoid optimized structures and type-checking at all cost, making your code slower. In rare cases, when names ain't verbose, they are just cryptic, like PSETF, CDAADR and REPLACA. Macros LET and COND have especially bloated syntax, where simpler (let name value …) would have been enough, LET adds additional 2 levels of parentheses (let ((name value)) …) just to annoy you. CLOS and defstruct syntaxes are especially verbose, which is aggravated by absence of self/this context. Many people complain about absence of infix expressions, which could have been easily supported through reader macro, like {a*b+c}, and while Scheme does support infix expressions, it misses operator precedence, making it incompatible with formulas you may copy-paste from your math textbook. LISP lefts unused a lot of special characters, which otherwise would made code succinct and provided visual cues: for example, you have to write (list 1 2 3), instead of [1 2 3].
7. Missing features: While Lisp does support complex and rational numbers, it doesn't support vector arithmetic, so you have to write something like (map 'vector (vector x1 y1 z1) (vector x2 y2 z2)), making any 3d graphics exceedingly verbose. A few important functions, like SPLIT and JOIN, are missing from standard library, which for some reason includes rarely used string-trim and string-right-trim, although JOIN usually simulated with  (format nil "~{~a~^-~}" '("a" "b" "c")), making code impenetrably cryptic. Absence of good type system, call-by-name (lazy evaluation) and immutability, which really makes functional programming shine. Although, Qi does provide acceptable type system and Clojure introduces immutability, we can't have all this in a single production quality Lisp. Call-by-name is more of an on-demand-feature to be used in some contexts (like implementing if/then/else special-form), but no Lisp features it, despite call-by-name being natural semantics of Lambda Calculus. Some popular Lisps, like Clojure and Common Lisp, don't even guarantee TCO (tail call optimization), meaning that expressing advanced control structures would be hard. No Lisp, beside Scheme, supports continuation - silver bullet control-flow feature, although some Lisps do support goto - limited form of continuations. "It needs to be said very firmly that LISP is not a functional language at all. My suspicion is that the success of Lisp set back the development of a properly functional style of programming by at least ten years." -- David Turner.

Name: Anonymous 2013-04-07 18:58

>>1
1. CONS-pairs simplicity is deceiving: ...
Lisp exposes too much of the cons interface for the compiler to abstract it's implementation, but you can still use other data structures for other purposes. But you need to use these data structures explicitly.

2. NIL punning (treatment of NIL of as empty list, false and void) ...
To be fair, C does this too, with NULL being false, and it was probably inspired from assembly language. It does create issues with recursive decent into trees. With C you don't have this problem because you can't express recursive decent of your data types without also being given the opportunity to handle certain fields as false or NULL.

3. Non-Lispy behavior: FORMAT function competes with regular expressions ...
FORMAT is ugly, but it can get small jobs done. The dollar syntax within quotes is implementable using a macro.

4. Over-engineered OOP...
While CLOS is complex, it's good to have all the tools that you would need to use. Lexically scoped classes in other languages are nice, but they also have paths to abuse, like classes within classes within classes and you get files over 7000 lines long. Or programmers who don't know how to use the feature properly, so rather than finding a way to share code between lexically scoped classes, they copy and paste the lexically scoped inner class to other classes, which is HORRIBLE. I think both packages and the way clos is done is comparable to C++.

6. Verbosity: ...
The longer names and verbose syntax can be shortened by a library of macros and functions. I haven't done this myself yet, but I think you can implement special syntax for the other bracket characters using reader macros.

7. Missing features: While Lisp does support complex and rational numbers, it doesn't support vector arithmetic...
Easy enough to define and overload with clos.

SPLIT and JOIN, are missing from standard library, ...
Kind of a bummer, but also easy enough to implement and modularize with clos.

Absence of good type system
Assertions can be used to give hints to the compiler for performance, but compile time static type checking is harder to pull off. I bet it is possible though, with macros that manipulate a shared global variable, but it wouldn't be easy. And then once you have the static type checking in place, it would be a waste if there wasn't a way to communicate the information to the compiler for better performance, although in lisp there is. So I guess it is possible.

call-by-name (lazy evaluation)...
Delay and force are easy to implement. Compiler awareness is harder though.

immutability, which really makes functional programming shine.
You can have it by restricting yourself to functional practice, but you need tail call elimination to really do it properly.

we can't have all this in a single production quality Lisp.
Fuck Clojure. Just write and use libraries within ANSI Common LISP.

Call-by-name...
Isn't that eval?

don't even guarantee TCO (tail call optimization)
This is unfortunate. SBCL does it though.

No Lisp, beside Scheme, supports continuation - silver bullet control-flow feature
Continuations can do more than you would really need. In order to keep efficient code, compiler needs to do global whole program optimization to determine when support for continuation isn't needed locally, which increases compilation time.

"It needs to be said very firmly that LISP is not a functional language at all. My suspicion is that the success of Lisp set back the development of a properly functional style of programming by at least ten years." -- David Turner.
Yes. But if scheme could actually do things it would have taken use as well.

Newer Posts
Don't change these.
Name: Email:
Entire Thread Thread List