62 comments

[ 1.9 ms ] story [ 136 ms ] thread
I won't go into details about deficiencies of Lisp. That's another problem to be discussed; an extremely painful problem, in fact.

I don't buy it. Nobody wants to talk about these apparently obvious (to non Lispers) problems of Lisp, yet they are still there? Bring them up and be argued with or be quiet.

Later he talks about the ugliness of Lisp syntax, especially CL, but Scheme and Clojure are supposed to be better. On what basis? I may admit scheme is elegant, but calling clojure's syntax an improvement over CL is a pretty long jump.

> Later he talks about the ugliness of Lisp syntax, especially CL, but Scheme and Clojure are supposed to be better. On what basis? I may admit scheme is elegant, but calling clojure's syntax an improvement over CL is a pretty long jump.

Having syntax for 4 different data structures instead of just one would be one of my main reasons. Also, the syntax of a lisp-1 is cleaner.

While I like scheme and clojure, why do you think lisp-1's are cleaner? Perhaps it was just my order of learning, but I rather liked the different namespaces CL provided, primarily as it eliminated name collisions between variables and functions. If list were an appropriate variable name, I could call a thing list versus the abbreviated form lst in scheme (that's a trivial example, but I recall more name collisions back when I was using scheme and CL as hobby languages).

I guess, syntax-wise, the main difference is no longer needing funcall, which was a headache when I was learning CL.

Because what if there where 6 equally important datastructures, or 100? The only datastructure you need to express when you are programming is the AST, in my humble opinion, and lists serve it well, visual trees might be interesting.

The Lisp-2 design has a very good practical reason: 50% of a common type of mistake are impossible to make. More namespaces don't hurt a language, while less namespaces usually do (Javascript for instance).

I think many people that don't like lisp haven't spent much time with it. I personally do not like lisp. I took an artificial intelligence class where we wrote search techniques in common lisp. One problem that I had with lisp is the end of my A* search had a massive number of parenthesis at the end. I think this would have been a little better if we were taught about common lisp structs earlier on. My next big problem was that I was coming from C where you occasionally put extra parenthesis in to group portions of code (sometimes necessary in macros or if statements). This is a very bad habit in lisp because it tries to call a function.
There've been dialects of Lisp where a single ] would close all outstanding open parentheses. Having never actually used one, I reserve judgment on whether this is a good or bad idea.
I think, just like any other syntactic sugar, it's fine if used with proper understanding of possible consequences. I'd like to see lisp use indention instead of parens like Python. I'm sure someone's done it and it isn't popular for some reason or another, but I think getting rid of the parens would go a long way to making people stop hating LISP.
I don't see how lisp could get rid of parends with indentation. Parends are used for function calls so you would then have to indent one extra level to call a function? If there was a workaround for the function calls, then it would also require every function to be written on a separate line, there would be no concept of a one liner.
Indeed. A workaround would be to introduce some infix operators (there are various lisps that do so), so you could use something like '$' for function application and end up with a syntax like Haskell.

Of course, this is a terrible idea and very un-lispy, because one of the key benefits of lisp is that it's simple to parse. Introducing infix operators destroys that simplicity.

Another workaround, which I quite like, is to simply remove all the "lisp syntax" entirely, and edit a tree directly using a syntax directed editor.

Building a tree with indentation is easy. You would need some method to distinguish between a one element list and a value, and some method of specifying an empty list (though you could just write nil). One option that leaves everything completely uniform is to use something that says "this starts a list", and then have the (zero or more) elements indented including the list head if present. If we use (), this would read like:

    ()
      prin1
      ()
        *
        ()
          +
          3
          4
        7

Exceedingly vertical, obviously, if you don't allow inline parens at all.

If you were, you could compress any run of lines by simply moving everything with no children into the parent parens. Applying that only to the deepest bit of the above, we'd get:

    ()
      prin1
      ()
        *
        (+ 3 4)
        7


Of course, the original could be transformed into actual lisp by simply moving end-parens:

    (
      prin1
      (
        *
        (
          +
          3
          4
        )
        7
      )
    )
I think it just became irrelevant at some point. Every decent Lisp editor since the 80's will automatically match parens for you, so I don't even notice them anymore, unless I specifically need to do something special with them.

Then when you add in other little features that you can do in emacs, like fading the parens close to background colour when you're not working on them, highlighting when you are, etc... It all adds up to make the old "parenthesis hell" a bit nonsense.

