Hacker News new | past | comments | ask | show | jobs | submit login
Playing with Syntax (stevelosh.com)
144 points by jsnell on Aug 20, 2016 | hide | past | favorite | 93 comments



I agree with the overall message of the article (Lisp is a good substrate to explore syntactic ideas) but find the article just sort of wandering aimlessly to construct some macros of dubious value.

Don't Repeat Yourself is a common tenet of programming. Lisp gives you the opportunity to virtually never repeat yourself because making syntactic abstractions is cheap and easy. It must be recognized however that abstractions range from totally useless to paradigm-shifting, and DRY isn't about saving typing but rather encapsulating ideas into reusable constructs. A common problem I see in production Lisp code bases is the existence of a programmer's set of pet syntactic abstractions that don't really have a high ceiling; you see all kinds of zaps, frobs, and lets for a reason no other than they save some typing. I do not appreciate such abstractions. The Lisp syntactic abstractions I do appreciate are ones that bring me into a new paradigm of thinking. For instance, Lisp includes a set of macros to express complex iteration readably. Without this, iteration wouldn't be much more than writing (un)conditional jumps, and by having the abstractions, you shift your thinking from the notion of jumps to the notion of traversal. Another recent example is a set of macros to simplify index wrangling in tensor algebra. These "macros" already existed in contemporary math in the form of constructs such as Einstein notation and they make reasoning and thinking about tensors easier.

This article, in my opinion, treks to the destination of a macro that, while perhaps neat, isn't going to give you higher quality programs.


> A common problem I see in production Lisp code bases is the existence of a programmer's set of pet syntactic abstractions that don't really have a high ceiling; you see all kinds of zaps, frobs, and lets for a reason no other than they save some typing. I do not appreciate such abstractions.

I found this style irritating at first, but after programming in CL for a while you begin to appreciate the reason for it.

The CL specification is basically frozen, which means no new language features. That has led to a collection of "canonical" libraries like alexandria, which brings in a lot of the batteries-included stuff you'd expect. However, if I need a version of `map` or `zap` or something, I'd sometimes rather just write the macro myself and save the external dependency. So a lot of these little single-purpose macros you find all the time are a symptom of the way CL is frozen. Notice how Clojure doesn't have this problem in the way CL does.

Another reason for the prevalence of pet syntactic abstractions is that this is actually to a certain degree the way you're supposed to program in CL. The idea is that you start with a runtime "lisp image" which is typically the `CL-USER` package, and your program makes modifications to the lisp image.


Contrast this with the dictum in Haskell land that typeclasses (abstractions) should have laws to justify their existence. just any random abstraction will not do. although in the small, higher-order functions seem to fill in most gaps. 'syntactic abstractions I do appreciate are ones that bring me into a new paradigm of thinking' indeed.


The same WAS true for scheme: Scheme's spec is fairly minimal, and for many years there was no standard way to handle external dependancies, so such deps were frowned upon in the tiny amount of portable code that existed. SLIB, SXML, and a handful of SRFIs were all you could really depend on. Hell, even Binary I/O wasn't standard.

With R7RS, hopefully we can have a larger set of portable code, and fix this problem.

But yeah, these kind of abstractions ARE just part of how lisp is designed. The rule is, if you see an idiom in your code, abstract it as a function or a macro.


> DRY isn't about saving typing but rather encapsulating ideas into reusable constructs

I thought it was about making sure that, if you changed something, you changed it everywhere at once.


It turns out that SBCL is really quite good at optimizing let statements and local variables.

That is not surprising for any dataflow-based compiler, since "a = b" by itself won't generate even one extra instruction unless those two variables' values get used independently, creating a "fork" in the path and necessitating a copy.

However --- and this is a good example of how compilers, especially for very high-level languages, are still far from optimum even for trivial optimisation --- the majority of those instructions are still unnecessary. Ultimately, the purpose of that code is to perform one addition and one modulus. Doing that should not take over two dozen instructions on any sane architecture.

At a glance I can already see that line 9 is superfluous: it writes the quotient into RBX, where it will never be used again. And there should not be any reason to check for 0 explicitly if you are going to just raise the exception anyway --- the CPU will do that automatically.

