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 17:19

Racket is a shit alternative. There is no good programming language.

Name: Anonymous 2013-04-07 17:26

>>2
QFT.

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.

Name: Anonymous 2013-04-07 18:59

Chicken Scheme

Name: Anonymous 2013-04-07 20:23

julia [julialang.org] learnt homoiconity from lisp, employs the power of llvm and is named after a woman.

Name: Anonymous 2013-04-07 20:31

This is unfortunate. SBCL does it though.
OH SHIT!!!!!!!
This changes everything. I thought no implementation of Common Lisp had GC TCO.

Name: Anonymous 2013-04-07 20:35

jewlia

Name: Anonymous 2013-04-07 20:37

>>5
Ah, that one scheme ~10 people use!

>>6
Might be worth keeping an eye on if it ever takes off.

Name: Anonymous 2013-04-07 20:38

>>7
My thoughts exactly. And SBCL is the only good implementation anyway, so portability isn't so much of an issue.

Name: Anonymous 2013-04-07 21:09

u guys r ab unch of fuckin nerds

Name: Anonymous 2013-04-07 22:26

I don't see much of that as pertaining to Clojure, save for perhaps for:

* point 2, which I agree, I would much prefer to have boolean values and typed predicates than truthiness and falsity to everything, and

* point 4, which both castigates Lisp languages for not adhering to a straight forward formulation in the lambda calculus (SICP-style objects are absurdly ad-hoc and give no type information), and not being full-featured enough. Not that the defrecord and deftype/reify/protocol/proxy split isn't bad (though proxies and protocols aren't that much for the benefit of writing Clojure code for its own sake).

* point 7, vector arithmetic? Are you kidding me? These are libraries in any but a few specialized languages.

Continuations are known not to cover all forms of control flow, most notably exceptions in their full extent. This has been explained by some researchers, but I'm afraid I'll have to take them at face value; the most in-depth article uses category theory and linear logic to present the issue, and fuck, I'd need like two weeks to understand their work superficially: www.cs.bham.ac.uk/~hxt/research/exncontjournal.pdf

Name: Anonymous 2013-04-07 23:36

>>12
Continuations are known not to cover all forms of control flow,
That statement is not precise without a definition of control flow. Brainfuck is turing complete, so you can't talk about what one can do that the other can't.

most notably exceptions in their full extent.
This is mostly people complaining that you can't push a continuation onto a mutable stack of handlers without having state or dynamic scope. Exceptions are a mechanism that is tied to dynamic scope. So this is really, you can do things with dynamic scope that you ``can't'' do with static scope. Well, dynamic scope is shit, so there!

Name: Anonymous 2013-04-08 1:07

>>1
>1
Agreed. CONS cells are nice and simple, but implementing something like a binary skew tree isn't hard and it has O(1) head and tail operations, and O(logn) insert and delete operations.
>2 
NIL punning isn't very disciplined, but it's very convenient.  Nil in hash tables is inconvenient but not that bad, the default argument is there for a reason.
>3
If you don't like format, use CL-INTERPOL
If you don't like loop, use Iterate (or dotimes, or dolist, or do)
>4
>Despite CLOS generics looking like functions and cluttering global scope, you can't pass them to MAP or other high-order functions.

That's bullshit and you know it