I sometimes imagine that there must've been a stage when universities forced people to write Lisp in Notepad or something, or by hand maybe. I can't imagine how much that would drive me insane!

Consider that most people writing Lisp or Scheme for schools are probably not using great editors. When I took a class with Scheme, the recommended editor was GEdit or Notepad++. It wasn't until I switched to Kate that I had colored parens.
That's quite a scary thought. It's like trying to program with one hand tied behind your back and only your little finger to poke out the keys...
This is simply the ignorance of academics. What they SHOULD be recommending for these languages is Vim or EMACS with EVIL (got to keep sane keybindings somehow).

The problem is that many programmers and academics today are unwilling to take the time to learn the right tools for the job.

both gedit and notepad++ have plugins for colored parens.
I've enjoyed coming up with a Lisp-ish toy language and implementing an interpreter for it. I haven't yet used any kind of Lisp for a real project (and may never), but getting a grip on its concepts has, and continues to be, interesting.
I'd really like to learn to implement a basic lisp in assembly. Do you know of any resources for doing this?
I can imagine people not liking lisp if they've used a dumb text editor to try writing it, but if using something a bit more intelligent like ParEdit+emacs, the closing parens become a non-issue, they're added for you, and you're given a nice set of commands to navigate around s-expressions.
There is a point beyond which the syntax of a language becomes unforgivable, even if you have an IDE to make it more palatable.

Java, for example, is completely awful to write (I find) without the use of Eclipse or some similar tool. C and Ruby, by contrast, I'm happy to hack on in Vim.

> but calling clojure's syntax an improvement over CL is a pretty long jump.

Well, it has all of _three_ types of brackets, which may be reassuring to the C++ programmer.

It seems as if every new iteration of C++ proves Greenspun more and more correct. The standards committee has been trying very hard to push templates as an "ad hoc, informally-specified, bug-ridden, slow implementation" of macros.

As for Lisp syntax being ugly, I beg to differ. (Common) Lisp syntax is somewhat arcane, with terminology and abbreviations that aren't common these days, but it reads pretty smoothly.

You know what's ugly? C++ lambda syntax:

    [=]() mutable throw() -> int
At some point C++ started following Perl down the road of using as many symbols above the number keys as possible. That's ugly.
You are being intentionally dishonest by using the most verbose lambda expression you can think of. It would be very unusual to write something like that. You even managed to include one of the rare deprecated language features.
The example is from the first hit for a Google search for "C++ lambda syntax" (an MSDN article). In any case, unusual language features still are part of the syntax. They must be parsed and they must be understood by programmers in case they come across them. The ugly parts of a language don't go away just because you don't use them that often.

Also, I'm not sure what you're objecting to. The throw clause? The ugly part here is the overloading of the brackets and equal sign to specify the capture list.

"[=]" Capture all variables by value, fair enough.

"mutable" Don't capture them const, this is very rare.

"throw()" Promise not to throw an exception, this is very rare for a lambda, and also deprecated in favor of noexcept.

"-> int" Explicit return type, usually not needed, and nearly never needed in C++14.

I was objecting to a contrived example used out of context to make C++'s syntax look worse than it is.

I'm not trying to make C++'s syntax "look bad." The standard itself makes C++'s syntax look bad. All those features, except maybe the empty throw clause, are basic things someone needs to know to use lambdas properly and to read code that uses lambdas. We're not talking about deprecated, never-used, features like "export" here.

I don't understand your line of reasoning here. Is syntax not ugly if you usually don't use the ugly parts? I think everyone would agree that the parser ambiguity that used to exist between "vector<vector<int>>" and the right-shift operator ">>" was ugliness in the syntax. Is it a defense that you don't usually instantiate a template with an instantiation of another template?

(comment deleted)
(comment deleted)
None of these features, except the capture clause, are things that anyone has to learn to use C++ lambdas; they should know these other features anyway, from other parts of the language.

The late return type syntax (`-> type`) works with nearly all functions (`auto fn(foo) -> bar { ... }`), the `throw()` (now `noexcept`) specification has been used for long time, and `mutable` is a well-known syntax for potentially const objects; so the lambda syntax is, or at least should be, unsurprising.

To say it's ugly is not saying much. We all know C++ is ugly. This is a given. But singling out lambda syntax, which is comparatively easy to grasp and largely consistent with the language, doesn't really make a case.

(comment deleted)
There is, of course, a reason why symbols are used (and keywords are re-used) instead introducing new keywords: backwards compatibility. Whenever a new keyword is introduced, there's a very real chance of breaking old programs and that is considered bad.

