Hacker News new | ask | show | jobs
by jeswin 4005 days ago
Learning lisp is enlightening, but to claim that it's that much more productive than some of the other well designed languages is a stretch.

- Lisp macros are powerful, so the core of the language can be kept simple. However, many languages take an alternate approach and codify the most common programming patterns into their specification. The result is a compromise that works for the majority of cases, and better understood than custom macros.

- Homoiconicity is elegant, but somewhat over rated in practice. Declaratively expressing intent does not require homoiconicity, you can do that in JSON or XML if you agree on a format. Now with lisp, code being data, you can be infinitely more expressive; but at the same time inferring intent out of complex code-as-data is essentially writing a very intelligent macro/parser. There's a cost to it.

- if you're not really gathering the intent from code-as-data, there are ways to do eval() in other languages as well.

- Lisp has not succeeded on a relative scale. Let's not discount that.

- Compilers can optimize well known constructs better than equivalent macros in lisp.

So again, learning lisp is a great idea. But there isn't a one programming paradigm that's universally better than others.

8 comments

> but to claim that it's that much more productive than some of the other well designed languages is a stretch

Given that you can turn Lisp into any of those "other well designed languages", it's not a stretch at all.

> and better understood than custom macros.

What can be easier than the macros?

> but somewhat over rated in practice

True. You can build a decent meta-language without homoiconicity, all you need is a quasiquotation.

> there are ways to do eval() in other languages as well.

Eval is runtime, macros are compile-time. Huge difference.

> Compilers can optimize well known constructs better than equivalent macros in lisp.

No. Macros can optimise any way you fancy. There are no limits.

> But there isn't a one programming paradigm that's universally better than others.

A paradigm which contains all the others is exactly this.

No. Macros can optimise any way you fancy. There are no limits.

For example, the Racket compiler does not need to know about the type specializing optimizations that Typed Racket makes possible.

- Lisp has not succeeded on a relative scale. Let's not discount that.

Is there a clear reason for this? I've only ever heard good things about lisp.

My impression, as a hobbyist programmer, is that lisp appeals to people who have a deep intellectual curiosity about the way programs work. It doesn't seem to appeal to the larger pool of programmers who are looking for a language thy can pick up in a straightforward way so thy can either get a job, or build a project they've been thinking of.

I fell in love with Lisps and FP precisely because they were an easier, more straightforward way of just getting the job done than the alternative.

How many times have you written a dozen lines of for-loop that could've been one map/reduce? How many times have you written a whole page of Object { this.foo = ... } just to add the simplest of new features?

Literally the reason I got out of programming after high school almost 15 years ago and wrote it off as 'not for me' was that kind of tedium, and learning Lisp and FP were the point in my return when I said 'Oh, wait, actually this is pretty great; where the hell was this when I was a kid?'

Lisp didn't take off because 1) home-computer-ready implementations were largely out of reach for three decades, and 2) Lisp and FP both were embracing the importance of expressive power during an era in which most programming still worshiped doing things the hardest way possible. Shit, when I was a kid, you weren't a 'real programmer' unless you did everything in assembly. Then it was C above all, to be followed by EnterpriseFactoryJavaFactoryFactories.

By the standards of most of the programming world, where there are still real bosses who grade coder performance in KLOC, Lisp is 'wrong'. But pumping out thousands of lines of repetitive boilerplate is not equal to efficiency, it just looks like it to a work culture that only understands output of work rather than quality of work. If programmer A takes 1 hour to solve the problem with 100 LOC, and programmer B thinks for 45 minutes and then solves the same problem with 4, who's the most efficient in that scenario?

And more to the point, which of those two work environments do you want to sign on for?

Same here, I never understood Java interfaces, abstract classes, and a ton of other "features" but picking up Clojure was a breeze. I don't understand why complicating thing that supposed to be simple helps you by any mean. On the top of that, I have seen several cases when Java programmers tipped over in their own code because of the complexity that they thought they understand, except there was a particular case when it was doing something else then expected.

Reasoning about Clojure (LISP) code is always easy because if you follow best practices you have small functions with very little context to understand.

On the top of these, I see the ratio of LOC 50:1 (worst case even higher) for Java : Clojure code that does the same thing. Usually people get triggered and say why does it matter, but in reality less code is better for everybody. Easier to understand, less chance of errors, etc. Correctness was long time lost for majority of Java developers, just put out a survey and you can see it for yourself.

It is also pretty common practice not to handle exceptions well and just let a DNS not found error explode as an IOexception and good luck tracking down what caused it (literary happened to me).

I understand that the average Java dev does not see any value in LISP (Clojure) but it silly to expect that the average of any group is going to lead the scientific advancement of any field including computer science.

One tendency that you can see if you are walking around with open eyes that the people who spent significant time in developing procedural code in an imperative language understand the importance of functional language features and the power of LISP. One can pretend it does not matter see you in 5-10 years and see how much this changes.

https://twitter.com/id_aa_carmack/status/577877590070919168

https://www.youtube.com/watch?v=8X69_42Mj-g

https://www.youtube.com/watch?v=P76Vbsk_3J0

The closest I got to Xerox Parc environments was Smalltalk VisualWorks and Oberon (Wirth made it based on his Cedar experience).

Then thanks to my curiosity I delved into the Xerox's and Genera documentation.

It is sad that a PDP-11 and VMS descendedant won the mainstream.

How much better could computing be if those behind those systems hadn't failed to bring them to the masses.

However environments like the JVM, .NET and their tooling bring us somehow close to it.

Also Swift with its Playground is helping new generations to rediscover this experience.

So maybe not all is lost.

I take it you're probably not a huge fan of Golang? :)
I would imagine not, though the parent can speak for himself. I agree with what has been said before, that Go is a bold step backwards, just another language to replace large amounts of Legacy Enterprise Code with (sometimes) slightly less large amounts of (Soon To Be Legacy) Enterprise Code. Go has going for it corporate backing and a good community, but on the technical merits alone, there is a better language for every task.
I wish I was. That little gopher is adorable.

Rust, on the other hand, gives me palpitations.

Is golang very verbose?
I don't know about "very", but it's pretty verbose and very imperative. You have to do a lot of repetition, often straight up code copy+pasting, in some cases.
In a way it's similar to frameworks. Frameworks which are more popular try to make choices for the user (like Rails). As an end-user I clearly wanna focus on my tasks, rather than choosing a toolset or perhaps building one myself.

Lisp is minimal and abstract. That's appealing to a different set of people, who aren't satisfied with off-the-shelf abstraction levels. It's also fun and challenging to work at that level, though IMO it's not always going to translate to better productivity.

For me, learning assembly and going through the 80386 reference manuals were more rewarding in terms of understanding how programs work. Sorry I have no specific insight to offer on the question you asked.

Lisp was the hot new thing in the mid-to-late 80s, when the AI Winter hit.

https://en.wikipedia.org/wiki/AI_winter

When AI couldn't live up to the hype, funding dried up. A lot of that funding was driving the companies and research projects that were doing major Lisp development. After the AI Winter, Lisp was strongly associated with the unmet promises of AI and thus gained a reputation as a poor choice for serious projects.

It's never really recovered.

Around 2000 a new generation rediscovered Lisp. SBCL was released in dec 1999. CMUCL rethought with a simplified implementation and build process. From then on various implementations were improved. Clozure CL was released on multiple platforms. The commercial Lisps were moving to the then important operating systems and architectures.

The hype moved to Clojure, Racket, Julia and other Lisp-like languages. The core Lisp may not have the same depth of commercial activity as in the 80s, but generally the implementations are in the best shape since two decades. There are still lots of open issues, but from the Lisp programmer standpoint, it's an incredible time.

It’s the Lisp Curse: http://www.winestockwebdesign.com/Essays/Lisp_Curse.html

