I haven't used Lisp professionally, but find the code terribly confusing to read (mostly due to the look of it, I assume), while I can usually follow code of an unknown language reasonably well.
I haven't used Lisp professionally, but find the code terribly confusing to read (mostly due to the look of it, I assume), while I can usually follow code of an unknown language reasonably well.
I used to say that proper indenting helps readability but these days I have departed from the standard indenting and do my own thing.
That said, this guide on formatting lisp is very useful IMO if you want to learn to read lisp code better:
https://link.medium.com/idMRZk93Fdb
I actually wrote it, but now I don’t follow many of its principles :D perhaps because I got a deeper understanding of it. Check it out if and see if it helps
This overloads the subtraction operator. And it makes the code very confusing to read.
And plus, Lisp also uses the hyphen as the subtraction operator.
Lisp has only ~7 special characters: (, ), #, ', ", ` and wite-space. All others are valid as part of an identifier, alone or together. So for example "---" is a valid Lisp identifier, just as much as "aaa".
There is no subtraction operator in lisp. "-" is simply the name of a function in the standard library that implements addition (technically, it might be a special form and not a function, but that's beside the point).
> This overloads the subtraction operator. And it makes the code very confusing to read.
a-b-c in Lisp is unambiguously an operator. Subtraction would be (- c b a) - you can't really confuse these two.
Subjectively, I happen to think that hyphenated-names are much easier to read and type than camelCase or snake_case. I think most of the world agrees as well, since this is one of the more common typographical roles of the hyphen: joining words, such as 'pre-approved' or 'ill-defined'.
There are a bunch of conventions for naming things:
+foo+ ; convention for naming a constant
*foo* ; convention for naming a special/global variable
foo-bar ; convention for a compound name
Other programming languages use FooBar, foo_bar or similar, instead of foo-bar.More naming conventions in Lisp are collected here:
$ foo-bar()
> {
> echo 42
> }
$ foo-bar
42
or Make: OBJ_COMMON := common.o
OBJ-$(CONFIG_FOO) += foo.o foo-tab.o
OBJ-$(CONFIG_BAR) += bar.o
OBJS := $(COMMON_OBJS) $(OBJ-y)
even though foo-bar and OBJ-y look like a C subtraction.C programmers on Unix came up with this stuff themselves.
a-b
is not a problem. As a practitioner, you don't get confused at all when you encounter a variable name that contains a dash, because you don't even read it as a subtraction. Your brain is trained to match only the (- a b)
pattern for that.It’s all preference though. I used to like camelCase when I was learning Java, now I like hyphens. I program both in JavaScript and Lisp and have no issues with both
Users are free to redefine it outright, or to redefine it it multiple ways in different scopes of their application. (Not sure how wise this would be, but not really the point ...)
Not to be offensive, but the question as written seems to imply looking at lisp from preconcieved notions from other languages that probably don't make sense to carry over in understanding lisp.
Which means that you can do this:
(let ((- 5)) ; bind the '-' variable to 5
(- ; call the '-' function
- ; on the '-' variable
17)) ; and the number 17
; this evaluates to -12Your code doesn't seem like a very good idea, even though it is legal.
Why not?
I usually use an 'anaphoric if' macro that binds - to the given value. It seems more clearly 'special' than a name like 'it'—which seems like the canonical name—closer to the perl/raku $_.
CL-USER 5 > (- 1 2 3 4 (third -))
-10I think. Haven’t verified this, but the thought came to my head the other day.
I would say that Javascript was not at all designed to be delivered the way that it is. The design characteristics of JS that allow minification, I think are just happy coincidence.
Consider that Javascript early on was marketed as a companion to Java (applets). Java, of course, used a bytecode representation to deliver programs on the Web from square one. If Javascript had been designed for efficient delivery of "compiled" programs, bytecode would have been the obvious choice.
I think my point is, languages are not generally designed to minify well, because minification is the technique of last resort for efficient delivery.
a-b - a - b
Even though I personally love hyphenated-identifiers in Lisp, I would not want them in any language that has infix operators, whether it cares about minification or not.On the other hand, some advice from Chuck Moore (found in Thinking Forth) that I think about sometimes:
"There are diverging programming styles in the Forth community. One uses hyphenated words that express in English what the word is doing. You string these big long words together and you get something that is quite readable.
But I immediately suspect that the programmer didn’t think out the words carefully enough, that the hyphen should be broken and the words defined separately. That isn’t always possible, and it isn’t always advantageous. But I suspect a hyphenated word of mixing two concepts."
(let ((foo-bar 29))
#i( foo\-bar - 3 * foo\-bar ))I can see others (beginners) getting confused, but it’s something they will very quickly get over. For example I didn’t know JavaScript was case sensitive, but it was a very quick adjustment when I learnt this
Example sentence:
"Examples of sado-masochism - such as Java-influenced languages - are rampant in the field of computing."
Quite readable.
Think about adding a bunch of numbers using infix
a+b+c+d
In prefix it’d look like
(+ a b c d)
You could as many terms in that list.
And as far as the - in indentifiers, it’s called kebab case and I find it very readable and natural to type as well. Also since identifiers are a lot more flexible than other languages you could have predicates such as empty? Rather than isEmpty and so on. Very very friendly and useful. Not trying to convince you or anything but if you give it a chance it’ll click for you.
Its the next level where it gets hard.
Forth, another language with a spartan approach to syntax, gets similar criticism.
It's what helps you find your way around in the forest, especially with editor support that can automagically apply operations to discrete chunks of your code.
That can include doing things like re-defining a function or value on the fly, which is one of the lovely things you can do in many lisps.
...
> That can include doing things like re-defining a function or value on the fly, which is one of the lovely things you can do in many lisps.
Sure, but like I said to lispm, I know enough Lisp to understand both of these points. Respectfully, neither addresses my point about syntax.
At least, things seem that way to me, as someone who knows C and C++ reasonably well but has only dabbled in Lisp.
That said, if you want a real C++ readability challenge, here you go:
int add_one(int x) { return x+1; }
Easy-to-miss undefined behavior is a much greater readability problem than the specific syntax of a language in my opinion.I thought you were going in another direction with that example, the Most Vexing Parse. https://en.wikipedia.org/wiki/Most_vexing_parse
Parentheses are there, because s-expression syntax is not only used for syntax for nested lists, but also for representing code as s-expressions. C / C++ does not have that feature.
These s-expressions in Lisp are data. This means one can also write programs which create programs as data - not as text, but as list structures.
Right, but this boils down to just getting used to Lisp.
> also for representing code as s-expressions. C / C++ does not have that feature.
Sure, I know enough Lisp to understand that, but the point about syntax stands.
Ultimately I think it's just a matter of acclimatisation. C++, Lisp, and Forth, are different enough that if you only know one or two of the languages, you'll likely find the third to be an unreadable mess.
Time to refer to this image again I guess: https://www.thejach.com/imgs/lisp_parens.png (Though that's technically Scheme -- I find Common Lisp more readable and encouraging of smaller functions and modularity. The clean code culture of tiny functions and unit tested everything is not there, though, and a lot of stuff is done by individuals so the benefits of code review aren't often captured.)
`loop` has its own special conventions. People write macros. And so on.
I recommend giving it a shot solely for the fun of it. It's worth the payoff, even if it takes awhile.
Yes there are lots of parentheses, just like there are lots of ampersand, asterisks, curly and square brackets on C derived languages.
It's not a problem with the syntax, though. The problem is that Lisp is so powerful that programmers can and will create very high level abstractions, the language and conventions encourage it, but a side effect of this is that every part of a program has its own DSL. If in addition there is not good enough documentation or a programmer has a knack for obscure elegance over KISS, then this becomes problematic.
In a nutshell, it's perfectly possible to write clean and easy to read Lisp but not many people do it.
Edit: I forgot to say that the code from Mezzano looks like a positive example of readable and clear-cut Lisp code.
The only problems I have are when I'm trying to debug something in macro code, where it becomes too hard to intuitively follow the layers of quoting and unquoting to understand what's going on.
Expressions should be readable in a single flow. Lisp breaks that.
For example:
(+ (* 5 3) (* 3 2))
This is not easily readable because there are breaks in the "flow".You need to go like:
* `+` is going to add things together
* oh, there's a nested expression here, pause...
* `` is multiplying 5 and 3 - cool, back to the previous thing.
oh, another nested thing.
* `` is multiplying 3 and 2 - back again...
done... so where were we?
Here's how a readable expression would look like IMO:
(5 * 3) + (3 * 2)
This reads very naturally:* 5 times 3 - 15, ok go on...
* nested expression... hm, ok.
* 3 times 2 - 6, let's go back now...
* ah right, 15 + 6 is 21... done.
Alternatively:
5 3 * 3 2 * +
* take 5 and 3, multiply: 15* take 3 and 2, multiply: 6
* add them together: 21
Which is what FORTH does. This is almost perfect, but a little hard for humans to follow (though this is exactly what machines want to do) because you still need to keep the stack of values in your head.
I've been studying about this and came up with something in the middle:
(5 * 3) (3 * 2) +
This has the advantage that you the distance from the values in the stack to the point of reading when going left-to-right is optimal: there is no better way to represent this if your objective is to avoid intermediate values.It is similar to FORTH, but optimized for humans to read instead of machines:
* `5 * 3` is 15.
* `3 * 2` is 6.
* `+` the values, which are 15 and 6: 21.
The parens are not required if you use some alternative syntax to mark boundaries, for example:
5 * 3 >> 3 * 2 >> + ;
The `>>` is used to "pipe" the value to the next expression, while `;` is used to terminate the expression. This also has the benefit of showing the reader the direction of the flow. (5 * 3) + (3 * 2)
That's not that great, since it uses infix precedence - either explicitly (here) or implicitly. I need to read into it to understand that a sum is computed and I need to then find out the boundaries of the subexpressions. But the brain can be trained for that, just as it can be trained to read Lisp, Erlang, or even APL code.Nested lists are the base for Lisp syntax. Thus numeric code is just like other ordinary code. Lisp was developed to also do symbolic computations: computing with expressions with symbols.
Thus to read Lisp you need to learn to read syntax based on nested lists and then numeric expressions are just an application of that. Still one is trained to do math with lot's of fancy syntax - fancy syntax you won't find in normal code anyway. For example the typical graphical root notation is mostly not found in programming languages.
If I (as a Lisp programmer) had problems understanding numeric expressions, I'd reformat them:
(+ (- a b) (* 100 (sqrt y) x) (* 50 (expt x 2)))
as (+ (- a b)
(* 100 (sqrt y) x)
(* 50 (expt x 2)))
That's just another way to format a nested list.The main problem is mapping that back to existing external forms which uses mixed infix/prefix/postfix syntax.
(a - b) + (100 * y^1/2 * x) + (50 * x^2)
Lisp allows one to compute this stuff: CL-USER 18 > '#i( (a - b) + (100 * y^^(1/2) * x) + (50 * x^^2) )
(+ (- A B) (* 100 (EXPT Y (/ 1 2)) X) (* 50 (EXPT X 2)))
CL-USER 19 > (let ((*print-right-margin* 30))
(pprint *))
(+ (- A B)
(* 100 (EXPT Y (/ 1 2)) X)
(* 50 (EXPT X 2)))This is not true. I was careful to not use operator precedence as I agree it's bad. I said that the syntax I had in mind reads left-to-right, hence the operators also work that way. So `5 * 3`, assuming `` takes 2 parameters, is all you need to know about the operation (you don't need to check what's next, you have no precedence to worry about).
> to read Lisp you need to learn to read syntax based on nested lists
Exactly! That's why it's so hard. Our brains are not good at keeping several levels of nesting. Any code that is deeply nested is inherently hard to read.
> But the brain can be trained for that,
That's something we should avoid to make programming more approachable. People already have a hard time learning other more important things, so let them use their mathematical intuition for this.
(a - b) + (100 * y^1/2 * x) + (50 * x^2)
You seem to not have fully understood my point. This is not what I am proposing as being better than Lisp.This is what this expression looks like in my proposed notation:
a - b >> 100 * x * sqrt y >> x ** 2 >> 50 * >> + >> + ;
There's zero nesting, but you still have to keep a few intermediate values (that's inherent to the calculation and this approach keeps that to the optimal minimum).I thought it was obvious, but let me point it out explicitly: there is no operator precedence in this notation.
I believe this is fundamentally better than Lisp and C-like languages.
a - b >> 100 * x * sqrt y >> x ** 2 >> 50 * >> + >> + ;
That's a mix of infix and postfix. I don't think writing code like that will be very readable for most people. Humans are much more trained for prefix and infix notations, than for postfix stack operations. We don't see widespread usage of postfix notations in programming languages besides languages like Forth, Postscript or Factor.The Lisp notation has a different reason why it has been invented: it is based on a data structure around nested lists of symbols. That's a very simple and powerful model. code is written as data. Lisp programmers find the code as data idea attractive. Without that, Lisp would have kept the original idea of a conventional syntax.
Non-programmers are not trained for prefix notation. Only Lisp programmers use prefix notation for mathematics, so I don't believe your assertion has much evidence to show for it.
> The Lisp notation has a different reason why it has been invented
Yes, and that reason was not readability. That's what I am trying to show.
> code is written as data
Which is great fun, but most programmers don't need or want that.
Mathematicians use that: cos (sin x)
; Yes, and that reason was not readability. That's what I am trying to show.
Not convincing so far. It turned out that the notation was the most practical (writing, reading, manipulating) for its purpose, otherwise the original syntax would have been used.
> Which is great fun, but most programmers don't need or want that.
Let's bring the fun back into programming.