Hacker News new | ask | show | jobs
by expression 3597 days ago
I guess I'm never to actually “get Lisp” to appreciate its syntax.

>Aside from the prefix ordering, Common Lisp’s syntax is already a bit more elegant because you can set arbitrarily many variables without repeating the assignment operator over and over again:

    ; Have to type out `=` three times
    x = 10
    y = 20
    z = 30

    ; But only one `setf` required
    (setf x 10
          y 20
          z 30)
I utterly fail to see the aforementioned elegance, although I certainly can't miss the line where it happens.
6 comments

So do I. I prefer to type out `=` three times, for a simple reason: while not being more verbose than the `setf` example (3 additional tokens in both examples), it makes all 3 lines the same, which is more regular.

We could make all 3 lines the same with `setf`, but that would be a bit more verbose:

  (setf x 10)
  (setf y 20)
  (setf z 30)
In a program that heavily use the assignment statement, I vote for the shorter form, `=`.
It is absolutely more verbose. The Lisp form can store N constants into N variables in 2N+3 tokens: two parentheses, plus the variables and constants and one symbol. Assignment statements require 3N tokens: N vars, N constants, N assignment operators. That's assuming they don't need some mandatory separator or terminator like a semicolon. We break even when N = 3. For N > 3, 2N+3 < 3N.

Anyway, this is hardly the main point of the article (and probably detracts from it).

Indeed, my main point is not verbosity —it's regularity. I'd rather repeat `setf` in most cases in the name of regularity. That way we spot the semantic difference between lines more easily.

You'd have to use the assignment heavily to justify implementing the `=` syntax, which is barely prettier than the regular `setf` syntax. Plus, you'd probably have to add some annotation anyway, partially defeating the point:

  (assign
    x = 10
    y = 20
    z = 30)
If you repeat setf, you may need a progn to make it one expression. And parallel assignment is off the table without temporary variables:

  (if condition
    (psetf a c b a c b)
    (psetf a b c a b c))
:)

P.S. of course I know rotatef would be better here.

I'm not much for Common Lisp, but I do think Scheme is the prettiest language in which people write ugly code. As such, while I am sympathetic to the virtues of s-expressions, this example is not going to win any hearts or minds. I don't much care for Python's performance or newer features, but your snippet screams for a comparison:

    if condition:
        a, b, c = c, a, b
        a, c, b = b, a, c
That really seems much more clear than the gymnastics my mind has to do manually pairing the assignments inside of psetf. I won't argue that it's objectively better syntax, but I still think it is.
Well...

  (define-syntax assign-group!
    (ir-macro-transformer
      (lambda (x i c)
        (let ((vars (cadr x))
              (vals (caddr x)))
          `(let ,(map (lambda (var val) '(var val)) vars vals)
             ,@(map (lambda (var) 
                      `(set! ,(i var) ,var)) vars))))))
Mind, I haven't tested this, because I don't have CHICKEN Scheme (the dialect this is written in) in front of me, and I may not have matched all the parens on the end, but it should work like this:

  (assign-group! (a b c) (c a b))

Giving you your nice assigment syntax you wanted.

That's the nice thing about Lisps: if you don't like it, you can change it.

That's why I mentioned rotatef:

   (rotatef a c b) ;; a <-- c <-- b
                   ;; `--->----->-'
You forgot "else:"
That is why you would typically create a struct, and then assign the whole struct at once. If you are regularly assigning > 20 variables at the same time, something is wrong with the language you are using.
on the other hand = is one character, and if it's not a legal character in identifiers then x=1 y=2 z=3 takes 2n characters (setf x 1 y 2 z 3) takes 2n+7 characters (plus however many we're using for the actual identifies and values but those'll be the same between examples)
Lisp has an = function; so that binding is taken. Symbols in the keyword package are allowed to have function bindings, and so a synonym for setf can be called := (the symbol named "=" in the keyword package).

  [1]> (defmacro := (&rest args) `(setf ,@args))
  :=
  [2]> (defvar a)
  A
  [3]> (:= a 42)
  42
  [4]> a
  42
  [5]> (= a 42)
  T
Having programmed many substantial projects in both worlds, I have to admit that I much prefer S-expressions over any other syntax -- without complicated macros that introduce some sort of keywords or other tricks. With S-expressions, there is basically just one syntax to learn for every construct, so you can focus on the semantics. (I do prefer Scheme's way of dealing with functions, but that's another matter, of course.)

Unfortunately, I otherwise prefer strongly typed systems languages with a strong focus on compile-time, zero cost solutions such as Ada or Rust. My ideal language would be a very fast, statically and strictly typed language with a modern incremental garbage collector that can be switched off and without type inference, but with an S-expression syntax.

However, if such a language existed or I'd develop it on my own, it probably wouldn't gain much popularity... ;-)

Shen is probably the closest you'll get. http://shenlanguage.org

It's strongly typed, but not a systems language, and very much not zero-cost.

There are some systems lisps with no GC. I just wish I could find them...

A notable Lisp without GC is Linear Lisp http://home.pipeline.com/~hbaker1/LinearLisp.html
...Hang on a second, that sounds a heck of a lot like NewLisp's memory management model, with the disadvantages thereof.
Ooh, that math reference.
With List you know that no matter what semantics someone invents, you are not going to have to struggle with its syntax (too).
> although I certainly can't miss the line where it happens.

Consider the following for many C-style languages:

  if (a = b) { a = a + 1; };
If there is any goodness in the world, I'll never write/fix another of these bugs again. This is the only reason languages have an '==' operator.

In many LISPs this type of bug is practically impossible to implement accidentally.

  (if (= a b) (incf a)) ; This form, by itself, can't assign b to a.
To recreate the bug as before, I would mangle the statement or have explicit declarations outside the conditional block.

  (if (progn (setf a b) (= a b)) (incf a))) ; uh, maybe? Does it even compile? I don't know how to computer.

  (setf a b)
  (if (= a b) (incf a)) ; more likely, but 'setf' would raise flags and the if statement itself isn't doing the mutating.
Clojure makes this even harder because of it's immutability by default.

  (let [a b]             ; statement must enclose 'if' block, which would be a red flag.
    (if (= a b) (inc a)) ; technically I can't 'change' anything here w/o the mutation primitives.

  (if (let [a b] (= a b)) (inc a)) ; mutate in conditional, but the 'a' doesn't change outside the block,
                                   ; and now you have two bugs.

  (if true (let [a b] (inc a))) ; Mimics the bug from the top, but the statement is mangled beyond comprehension.
There are many languages that use = for assignment and == for comparison, without the possibility of confusing the two. In Python, for example, assignment is not an operator at all, so using it in expression context is invalid.

Which leaves the distinction between assignment and comparison - which is always there - as the only reason to have two distinct operators. Which ones they are becomes a matter of taste, but as I recall, the reason why C settled on = for assignment is because it's that much more common. This is still the case today, even in pure languages where it's used for bindings only.

Well, in lisp making assignment a statement is a non-option: statements don't exist. The closest thing is returning #<unspecified> in scheme.
Well, it's similar to math and that was there long before lisp or C
Math notation uses := for assignment and = for equality, no? So in that sense Algol and the Pascal family are more consistent with that, while C actually diverged.
Depends on your field. Some math subjects would argue that "assignment" does not exist, because the same name within the same scope always refers to the same value, whether it's known or not.

Within algorithms, I'm most used to "<-" for assignment, for example "i <- i + 1" inside a loop to count iterations.

I'd argue that all these brackets make it way harder to read.
I'd argue that anything unfamiliar is harder to read because your brain had trouble picking out the signal, and this is not a real problem with the language.
These are really bad examples.

I sat down for a month and a half and just did Racket every day. It was a very good experience, in that I learned a lot about programming in general (Racket's OO constructs are particularly good).

These little syntax tricks aren't the point of the SExpr languages. I think beginners tend to get caught up in them, but they don't actually affect the ergonomics of the language that much and end up becoming transparent to the programmer. Translating Python to Racket is a fairly trivial process, for example.

The point of them is that they are infinitely composable, specifically programmatically. The emphasis is more on writing programs that generate themselves. Think of it like making it trivial to make your own opinionated framework on the fly that costs nothing at runtime.

In Racket, things like File IO vs Network IO can be abstracted to the same exact interface. You might use such a thing to write web services that you can test using saved sessions, and it's fairly easy to do. There is some misconception that Racket is missing a lot of libraries that I don't think is quite as bad as people think because a lot of the things you would need a library or framework for in other languages are ready enough to do on your own with a well thought out macro.

I agree that the former is more elegant in that the syntax is helping more.

But LISPers prefer the latter, because its elegance is that it's a single expression, and the same sort of expression as everything else. Which makes it easier to extend, compose, manipulate, etc. That's the tradeoff of LISP's syntax--you get extreme flexibility and customizability, at the expense of being constrained by its syntactic simplicity.

I agree. This is one of the least compelling Lisp things I've ever read - because, aethestically, I really dislike Lisp. From a design standpoint, I want to love it. The elegance of S-expressions and the simplicity of it all call to me! But in practice its just too ugly!

:(