Lisp is so powerful that problems which are technical issues in other programming languages are social issues in Lisp.

lisp makes you an asshole programmer. you're encouraged and enabled to write your own language for each problem, thus isolating you in a world of your own views and ideas. it's a babelian tar pit, luring programmers to their doom.

being your own tin pot dictator is quite alluring. you get to go to great feats and neat hacks to get code working. to control and manipulate the code to allow you to write what you want. every new macro and construct shapes the product in your own image and ideals, subsequently alienating other programmers.

it's like these language revisionist cranks who want to replace english with their own little concoction that's just ever so perfect and logical. a complete ignorance of social factors.

anecdotally, I know of large scale codebases and products in simpler, less elegant languages, meanwhile lisp seems to be popular with the lone hacker aesthetic.

eventually, with enough practice, you get to the smug lisp asshole stage.

this is where you wonder why lisp is unpopular, or fragmented, but assume that it's simply too good for the populace. Classics like 'worse is better' struggle with the notion that lisp maybe isn't that good. Sometimes you get a naggum complex and trot out saphir-whorf. Other people are terrible and that is why they don't use lisp.

it can't be that lisp isn't a great idea. or macros aren't a great tradeoff. at least the ruby community is coming to terms with monkey patching coming at the expense of library conflicts.

lisp is a strange beast. a simple tool that encourages complexity. purity over utility. a perfect goo waiting for the next hero to shape it and return to the mortal plain with their new, perfect macros.

http://forums.somethingawful.com/showthread.php?threadid=348...

> a complete ignorance of social factors.

Maybe that's why I like Lisp so much. Because "social factors" are so frikkin' annoying and irrelevant and I feel the world would be so much better for everyone if we stopped paying so much attention to them as we do now.

To formulate this a little more nicely, I might say instead that there is a real need for "intimate" languages, just as there is a need for "collaborative" languages.

As an example, the shells on my personal machines are often customized beyond the comprehension of anyone who isn't me, with tons of two-letter aliases and bash functions, cryptic environment variables, and a $PATH that's several terminal lines long, filled with labrythine symlinks and oneliners that I've accumulated over the years. Many people have similarly elaborate and impenetrable emacs configurations.

That's fine, since this is my personal environment, but at work (I'm a sysadmin, more or less) I'm still able to use more-or-less bash, and even write portable shell scripts that eschew bash-isms. Similarly, all that horrible e-lisp powering super-personalized workflows doesn't prevent someone from writing an emacs mode that they can share with others, the point being that a language that enables customization is great, because you can always just not do that and write code that others will find comprehensible.

Conversely, if your language forces you to write in a collaborative style, you can't gain efficiencies in your private use of it.

> To formulate this a little more nicely, I might say instead that there is a real need for "intimate" languages, just as there is a need for "collaborative" languages.

That's a... way of putting it I've never seen before. I'll remember the concept of "intimate" vs. "collaborative" language for the future.

Personally, even though I write a lot of Lisp and live inside Emacs, my environment seems to be quite... standard. The "collaborative" mindset is emphasized in pretty much every programming book out there, and I must have acquired this kind of weird fear of overcustomizing my environment thanks to it.

I'm not a lisp user, but I've used xml + xslt to generate xslt that processes xml to xhtml and I liked it ;)
"own languages" are much more approachable for the others than the "own libraries". For very obvious reasons.
My naive reasoning of why is that a lot of people start by learning c-like languages and don't see the need to learn something as different as lisp. As lisp and it's descendants were never really the dominant language, it was never the first type of language most people learned. Now that many mainstream languages have progress to incorporate more and more lisp features, it's becoming less foreign to many devs and the popularity is increasing and is now higher than I think it ever was.
>> Lisp has not succeeded on a relative scale. Let's not discount that.

> Is there a clear reason for this?

Yes. In the 1980s AI was teh new shiny and at that time, Lisp was almost synonymous with AI.

A bunch of people over-promised when it came to AI and expert systems, and failed to deliver. And people conflated the failure of the promise of AI with the failure of Lisp. Essentially, guilt by association -- people can be dumb that way.

Amusingly, once something in the realm of AI actually works -- we stop calling it AI. But one thing is for certain: the scruffies have been right more often than the neats.

Still, Common Lisp is pretty effin' awesome.

Lisp macros are powerful, so the core of the language can be kept simple. However, many languages take an alternate approach and codify the most common programming patterns into their specification. The result is a compromise that works for the majority of cases, and better understood than custom macros.

The semantic core is kept simple. That doesn't mean lisps don't provide constructs for common patterns. Ej. Loop, with-open-file or defclass or Racket's for/ constructs.

Plus a good written macro is easy to understand and I'd argue most of macros are good written. In fact I'd like to see a macro that what it does is unclear on a widely use liso library.

> - Compilers can optimize well known constructs better than equivalent macros in lisp.

Lookup compiler macros. Macros can help the compiler optimize the code.

99% of programming works like this: you pass parameters to a function/macro. If you screw up, the compiler spits an error. The details of the error message are rarely relevant, you mostly know what's wrong even before you read that message anyway.

This whole talk about macros being crazy dangerous and difficult is very misguided. Most of the time, if a Lisp compiler spits a weird message on you, you know what you've screwed up. In the 1% cases you don't, you apply macroexpand-1 (or equivalent), see why the expansion doesn't make sense and fix it. In the 1% of the cases it doesn't help, you keep reading the source until you understand what's wrong. It's no different than debugging functions. Same rules apply.

> But there isn't a one programming paradigm that's universally better than others.

Maybe that's why Racket supports functional, imperative, declarative, and object oriented programming. I'm sure I'm even missing a few.

Don't forget relational and logic programming. :)

http://minikanren.org

> there isn't a one programming paradigm that's universally better than others.

That's so true. I wish someone would come up with a language where a wide variety of programming paradigms, including imperative, functional, and message passing styles, could find convenient expression.

EDIT: This comment is almost an exact quote from somewhere. Bonus points to whoever can identify where.

The parent's humorous wish is granted by the wizards of MIT Scheme (and, I daresay, even more fulfilled by Racket):

"Scheme is a statically scoped and properly tail-recursive dialect of the Lisp programming language invented by Guy Lewis Steele Jr. and Gerald Jay Sussman. It was designed to have an exceptionally clear and simple semantics and few different ways to form expressions. A wide variety of programming paradigms, including imperative, functional, and message passing styles, find convenient expression in Scheme."

http://groups.csail.mit.edu/mac/projects/scheme/

I got it from R4RS. I imagine MIT Scheme also got it from there.
Maybe. Looks like R4RS got it from R3RS. :-)
I think you should give Elixir a look: http://elixir-lang.org

Bonus points: Elixir has compile time macros and an AST with a representation that is similar to Lisp.

> and an AST with a representation that is similar to Lisp

That's actually cheating, you know :). Lisp pretty much is AST. It's a fun fact about Erlang that it's not directly compiled to BEAM, but is first translated into a very strange, almost lovecraftian Lisp. I've worked with Erlang commercially for some time and ever since learning about parse transforms I kept wondering why they didn't just clean up the "intermediate language" syntax; they'd have a decent Lisp instead of a parallel Prolog.

There is, of course, Lisp Flavoured Erlang: http://lfe.io/
Indeed there is :). I used to sneak up some code in it on the job ;). I'm happy to see it being actively developed to this very day.
oz/mozart does a good job of that; it's a shame it never really caught on as a non-research language.

http://mozart.github.io/

You have Scala. They even try to shoehorn macros into it. The result is a powerful but IMO messy language.
What for? With any meta-language you can build and mix any paradigms you like.
Although Lisp itself may not have succeeded on a relative scale, Clojure (a Lisp dialect for the JVM) seems to have a fairly good foothold.
Also: writing good macros is (even) more diffcult than writing good functions or modules.

