Hacker News new | comments | show | ask | jobs | submit login
Common Lisp is not secretly Scheme with bad syntax (groups.google.com)
56 points by gnosis on Jan 14, 2011 | hide | past | web | favorite | 25 comments

Whenever I introduce people to functional programming, I always list the standard benefits you'll hear and then put emphasis on powerful abstraction being the most important feature that makes fp worth learning.

Common Lisp is great because it realizes that abstraction and expressiveness are what make fp powerful, and is smart enough to know when to drop 'purity' in favor of these.

After spending a lot of time with Haskell and Scheme, Common Lisp feels very non-functional, however I still feel it is the best language for expressiveness and abstraction. Work with Scheme and Haskell enough and you'll find yourself writing some convoluted things to maintain purity. The problem with purity is that while it makes somethings much easier to express, there are times when you have to stop and ask "how do I express this", I find common lisp always gives you some way to express anything, but leaves you asking "how can I express this better?"

>Work with Scheme and Haskell enough and you'll find yourself writing some convoluted things to maintain purity.

I don't think you can lump all the Scheme languages into one umbrella and more importantly lump Scheme and Haskell in that context.

For example, Racket has really good constructs that lets me "drop 'purity'", control side-effects, build sequences, control the flow, build classes, manage complexity while at the same time giving me the power to build higher levels of abstractions, express my thoughts and reason with my program.


0. Racket Guide: http://docs.racket-lang.org/guide/index.html

1. System Programming with Racket : http://docs.racket-lang.org/more/

Racket is not a scheme.

Racket is a Scheme

"Racket is still a dialect of Lisp and a descendant of Scheme. The tools developed by PLT will continue to support R5RS, R6RS, the old mzscheme environment, Typed Scheme, and more. At the same time, instead of having to say “PLT's main variant of Scheme,” programmers can now simply say “Racket” to refer to the specific descendant of Scheme that powers PLT's languages and libraries."

Reference : http://racket-lang.org/new-name.html

Taken from the same thread, I loved this response, from Stefan Scholl:

    >There seems to be a great deal of interest 
    >from the functional programming community 
    >in benchmarking. Is there enough interest 
    >to create a new computer language shootout 
    >that showcases more relevant/suitable tasks 
    >for FP?

    Does this sell books? Should I write one?

After reading the first paragraph, I realized that I mistook "CL" to mean "Clojure." The comment about Common Lisp lacking tail-call-optimization being a factor in not considering it a functional language reflects my only real problem with Clojure so far.

Other than that, this writeup reflects what I've learned so far of Common Lisp - it's much more of an imperative style language than it is a functional language.

Just to clarify, although the CL spec doesn't require TCO, every CL implementation I've used has it anyways.

Clojure does have TCO, though you have to be explicit about it.

I use Clojure's looping constructs and list comprehensions for most of the tasks; explicit TCO using loop/recur for problems which are best expressed recursively.

Sort of.

In true TCO, you optimize not only tail calls to the same function, but also tail calls to other functions. (Think of coroutines as an application).

One way to think of a TCO is that you are turning a function call (machine code to save your spot in the function, then a jump) into a simple jump (you don't need to save your spot, because there is nothing left in the function anyway).

Clojure doesn't support this (because it would be too difficult[inefficient, i think kawa scheme and JRuby do implement it] on the JVM). I assume that the common lisps which do support TCO, have true TCO, as they are using assembly or interpreters.

Clojure's TCO can be seen as a special form of a loop (in fact, it is very easy to write a macro that takes a loop-recur form and transforms it into a common lisp style Do* loop).

As I mention below you can get the same result with lazy-sequences and it's very efficient.

I think that you would have to be careful with that. To my knowledge, with lazy sequences in Clojure, the thunks are grouped into segments of 32.

This chunking is for efficiency, but it has the (sometimes) weird implication that you ask for the first element in a sequence and it evaluates that first element, and the next 31 elements as well. This is troublesome if you attempt to use side effects.

(Mr. Fogus has a nice writeup (and workaround) of this phenomena here: http://blog.fogus.me/2010/01/22/de-chunkifying-sequences-in-...)

I have to admit, I am not quite getting my head around how using a lazy sequence to the same effect would work (ignoring the chunking issue). I see how they are similar, a lazy sequence is just a flattened trampoline. Could you maybe pastebin or lisppaste an example?

Anyway, I guess the point I was trying to make was not that it is impossible to do these things, but that TCO is a compiler level optimization of the function calling convention (manipulating the stack), and although Clojure has 'tail calls,' it can't really be called TCO in the true sense of the word. (Which is OK, as you said, there are other ways to do this sort of stuff).

You can easily create true one-at-a-time lazy-sequences. I've been working on my own implementation of miniKanren (from The Reasoned Schemer) in Clojure. It's been quite challenging since the original implementation assumes the presence of TCO. Also one-at-time laziness is very much required here to enforce interleaving of streams results and to avoid divergence. I was happy to find I could get the same non-stack consuming behavior and that it turns out to perform better than miniKanren under Racket.


Interesting, I didn't realize that loop/recur used TCO. Makes sense. :) As far as usage, I do like a friend told me: Favor map and filter, but use loop/recur if you're stuck.

You can get most of the real benefits of TCO (including many mutually recursive functions) in Clojure with lazy-sequences. The harder (and less frequest) use case of TCO'ing CPS style code can be accomplished with trampoline.

Lazy sequences are my favorite new language feature since learning Clojure.

This link doesn't work for me. Were you trying to link to the following thread?


Nope. Just search google for:

  "secretly Scheme with bad syntax"
It should be the first hit.

It's a comp.lang.lisp post by Kent Pitman. Here's the header:

  Newsgroups: comp.lang.lisp
  From: Kent M Pitman <pit...@nhplace.com>
  Date: 15 Jul 2007 02:19:09 -0400
  Local: Sun, Jul 15 2007 1:19 am 
  Subject: Re: New Computer Language Shootout?
Here's a copy of the message:


[insert Arc joke here]

Didn't you mean (insert Arc joke here) ?

Doesn't matter. In Racket you can use either bracket-style, as you please.

I thought () was used for function applications and [] for those that weren't. For example (let [x 3] ... )

Nope they are interchangeable.

The compiler may not mind, but isn't that what they are actually used for?

Not quite; in (let ([x 1] [y 2]) (+ x y)) there's a set of parens which aren't used for function (or macro) application.

In Arc, square brackets are shorthand for an anonymous function. The argument is an underscore. ([+ _ 2] 2) returns 4.

Applications are open for YC Winter 2019

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact