"Oh god, syntactically significant white space!"
Python feels a bit like a toy Lisp with all the adult parts hidden.
I'm not really speaking for LISP, but in my projects what really bothers me are people that don't have common sense when indenting / formatting their code and so they start using IDE or build plugins, using common conventions and formatters that will never be able to understand the meaning of the code being formatted, making a total mess of other people' carefully crafted code. And we go round and round on this.
I don't advocate writing cryptic code. Of course I prefer code that is easy to read. That's a major basis for my opinions on syntax.
Do you think Python's syntax is cryptic and hard to read, because of the lack of end parens? Or did you just misunderstand my point completely?
If a lisp dialect had a large, vibrant ecosystem on the scale of Python or Javascript, then a lot of people would see the parens, see the advantage of having them, work their way through it and eventually get used to them and even embrace them because they see a real payoff.
That's not the case with lisp dialects. The major payoff with lisp dialects is macros. But for most people, it isn't obvious why they are important. They are something you have to use to really get.
So the intellectually reasonable position for most people is to either ignore lisp or laugh at it. Meanwhile, you see endless articles like this posted over the years (not knocking the article itself), trying to explain lisp in a kind of reverence and terminology usually associated with religious texts. "Hey, brother, you just need to see the light".
I've been doing quite a bit of Clojure coding lately and I love it. I just can't see a lisp ever catching on. Actually, the project I'm working on is a lisp that does not look like a lisp but compiles down to Clojure. The parens aren't really necessary for lisp's best feature, as seen by Elixir. Here's a bit of my language:
" " join([1, 2, 3]) count println"the intellectually reasonable position for most people is to either ignore lisp or laugh at it"
I said that such a position has nothing to do with "intellectual reasonableness" because it's barely an intellectual position at all. There is no propositional content; there is very little thought involved at all; it's a default "meh" position that is more accurately labelled "laziness."
I used to be a HUGE Perl fan, and discounted Python for years. I figured that I already knew Java, and since Python and Perl were effectively similar, it was a waste of my time to study both. I now regret that. As formative as that was for my growth as a programmer, I've loved coding in Python more than almost any other language -- and I delayed that discovery by a decade because I wrote it off as "effectively the same as" Perl.
Sure, it was pragmatic to focus on one language that I was learning and using. But the reasons I did so were flawed, IMO.
it's joining the vector 1,2,3 with a space to make "1 2 3 ", right? so what is that count doing there? I'm not saying that lists are the end all be all of syntax (clojure has vectors for a lot of things specifically to reduce the parens), but you need something there to make it clearer
If you are familiar with Python, it more or less is equivalent to something like:
" ".join([1, 2, 3]).count().println()
Which, imo, reads much better than: (println (count (join " " [1 2 3])))
Of course, Clojure has the threading macro, but this language emphasizes it. f(x).sin().log()
instead of log(sin(f(x))). x.f().sin().log()
and that is exactly what they type on their calculators:[3] [1/x] [sin] [log]
(either that, or I am getting old. Modern graphing calculators may have different input modes)
log(sin(f(x)))) -> (log (sin (f x)))
They don't have to evangelize function composition being indicated by nesting.too easy languages to conflate a lot of logic into large side effectful statements. FP forces you to separate layers, and deforestation (when available) helps reclaiming space/time costs.
But there are syntactic schemes besides s-expressions that allow for good flexibility. For instance, in my programming language Earl Grey[1], I use this very simple sugar: `a b: c` <=> `a(b, c)`. So you can use this todo syntax if you want, and it will just work:
todo "housework":
item priority(high): "Clean the house."
item priority(medium): "Wash the dishes."
item priority(medium): "Buy more soap."
Interestingly this has one advantage over s-expressions: whereas Lisp editors usually need to specify a special highlighting/indent policy for keywords like `let`, here editors can just highlight/indent `a b: c` indiscriminately.I also think a limited number of infix operators are worth defining in most languages, and I don't mean arithmetic operators (although I'm okay with them). I mean ubiquitous or easily repurposed operators like assignment/declaration, lambda, pairing, or commas. In my view, Lisp-like languages hold to standards of purity that are unreasonable (don't get me wrong, they are quite usable, just not optimal according to any useful metric).
(todo "housework"
(item (priority high) "Clean the house.")
(item (priority medium) "Wash the dishes.")
(item (priority medium) "Buy more soap."))
Yours looks nicer, but if I wanted to operate on your todo there, it's not obvious how just by looking.With the lisp, it's obvious: There's a list of 5 elements. The last three are 'items', each of which is a list of 3 elements.
The benefit of s-expressions isn't just that they're flexible in what you can write with them, but that they're easy to manipulate programmatically, and their structure is immediately obvious.
Not to mention Lisps, especially the new ones, throw obviousness out the window with merry abandon. Let's look at Clojure. You think it's obvious how to operate on Clojure? Here: `(let [a 1, b 2] (+ a b))`. What is that? I've mostly coded in Scheme, I don't know what square brackets are supposed to do. Vectors? How do I manipulate them and what are they doing in my let? And what does the comma do? Oh, nothing? Then why is it there? And if the comma means nothing are you telling me that if I want to list the names that are being defined by this form, I need to extract the even-indexed elements of a vector? Sure I can do that, but don't tell me you care about ease of manipulation.
Anyway, sorry for the rant ;)
I think the point I really want to make here is that sure, s-expressions are quite simple, but if I can explain how s-expressions work in a minute, and how my expressions work in five minutes, this isn't really a matter of ease of manipulation, this is a matter of laziness. If a language's source-to-AST rules can be fully explained in one or two paragraphs or a small table, and can be easily remembered, then that's all that matters. I think that `a b: c` <=> `a(b, c)` is trivial enough that it does not have any meaningful impact on ease of manipulation.
(todo "name" (item ...) (item ...) (item ...)).
Basically the symbol todo is enough of a clue about the structure, and the pure syntax can determine most of the rest, with possibly a modicum of keywords here and there. (todo housework ;; symbols for naming!
(:high "Clean the house")
(:medium "Wash the dishes")
(:medium "Buy more soap")) todo [
housework
high: "Clean the house"
medium: "Wash the dishes"
medium: "Buy more soap"
]
And this is so easy to validate & use via the parse dialect that comes with Rebol.Here's a validation example:
todo-rule: [
set task-name word!
some [
set item-priority set-word!
set item-desc string!
]
]
>> parse [bad] todo-rule
== false
>> parse [incomplete high:] todo-rule
== false
>> parse [incorrect high: "item1" "erggh!"] todo-rule
== false
>> parse [housework high: "foo" medium: "bar" medium: "baz"] todo-rule
== trueFor example
a := {1 + 2 - 10, 10, {1, 2}}
is just Set[a, List[Plus[1, 2, -10], 10, List[1, 2]]] >> 1 + 2 * 3
== 9
The Rebol interpreter see this as: >> multiply add 1 2 3
== 9
Functions in Rebol have fixed arity, both add and multiple take two arguments. You can express the above with parens if you want to make it clearer: >> multiply (add 1 2) 3
== 9
Rebols infix operators have no precedence they just work left to right (like Smalltalk). Use parens where needed! >> 1 + (2 * 3)
== 7 (todo "housework"
(item :priority high "Clean the house.")
(item :priority medium "Wash the dishes.")
(item :priority medium "Buy more soap"))
Your syntax certainly has advantages, but one can easily prefer S-expressions in this case without appealing to vague values like "aesthetic purity," nor are there any blatantly obvious "practical" downsides to using a more fully parenthesized syntax.
The last time I tried to learn Lisp (prior to reading this) was in like 2003/4 from some CLISP tutorial that scared me away from having nothing but math examples and zero explanation of the philosophy or design. It felt like an esoteric and purely academic language, and I brushed it off as not worth my time. (I was never really put off by the parens, and now I love them. Never understood that aspect of Lisp discourse. Once you learn ParEdit[1] you don't want to go back.)
Now I link this page whenever I want to explain to someone why there are "all these parens". It's dated, though, and associating s-expressions with JSON and JavaScript would probably be a more contemporary approach. Maybe I should rewrite this article for a modern developer audience.
[1]: http://danmidwood.com/content/2014/11/21/animated-paredit.ht... "GIFs of ParEdit in action"