11 ms·
Playing with Syntax
- expression 10y agoI 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.
- loup-vaillant 10y agoSo 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, `=`.
- kazinator 10y agoIt 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).
- loup-vaillant 10y agoIndeed, 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)
- kazinator 10y agoIf 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.
- PrettyButUgly 10y agoI'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.
- gluggymug 10y agoYou forgot "else:"
- PrettyButUgly 10y agoFunny that. In my head, I parsed it as two (odd) statements under one if statement. More like when than if.
- kazinator 10y agoThat's why I mentioned rotatef: (rotatef a c b) ;; a <-- c <-- b ;; `--->----->-'
- qwertyuiop924 10y agoWell... (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.
- Avshalom 10y agoon 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)
- kazinator 10y agoLisp 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
- orbifold 10y agoThat 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.
- cle 10y agoI 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.
- eximius 10y agoI 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! :(
- JohnStrange 10y agoHaving 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... ;-)
- qwertyuiop924 10y agoShen is probably the closest you'll get. http://shenlanguage.org 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...
- chriswarbo 10y agoA notable Lisp without GC is Linear Lisp http://home.pipeline.com/~hbaker1/LinearLisp.html http://home.pipeline.com/~hbaker1/LinearLisp.html
- qwertyuiop924 10y ago...Hang on a second, that sounds a heck of a lot like NewLisp's memory management model, with the disadvantages thereof.
- defgeneric 10y agoThere's also Carp: https://github.com/carp-lang/Carp https://github.com/carp-lang/Carp
- kazinator 10y ago
- moron4hire 10y agoThese 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.
- JamesLeonis 10y ago> 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.
- deleted 10y ago[deleted]
- int_19h 10y agoThere 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.
- quickben 10y agoWell, it's similar to math and that was there long before lisp or C
- kazinator 10y ago(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))
- kazinator 10y agoBy 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).
- rch 10y agoI'd like to see a service that lets people play with grammar specs interactively, with working snippets.
- akkartik 10y agoIt 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.arc#L1311 https://github.com/arclanguage/anarki/blob/15481f843d/arc.ar...
- qwertyuiop924 10y agoAnd 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.
- userbinator 10y agoIt 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.
- junke 10y agoWell, 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-assembly-code-breadboard/ https://www.pvk.ca/Blog/2014/03/15/sbcl-the-ultimate-assembl...).
- abritinthebay 10y agoEvery 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.
- lispm 10y agoIn CL without the need for destructuring: (setf (values a b c) (values 10 20 30))
- icebraining 10y agoThat'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
- lispm 10y agoWhat about all those superfluous commas? I also dislike languages where expressions don't have visible bounds.
- icebraining 10y agoA line break isn't visible? Not by itself, I suppose, but in the code, it's hard to miss.
- lispm 10y agofor what expression does the line break stand, exactly?
- smallnamespace 10y ago
- jiyinyiyong 10y agoI would still suggest editing AST direcltly https://www.youtube.com/watch?v=g0tAVjwuc1U https://www.youtube.com/watch?v=g0tAVjwuc1U text syntaxes brings complexities, a lot.
- _mhr_ 10y agoYou should submit that project as a Show HN, that is seriously cool.
- tluyben2 10y agohttps://github.com/Cirru/cirru-editor https://github.com/Cirru/cirru-editor but yes do a Show HN.
- elbear 10y agoThis project is in the same vein. There are no syntax errors. http://unisonweb.org/2015-05-07/about.html#post-start http://unisonweb.org/2015-05-07/about.html#post-start
- jiyinyiyong 10y agoMaybe this link http://cirru.org/ 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-functions-a961f1d9555c https://medium.com/cirru-project/stack-editor-programming-by...
- reikonomusha 10y agoI 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.
- defgeneric 10y ago> 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.
- mcphage 10y agoDoes 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.
- LeanderK 10y agoI 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)
- xytop 10y agoI'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.
- qwertyuiop924 10y agoThey'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.
- PuercoPop 10y agoYou can view macros as functions that run at compile time and that take code and return code instead of values
- deleted 10y ago[deleted]
- tomjakubowski 10y agoAn 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.
- majewsky 10y agoThis 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".
- divs1210 10y agoswap! 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].
- ilovecomputers 10y agoArticles like these remind me that Lisp uses the perfect number of tokens.