(defclass num () ((val :initarg :val :accessor val)))
(defun make-num (v) (make-instance 'num :val v))

(defmethod plus ((num num) &rest nums)
    (make-num (reduce #'+ nums :key #'val :initial-value (val num))))

(mapcar #'val
    (mapcar #'plus (list (make-num 1) (make-num 2))
                   (list (make-num 12) (make-num 23))))

=> (13 25)

>6
Yeah, long names like destructuring-bind, multiple-value-bind, make-hash-table, and make-array suck, but that's what macros are for.  Common lisp leaves out special syntax simply because it's so easy to add your own. Also, check out the bind library.

Name: Anonymous 2013-04-08 1:08

>>14
CL-INTERPOL
No thanks, I don't want any government backdoors in my code.

Name: Anonymous 2013-04-08 1:12

Name: Anonymous 2013-04-08 1:14

Name: Anonymous 2013-04-08 1:15

>>15
Might as well check out bind while you're at it

http://common-lisp.net/project/metabang-bind/

lots of parentheses, but whatever. that's what emacs is for.

Name: Anonymous 2013-04-08 1:16

Name: Anonymous 2013-04-08 2:48

Name: Anonymous 2013-04-08 2:55

>>20
Samuel Hadida
Jason Isaacs
Milla Jovovich

Jews.

Paul W. S. Anderson
likely 1/2 Jewish

Name: Anonymous 2013-04-08 2:57

>>4
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.
It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. -- Alan Perlis


I.e. you want your other data structures to work as lists, so you can reuse 100 functions already present.

Name: Anonymous 2013-04-08 3:04

>>4
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.
Just get rid of dynamic scope (which isn't the right thing anyway), then all global variables would be in lexical scope, which could be optimized even better than C/C++ program.

Name: Anonymous 2013-04-08 4:07

>>12
(SICP-style objects are absurdly ad-hoc and give no type information
They can give type information, if every such object provides `type` method. And they can provide inheritance, if you pass all unknown messages to parent object. You can also discern between SICP objects and normal functions, either by hashing their addresses or by labels (SBCL provides SB-INT:NAMED-LAMBDA to attach type to an object)

Name: Anonymous 2013-04-08 4:16

>>12
point 7, vector arithmetic? Are you kidding me? These are libraries in any but a few specialized languages.
You can't overload + - *

Name: Anonymous 2013-04-08 4:27

>>14
Oopsie... "VAL already names an ordinary function or a macro."

encapsulation at it's finest!

Name: Anonymous 2013-04-08 20:08

>>26
I didn't get that when I wrote it. Which expression causes the error?

Either way, my point still stands, generic functions can be passed around as objects.

Name: Anonymous 2013-04-08 22:35

>>25

You can make your own package that defines + - * as generic functions, and uses the standard + - * functions on type number. The downside is generic functions don't take variable length arguments.

Name: Anonymous 2013-04-08 22:43

>>28

Actually, they do, but you can't specify the class of the rest of the arguments. (though you can declare it to be a list of whatever type)

from the example above

(defmethod plus ((num num) &rest nums)
    (make-num (reduce #'+ nums :key #'val :initial-value (val num))))

Name: Anonymous 2013-04-08 22:44

>>29
And of course, other version of the generic function have to take &rest parameters as well, but that's not a big deal.

Name: Anonymous 2013-04-08 22:47

>>30
>>29

nevermind i'm stupid

Name: Anonymous 2013-04-08 23:30

>>31
of course you are, you're merely a dumb goy.

Name: Anonymous 2013-04-09 1:19

>>29
Wow, that works surprisingly well.

>>32
my dubs command you to improve the quality of your contributions.

Name: Anonymous 2013-04-09 5:05

>>27
I didn't get that when I wrote it.
Exactly my point! CLOS cannot into context-free code.

>>28
That is a lot of inconvenience for a newbie, wanting to do just a few OpenGL calls.

Name: Anonymous 2013-04-09 5:12

>>1
The Indian is improper.

Name: Anonymous 2013-04-09 5:15

Real criticism of Lisp would be that it is stuck in the past. Lispers are still looking down on C; they never try to look up. If they did, they would see languages like ML and Haskell have surpassed them in all respects. Heck, even Matlab and its descendants with the "everything is a matrix" philosophy have created a powerful new paradigm that is certainly not a special case of "everything is a list". In short, the world has flown by Lisp, and Lispers (at least the vocal ones on blogs) still did not notice.

Name: Anonymous 2013-04-09 6:28

It's important to realize that Common Lisp wasn't so much designed as it was agreed upon. That is, the whole drive of Common Lisp was DARPA basically telling the Lisp community to stop proliferating numerous Lisp dialects and to come together and agree on a standard that could be used by DARPA for all future Lisp projects. As a result, Common Lisp just standardized bits and pieces of multiple Lisp dialects and makes numerous compromises based on the implementations that were in existence at the time (nobody want to completely reimplement everything). Yes, there were some other very interesting pieces of new work, such as CLOS, but much of the standard simply put a stamp on existing practice from one or more different dialects. In other words, in the same way that a camel is a horse designed by a committee, Common Lisp is a Lisp designed by committee. Even worse, there was a specific goal of trying to keep compatibility with as much code as possible. This meant that even when warts were obvious, there was great incentive to avoid fixing them unless it was absolutely necessary to do so. From talks with people involved with the process, my sense is that the whole thing was so painful that nobody has had the emotional energy to do any more standards work on Common Lisp since, and that's one of the reasons that CL has been so static.

Name: Anonymous 2013-04-09 8:13

If it ain't Lisp, it's crap.

Name: Anonymous 2013-04-09 11:29

If it ain't Scheme, it's shit.
Common LISP is crap.

Name: Anonymous 2013-04-10 4:29

>>36
c/c++ is just as stuck in the past as lisp and look where it is. And there's a reason for it. Change a language, and all code that it is written in needs as rewrite. Only pythonistas think that is sane.

>>37
So was See and Seeples. If you're going to be critical of a language, try talking about the language itself instead of it's origin. And besides, more than enough abominations have come from individuals as well as committees.

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