What are the bugs you speak of?

> What are the bugs you speak of?

It's part of the quote. I'm not sure if there are bugs with respect to lambdas, but historically, C++ implementations have been pretty buggy. Even with all the resources that go into C++ implementations, it took forever to get ones that correctly supported C++98.

That has changed a great deal over the last 10 years. Modern C++ implementations are quite stable, and the push for and uptake of C++11 support has been quite rapid. Most of the new standard was implemented before the final draft was even published.
Most of the time your lambda looks like

[](arg1, arg2){ short line of code};

which isn't that verbose at all.

"In fact, many people claim that Lisp "has no syntax". This really means that Lisp has uniform syntax: every piece of code is just a list delimited with parentheses."

For an appropriate definition of "uniform syntax", perhaps. What I mean when I say "lisp has no syntax" is that you effectively write out the AST. If the syntax were somehow uniform but had a radically different structure than the AST I would claim that Lisp has syntax.

> What I mean when I say "lisp has no syntax" is that you effectively write out the AST

Agreed. This is a very common misunderstanding of the statement, but understanding the meaning of the statement correctly demonstrates the single reason that Lisp is so powerful: its homoiconicity, and everything that implies.

Racket [http://www.racket-lang.org] is a modern Lisp with STATIC TYPING.

Quote from the static typing example on home page:

Racket's type system is designed to let you add types after you've worked for a while in untyped mode — even if your untyped program wouldn't fit nicely in a conventional type system.

#lang typed/racket

;; Using higher-order occurrence typing

(define-type SrN (U String Number))

(: tog ((Listof SrN) -> String))

(define (tog l)

  (apply string-append (filter string? l)))
(tog (list 5 "hello " 1/2 "world" (sqrt -1)))

To run the example, install Racket, start DrRacket, paste the example program into the top area in DrRacket, and click the Run button. Alternatively, save the program to a file and run racket on the file.

Hell, you don't even need to use typed/racket to implement static types. You can just use contracts with plain old Racket to achieve true lispyness...because contracts let you impose static type-checking selectively within your code and for staticly-typed code to symbiose with dynamically-typed code.

Racket Guide on contracts: http://docs.racket-lang.org/guide/contracts.html

Racket Reference on contracts: http://docs.racket-lang.org/reference/contracts.html

There's even this groovy academic paper: http://www.eecs.northwestern.edu/~robby/pubs/papers/oopsla20...

It almost goes without saying that contracts are the mechanism by which typed/racket is statically typed.

Common Lisp is a modern Lisp with static typing, too. For example, here's compile-time type checking performed by the SBCL compiler:

  * (defun test (x)
      (declare (fixnum x))
      (the string x))
  ; in: DEFUN TEST
  ;     (THE STRING X)
  ;
  ; caught WARNING:
  ;   Derived type of X is
  ;     (VALUES FIXNUM &OPTIONAL),
  ;   conflicting with its asserted type
  ;     STRING.
  ;   See also:
  ;     The SBCL Manual, Node "Handling of Types"
  ;
  ; compilation unit finished
  ;   caught 1 WARNING condition
  STYLE-WARNING: redefining COMMON-LISP-USER::TEST in DEFUN
  
  TEST
  * (test 1)
  
  debugger invoked on a SIMPLE-TYPE-ERROR:
    Value of X in (THE STRING X) is 1, not a STRING.
  
  Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
  
  restarts (invokable by number or by possibly-abbreviated name):
    0: [ABORT] Exit debugger, returning to top level.
  
  (SB-C::%COMPILE-TIME-TYPE-ERROR (1) STRING #<unavailable argument> ((THE STRING X) X))
  0] 0
Calling "strawman" on:

Which is more readable:

The article misformats the example Lisp code [at least in two of my browsers]:

    (aset a x (+ (aref a x)
                      (aref b (- x 1))
                      (* 3 (aref b (+ x 1)))))
should be:

    (aset a x (+ (aref a x)
                 (aref b (- x 1))
                 (* 3 (aref b (+ x 1)))))
Yeah, I wanted to point it out as well, but I realized it's the text renderer that didn't format his expression correctly. If you do a copy-paste, you'll see that he indented it properly.
I did in fact copy and paste the code directly from the article into my comment above. The only reformatting I did was to add the same number of spaces to the beginning of each line in order to have HN display it as code.

If I was being pedantic about the formatting, the error would be excusable. But the code is presented as evidence that Lisp code is hard to read, so the poor formatting is highly relevant. Furthermore, when presenting C++ code earlier in the article, the formatting is correct:

    typedef std::map<std::string, int>::value_type vtype;
    vtype things[] = [vtype("one", 1),
                      vtype("two",   2),
                      vtype("three", 3)];
    std::map<std::string, int> m3(&things[0]
Perhaps I'm biased, but that hardly helps. Still far less readable than the C++ version which looks almost exactly how you would write out a math problem.
It would be interesting to write a steganographic C++ program which is a valid lisp program when everything but identifiers and parentheses are stripped away.
That would be fairly easy if each line were prepended by a semicolon.
(comment deleted)
One thing I still miss from this and most other Lisp tutorials is good practical examples of problems you would solve with macros. Its one of the biggest differences between Lisp and other languages but I have no idea where I should use macros and where I should simply use functions instead.

For example, in this tutorial he shows a stopwatch macro but this is precisely the sort of stuff that is easy to do with lambdas:

  (define stopwatch (body)
    (start-timer)
    (body)
    (end-timer))

  (stopwatch (lambda () (my_code 42)))
Macros unfortunately make themselves most useful in cases that don't lend themselves to short snippets.

My favorite example involves looking at some real code.

Compare how to add a new intrinsic to LLVM: http://llvm.org/docs/ExtendingLLVM.html versus how to do it for SBCL: http://pvk.ca/Blog/Lisp/hacking_SSE_intrinsics-part_1.html.

Look at how SBCL's x86-64 backend defines the x86-64 register set and specifies what kind of values can go in what registers: https://github.com/sbcl/sbcl/blob/master/src/compiler/x86-64....

Compare that to the equivalent for LLVM: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Target/X8....

The register descriptions are written in a very similar, declarative, way, except the SBCL code uses macros built into the language while the LLVM code uses a separate tool called TableGen that generates C++ code from the declarative descriptions. Using a separate tool means complicating the build process, having an ad-hoc parser/expander that can be a source of bugs, losing access to compiler facilities that allow you to step through the expansions and look at the expanded code, etc.

If you can't get through the prefix syntax, look at how the OpenDylan compiler uses a macro to define the x86 instruction set: https://github.com/dylan-lang/opendylan/blob/master/sources/...

Also see the x86-64 assembler I wrote in Common Lisp: https://github.com/rayiner/amd64-asm/blob/master/encoders.li.... Specifically, lines 374-504, 621+.

Each instruction description basically specifies a list of alternative patterns that are matched against the assembly source. When a pattern matches, it is handed off to a compiled function that generates the actual encoded bytes. That function is automatically generated at compile-time from the declarative list of patterns. Not exactly brilliant code like you'll find in SBCL, but in a couple of weekends I was getting bit-for-bit identical assembly to YASM over a substantial subset of the instruction set. Part of the testsuite uses the same instruction descriptions that are used to generate the encoder: https://github.com/rayiner/amd64-asm/blob/master/testsuite.l... (Line 128).

Sure, you can do it that way -- but isn't it ugly? Isn't it harder to read?

One thing I recently did that required a lot of macros was writing a unit test framework for Arc, the Lisp Hacker News is written in. (framework, with examples, here: https://bitbucket.org/zck/unit-test.arc) So to create a suite, you write:

    (suite math
           this-will-pass (assert-same 4 (+ 2 2))
           this-will-fail (assert-same 3 (+ 2 2)))
Like stopwatch, instead of passing a lambda that runs the body of the unit test, it just lets you put the body in. Why? In c-like languages, you don't write an if statement this way:

    if((val % 2 == 0), lambda (): { return true; }, lambda (): { return false; })
Isn't it easier to write:

    if(val %2 == 0) {
      return true;
    } else {
      return false;
    }
So if using lambdas isn't good for if, why is it good for stopwatch?

Also, none of math, this-will-pass, or this-will-fail are predefined in the language, or even by my framework. You don't have to use strings, as you might otherwise:

    (suite "math"
           "this-will-pass" (assert-same 4 (+ 2 2))
           "this-will-fail" (assert-same 3 (+ 2 2)))
Why might this be useful? Well, when you're writing a function:

    int abs(int val){ return val<0 ? -val : val; }
Wouldn't you find it annoying to write:

    int "abs" (int "val"){ return val<0 ? -val : val ; }
So Lisp lets you incorporate your code as part of the language, and doesn't force you (as much) to hammer your thoughts into the built-in structures of it.
> Sure, you can do it that way -- but isn't it ugly? Isn't it harder to read?

Its the old tradeoff between using long-winded existing constructs or paying a complexity cost and forcing the reader to learn a new abstraction or DSL.

But I like your example. I imagine the extra lambda or string-quoting noise starts getting significant when you have lots of tiny test cases.

>Its the old tradeoff between using long-winded existing constructs or paying a complexity cost and forcing the reader to learn a new abstraction or DSL.

Yeah, that's definitely true. When macros are used to add to the language, you're acting, by definition, as a language designer. You can make good or bad choices. I normally think it's good to eliminate boilerplate (e.g., the lambda calls in the examples) and to simplify things (passing bare symbols instead of strings). To my eye, the new coder actually has to learn less than if they had to remember to wrap with lambdas, or use strings: you code just like other parts of the language. And that's a win.

However, bad choices with macros can definitely be made. Some people consider the loop macro ugly, because it's a hairy ball of special cases with new syntax.

Most macros fall under one of three categories.

Improving performance by removing function calls or doing work at compile time eg https://github.com/ztellman/vertigo emulates c-style structs and uses macros to calculate the correct array offsets at compile-time, so you can treat a byte-array like a rich clojure datastructure but it compiles down to inline array access.

Controlling binding or control flow eg https://github.com/clojure/core.match adds a pattern matching construct similar to those found in eg haskell or ocaml.

Rewriting code eg the core.async/go macro mechanically converts synchronous code into js-style callback code (examples - http://swannodette.github.io/2013/08/17/comparative/ , internals - http://hueypetersen.com/posts/2013/08/02/the-state-machines-...)

EDIT: In a lazy language like haskell, you can do most of that with lambdas, monad/arrow syntax and rewrite rules but the dsl syntax often ends up with just as much cognitive overhead as using a macro. Imagine how much less clear pattern matching would be in haskell if it was a library construct instead of having its own syntax.

Object relational mappers. Or think of anything that in the java world you'd do with an XML file and an external build process, like SOAP APIs or parser generators.
Until I can write something like Ableton Live in any Lisp I'll pass, thanks.
I'm trying to figure out why you posted this, it just seems like a non sequitur. Setting aside, for a moment, the issue of performance on signal processing tasks, from a GUI perspective there's no reason that an interface akin to Ableton Live shouldn't be producible in Common Lisp, Clojure or any currently maintained Scheme implementation.

If you're dependent on 3rd party C/C++ libraries for the signal processing, many schemes are pretty good for FFI, I've not used Clojure FFI or Common Lisp FFI so I can't comment much on them. Clojure seems like it would be as good or at least no worse than Java for interfacing with existing C/C++ libraries. Common Lisp FFI is going to be implementation dependent (like with scheme). I do know that Chicken Scheme's FFI is pretty easy. I made a wrapper for a subset of GLUT and OpenGL, and adding additional components as they became relevant to the project was straightforward.

Regarding performance, I won't comment on Common Lisp or Clojure, I never measured their performance for numeric tasks. Scheme, however, with something like Chicken Scheme has good performance, and again FFI is straightforward so making a C module to do some heavy lifting is not unreasonable.

Clearly the signal processing/audio side of the app can't be written in Lisp, and that actually makes up a pretty large chunk of the code.

And having to bridge everything over FFI really isn't very practical for a UI of this complexity either for performance and code overhead reasons. Being able to access your internal data structures directly without jumping through FFI/translation hoops is a big win for both efficiency and code maintenance.

So Lisp zealots can ding my comment all they want but it doesn't change the fact that intelligent, informed programmers all over the world continue to reject Lisp for very practical reasons that the true believers of the Lisp community will never understand.

It's more likely to be GC that makes Lisp unattractive as an alternate to C++ for the things for which I'd normally use C/C++. I have spent way too much time wrangling Wowza, Hadoop, and other things written in Java in the last years, and have yet to see a good incremental garbage collector in a JVM to support things that do gigabits of throughput and need to do it every second day in+out. Pointers to the contrary are welcome...
This is mostly orthogonal to the article's point, but the motivating example ignores the actual syntax that already exists in C++(11) to initialize classes:

    std::map<std::string, int> m1 = 
    {
        {"one", 1}, 
        {"two", 2}, 
        {"three", 3}
    };
It may be relevant to mention IntelLib [1] here, which is one of many (ab)uses of C++ to enact DSLs.

[1] http://www.intelib.org/intro.html