Writing macros is doing language design and compiler implementation at the same time.

If you've ever cursed the error messages from a C++ compiler, think how much worse it would be if that compiler was written by your co-worker part-time as a side effect of writing rest of the code.

(I wrote clojure in anger for three years with brilliant co-workers)

> If you've ever cursed the error messages from a C++ compiler, think how much worse it would be if that compiler was written by your co-worker part-time as a side effect of writing rest of the code.

This is why you use proper facilities to write macros, that will enable you to forge better error messages than most C compilers. This is also why you don't simply use something like `defmacro` that will not let you supply syntactic information (syntax objects) with location and context information.

A good macro has a contract that constrains its use and inform you when you're breaking it, so that you don't have to rely on your coworkers to explain their macro to you.

See this[0][1] for a very detailed view on how you can provide the programmer with the tools they need to create abstractions that stretch well into the world of macros and still be able to make them usable.

You can set enforcable conditions for your macros to guide people, just like any language can statically check their syntax.

This is not an unsolved problem. What is unsolved, like many people have mentioned, is when the culture of a language (and the facilities of lesser languages) don't emphasize managing macros like other abstractions.

Using Lisps that don't emphasize more than "Macros are functions that modify lists of syntax at read time" will lead people to believe that's all there is to it. You won't have to be angry about macros if you use a language that gives people the tools to help you use them and then fosters that idea.

0 - http://docs.racket-lang.org/syntax/Parsing_Syntax.html?q=syn...

1 - http://docs.racket-lang.org/syntax/stxparse-specifying.html?...

No. Writing macros is less difficult than pretty much anything else. You just have to follow the proper methods.

Macros are, essentially, compilers. It is a very well studied area, writing compilers is a totally mechanical thing which does not require any creative thinking.

Compilers are best implemented if split into long sequences of very trivial tree rewrites (see the Nanopass framework for an example of this approach). Such tree rewrites should be declarative, and you don't really need a Turing-complete language to do this. This approach is inherently modular and highly self-documenting, so you're ending up with nicely organised, readable, maintainable code, much better than whatever you'd do with plain functions.

> However, many languages take an alternate approach and codify the most common programming patterns into their specification. The result is a compromise that works for the majority of cases, and better understood than custom macros.

That's not the case in my experience. It's very easy to look up the definition of a macro, or see what the macro-expansion of an expression looks like. Good luck trying to figure out what your interpreter/compiler/JIT is actually doing when the language documentation is lacking.

For Common Lisp at least the quality of documentation is also another huge advantage over any other language I've used - the Hyperspec (http://clhs.lisp.se/) is extremely detailed, concise, and easy to access (you can pull up the documentation page for a particular symbol with a keystroke, or browse through the index or by TOC when you aren't sure what you are looking for).

> Homoiconicity is elegant, but somewhat over rated in practice. Declaratively expressing intent does not require homoiconicity, you can do that in JSON or XML if you agree on a format. Now with lisp, code being data, you can be infinitely more expressive; but at the same time inferring intent out of complex code-as-data is essentially writing a very intelligent macro/parser. There's a cost to it.

Except that doing it with JSON or XML is usually two orders of magnitude more code than a macro solution would be. Now simple problems that could have been debugged with a macroexpand involve tens of thousands of lines of parser libraries (it's 2015, why are encoding errors still an issue for anyone?), ConfigurationFactories and class hierarchies for the internal representation of whatever it is you are trying to encode in JSON, and glue code to connect it to your current controller and model classes.

> Compilers can optimize well known constructs better than equivalent macros in lisp.

Compilers are very limited in the partial evaluation and domain-specific optimizations that they can do. This is not the case for macros.

> But there isn't a one programming paradigm that's universally better than others.

That's kind of the point of macros - to allow you to add new programming paradigms without having to change the base language or compiler.