If I analyse that snippet a little more I could probably find more ways to optimise it, but it would be easier and faster to just show what a human, and presumably a better compiler, could do:

    add rax, rcx
    cqo
    idiv rdx
Assuming we are using registers for arguments and have a choice of where the caller expects the return value, those 3 instructions are all that's necessary, and the cqo is only because x86's divide takes a double-width dividend.

This is why I love Common Lisp. You can be rewriting the syntax of the language and working on a tower of abstraction one moment, and looking at X86 assembly to see what the hell the computer is actually doing the next. It’s wonderful.

I want to believe that compiler optimisations are actually good enough to "collapse the abstractions" and make these languages generate nearly the same code as an expert Asm programmer, but when a simple function, even at maximum optimisation, turns into 23 instructions in 70 bytes vs. the 3 instructions in 8 bytes that I'd expect, it's rather disappointing.


Well, if you want a correct result with negative numbers, you have to implement the modulo operation, not remainder. The distance in the example can be negative, so can be the sum, but you want the resulting position to always be positive.

There are other instructions that probably deal with returning a proper fixnum. If I remember correctly, the CLC before the RET is to signal that there is a single return value, not multiple ones. I am not saying that there aren't possible optimisations, but (i) some things are tied to the existing computation models and (ii) SBCL developers have limited resources.

See also SB-ASSEM to emit machine code (https://www.pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembl...).


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.


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.


There is in fact one error in the above macro: well, I think only one: in the map, (lambda (var val) '(var val)) should be (lambda (var val) (list var val))


That's why I mentioned rotatef:

   (rotatef a c b) ;; a <-- c <-- b
                   ;; `--->----->-'


You forgot "else:"


Funny that. In my head, I parsed it as two (odd) statements under one if statement. More like when than if.


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!

:(


Every Lisp article promoting the "elegance" of its syntax will do the exact opposite to non-users of Lisp.

This is no exception.

From the first example on there is not one case where the Lisp syntax is a clearer expression of the concept and it's hard to justify that anything that is less clear is elegant in the slightest.

Personally I'd put destructing assignment as more elegant for the first example:

    [a, b, c] = [10, 20, 30]
But that's just me.


Wouldn't this work:

    (destructuring-bind (a b c) '(10 20 30) c)
? Destructuring is also supported in the LOOP macro (http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node252.html)

I don't think this article was written for the non-users of Lisp, it uses too many advanced language features without any explanation to be accessible to non-Lispers.


Thats... similar but I wouldn't call it "more elegant". It's less clear in almost every way.

Why c? Why the quote? Why not have elegant language syntax instead of that clunky macro?


I put `c` there because `destructuring-bind` is lexically scoped and only binds values to names inside of it. This may be a good or a bad thing, depending on your use case, but I prefer having the scopes clearly visible.

The quote is a multi-purpose element of the syntax, unlike `[` in Python, which only serves a single purpose (actually, it doubles as an item getter syntax). Whether having a single, more universal construct which you can use in many situations, or having a few more specialized constructs is more elegant is debatable and I think a matter of taste.

The problem with having specialized syntax for this is that it's not extensible. For example, we (I'm a Python programmer, too) had to wait for 3.4 (IIRC) for a very simple feture of `a, b, *c = range(10)`; with a language built on macros you can extend these constructs as much as you wish. For example adding support for destructuring hash-maps (if it's not supported already) shouldn't be a big problem; on the other hand adding a specialized syntax for dict destructuring in Python is impossible within a language.


Good explaination! Thank you!

That does explain it very well but I'd also argue that a syntax that requires that much explaining probably can't be called "elegant".

Powerful though? Yes. Lips syntax certainly is that.


That assignment has 9 pieces of extra syntax above a b c 10 20 30. By contrast, (setf a 10 b 20 c 30) has only three: two parens and setf. Let's count the crumbs:

  [a, b, c] = [10, 20, 30]
  1 2  3  4 5 6  7   8   9
In Common Lisp, you an express that 10 20 30 should go to a b c using values:

  (setf (values a b c) (values 10 2 30))
That still has fewer overhead tokens!

  (setf (values a b c) (values 10 2 30))
  1 2   3   4          5 6            78
If they are bother make yourself

  (vsetf (a b c) (10 20 30))
This is easy to implement in a quick and dirty way:

  (defmacro vsetf (places exprs) `(psetf ,@(mapcan #'list places exprs))

  (macroexpand-1 '(vsetf (a b c) (1 2 3)))
  -> (PSETF A 1 B 2 C 3)
psetf is probably a good choice here since it is probably best for the construct to have parallel semantics. That is to say, we want, for instance, to be able to exchange variables with it:

  (vsetf (x y) (y x))
which will work thanks to psetf.

We could huff and puff a little harder in the macro and make it work like

  (vsetf x y = y x)


Token-counting is a strawman here; you can use more tokens and still have something that's easier for a person to read.

I think his point was that Lisp pushes syntactic minimalism to an extreme, but all the complexity then gets shoved into what your keywords are doing. You're not eliminating complexity, you're just moving it somewhere else. Judicious introduction of syntax + keywords can be better than depending on magic keywords/macros/symbols alone.

A concrete example of this comes from math--while we could write every integral as

  (Integral lbound ubound body differential)
I'd argue that our usual integral notation is both more readable and compact because the spatial positioning of the elements has semantic meaning.

The tradeoff is as you introduce more syntax, it's harder to get a macro system as elegant as Lisp's; Lisp macros are relatively easy to write because s-exprs are simple. But we shouldn't pretend that there's no downside to a minimal syntax--otherwise we would all be coding in pure untyped lambda calculus or Brainfuck.


Yes, you're eliminating complexity. Because there is a fixed amount of complexity in the semantics of the assignment operation itself. Having to parse some syntax with precedence and whatnot is extra complexity that doesn't further the goal of assigning to those three variables.

The ideal syntax for performing some operation O on entities X, Y and Z consists only of the symbols O, X, Y an Z. We need the parentheses to delimit that as an expression among other symbols.


> The ideal syntax for performing some operation O on entities X, Y and Z consists only of the symbols O, X, Y an Z

That's simply begging the question.

That viewpoint only makes sense if all code is most parsimoniously broken down into its parse tree, but I don't think that's been shown.

When all you have is a hammer (even a slick, homoiconic hammer), everything looks like a nail.

> We need the parentheses to delimit that as an expression among other symbols.

There are other ways to delimit expressions as well (e.g. implicitly with precedence, with whitespace, with HTML tags, with curly braces, etc.); why is Lisp's way the one true way?


All the extra punctuation in syntax is redundant in the same way serifs are redundant on letters - sure, you don't need them to be able to figure out what the letters are, but having them there makes it easier and increases your reading speed, despite the increased complexity of shapes.


When was the last time you saw a freeway sign in Times Roman or Garamond or their ilk? Or any other public signage related to transportation.

All modern console fonts for writing code are sans-serif.

(I'm using Microsoft's Consolas, even on GNU/Linux systems).

A number of times in the past I tried finding a serif font for editing code, just for shits and giggles. And, by golly, that's precisely all it was good for.

If you want squiggly high frequency noise in your code, you can just switch to that type of font; no need to foist that into the programming language.


When was the last time you've read a book typeset in a sans serif font?

For clarification, I wasn't suggesting to use serifs for coding. It's not necessary there, because it's not the letters themselves that need to catch the eye, but code structure. All those square and curly braces, commas etc do just that. As does indentation, but using indentation for everything, the way Lisp tends to do, blurs too many distinctions.

S-exprs are great for machines, just as binary is - it's simple and consistent and lacks pointless redundancy. But humans aren't machines - redundancy is good for us sometimes.


> When was the last time you saw a freeway sign in Times Roman or Garamond or their ilk? Or any other public signage related to transportation.

Non-sequitur - serifs are for small, black-on-white, type as visual hinting. You'd not expect serif type on road signs.

All you've done is demonstrate ignorance of typography


That's a lot of work to demonstrate that Lisp's syntax is less elegant than freaking ES6.

> TThat assignment has 9 pieces of extra syntax

Seeing as we're not playing Code Golf that does t really matter does it? What it is is clearer, easier to understand, and actually built into the language.

Lisp is many things, syntactically elegant isn't one of them.


I feel that I hadn't seen true beauty in a text editor window until I got involved with Lisp, and that EczemaScript is designed by imbeciles.

I'm willing to try to put some kind of numbers on my point of view. Basically, it boils down to token counts and the presence of implicit associativity and precedence rules.

We can remove four square parentheses from the [ a, b, c ] = [ 1, 2, 3 ] assignment, thereby significantly reducing the verbiage. However, we end up with ambiguity. There is no universal precedence between comma and assignment. For instance, in C (a language which indirectly inspired the syntax of ES), it is the other way: the expression a, b, c = 1, 2, 3 in C will perform only the c = 1 assignment. It's good that ES spends these extra tokens to reduce ambiguity here.

Every extra token should count as one demerit point. Then, every instance in a sentence where an implicit rule must be invoked to disambiguate the parse should result in multiple demerits determined according to how the smallest number of grouping tokens would have to be inserted to remove the ambiguity. (That's being generous, because it doesn't take into account the work of determining the parse and doing the insertion.)

So for instance "Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo" has a minimal number of tokens to express its meaning (which is good), but there is a significant cognitive load to figure out its structure from all the implicit grammar rules (bad). If we insert the parentheses we get "((Buffalo buffalo) (Buffalo buffalo) buffalo) buffalo (Buffalo buffalo)".

Some functional languages have this "buffalo disease"; strings of words are just catenated, and working out all the partial applications that are buried in there and whatnot is a hairball.


> Basically, it boils down to token counts and the presence of implicit associativity and precedence rules.

That's... a really arbitrary and contrived presumption. Not to mention presuppositional (and circular) in nature. "I define what makes a language beautiful as what Lisp is best at, therefore lisp is most beautiful."

It's things like this which cause Lisp evangelists to not get taken seriously.

Based on your comparison anyhow Python is much better with it's Tuples (ie - a, b, c = 1, 2, 3) and lisp would be fundamentally worse.


I see; so when you say things like 'Lisp is many things, syntactically elegant isn't one of them' you are taken seriously, whereas if I say the opposite I am not; and it's because I have tried to reflect over my biases, and quantify them in some sort of objectively evaluable terms.


No, it's when you define "beautiful" as "equivalent to lisp syntax" that you are not taken seriously.

Which you did. So you weren't.


In CL without the need for destructuring:

    (setf (values a b c)
          (values 10 20 30))


That's still three superfluous words. I have to agree with abritinthebay, especially when comparing with Python's destructuring, which doesn't even need brackets:

  a, b, c = 10, 20, 30


What about all those superfluous commas? I also dislike languages where expressions don't have visible bounds.


A line break isn't visible? Not by itself, I suppose, but in the code, it's hard to miss.


for what expression does the line break stand, exactly?


But why must everything be an expression?


Because anything else is a language design mistake.


Can you justify that statement empirically?


a, b, c is a tuple. Parentheses can be omitted, same as in some ML dialects:

  # let a, b, c = 10, 11, 22;;
  val a : int = 10
  val b : int = 11
  val c : int = 22

  # a, b, c;;
  - : int * int * int = (10, 11, 22)
  # (a, b, c);;
  - : int * int * int = (10, 11, 22)


Python needs a piece of whitespace on the left of the correct size and tab/space constituency, and a newline on the right.

It's ambiguous. What is the relative precedence of the comma and equal sign? You just have to know.

Lisp means never memorizing associativity and precedence tables again.


No, just the name and argument order of a bunch of functions/macros (set, setf, setq...).


> never memorizing associativity and precedence tables again.

Why is that an unambiguous benefit though?

By that logic, we ought to have parentheses around every mathematical expression as well.

I think there's a sane middle ground between forcing people to memorize endless precedence tables versus not having any implicit precedence at all.


> By that logic, we ought to have parentheses around every mathematical expression as well.

Yes we should. Some math notations are simply crazy; they make it hard to follow the semantics.

The one thing that turns me off math is the notation.

A lot of math papers don't explain their notation; that's another problem.


> Python needs a piece of whitespace on the left of the correct size and tab/space constituency, and a newline on the right.

Good luck getting your lisp code merged with mismatched tabs/spaces or everything aligned to column 0.


Better, I wouldn't call it elegant though. But better than other Lisp example in this thread.

Though realistically you're destructuring in the same way just with implicit vs explicit structure.

Practically speaking there's no difference but CL has a less clear syntax.


Or maybe it will invite them to try it out themselves?

I think the 'which is clearer' thing is up to personal choice and experience. And personally, often when I'm programming in one not-Lisp, I'm often bitten by things which would have been explicit in Lisp, and the other way around - sometimes I really wish that different things looked different, as in Perl.


An great thing about Lisp/s-expressions, overlooked in this article, is that your code is divided hierarchically into little chunks that can be slurped, barfed, cut, paste and rearranged trivially with structured sexpr editors like Paredit or Smartparens.

"Take just this subexpression of a larger expression, move it into the parameter list of the function call five lines down" is annoying using almost any text editor with languages like C or Python. It takes about a half-dozen composable keystrokes for sexprs in Emacs with Paredit.


This sounds easy with the "%" movement in vim. Put the cursor before the opening paren of the subexpression you want to move, "d%", then go to the target and "p".


  (defun move-ball (ball distance screen-width)
    (zapf (ball-x ball)
          (mod (+ distance %) screen-width)))
In TXR Lisp:

    (defun move-ball (ball distance screen-width)
      (placelet ((it (ball-x ball)))
         (set it (mod (+ distance it) screen-width)))
Trivial exercise: zapf macro expanding to the placelet form.

TXR Lisp's anaphoric operators like ifa and conda use placelet, so that their "it" can be a place referring to the original:

   ;; decrement (ball-x ball) place if it exceeds 15:

   (ifa (> (ball-x ball) 15)
     (dec it))


By the way, placelet isn't macrolet. In the above, the (ball-x ball) place form is evaluated once to determine the place which becomes aliased by the it symbol. The two occurrences of it in the (set it ...) form do not cause multiple evaluation of (ball-x ball).


I would still suggest editing AST direcltly https://www.youtube.com/watch?v=g0tAVjwuc1U text syntaxes brings complexities, a lot.


You should submit that project as a Show HN, that is seriously cool.


Maybe this link http://cirru.org/ , what do you think?

I wrote about it actually, Cirru Editor's demo is hard for trying out https://medium.com/cirru-project/stack-editor-programming-by...


This project is in the same vein. There are no syntax errors.

http://unisonweb.org/2015-05-07/about.html#post-start



I like Lisp, but i really hate these posts. In Lisp you can do some powerful stuff, but these posts (which come up) are just trying to promote the language by being overly clever. Don't try to be overly clever when programming.

Also this is stupid:

  ; Have to type out `=` three times
  x = 10
  y = 20
  z = 30
  
  ; But only one `setf` required
  (setf x 10
        y 20
        z 30)


It seems worth pointing out that credit for the name zap should go to Arc. (Unless it already existed somewhere else even before that?)

https://github.com/arclanguage/anarki/blob/15481f843d/arc.ar...


I'd like to see a service that lets people play with grammar specs interactively, with working snippets.


And all I can think is that all of this would be more elegant in scheme. In particular, scheme's cut macro makes it practical to re-order args, as opposed to implementing the % expansion used here. And it composes.

Or you could always just used readtables to implement your own version of the Clojure lambda shorthand. I'm not clear on why Steve didn't do that. It's a more effective alternative to implementing the % syntax in every macro.


Articles like these remind me that Lisp uses the perfect number of tokens.


swap! in Clojure is analogous to callf.

(swap! x + 5) atomically increments x by 5.

(swap! x concat [3 4]) atomically concats the seq x and the vector [3 4].


I'm not Lisp guy.. Please tell me what's that new syntax means in that context?

I see it as just new functions, not language extension.


They're not functions, they're macros: functions that are called with their arguments unevaluated, and return code to be evaled in the environment of the call.


You can view macros as functions that run at compile time and that take code and return code instead of values


Does this article improve as it goes on? I got to:

> 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

Which is both wrong, and pointless, and it didn't seem worth continuing.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: