The opening paren is simply relocated to the other side of the function name or keyword.
What we're experiencing is just a cognitive bias which causes us to prefer the more familiar over the less familiar ([the mere-exposure effect, also called the familiarity principle](https://en.wikipedia.org/wiki/Mere-exposure_effect)).
I've never used a Lisp-family language, but I find the reaction against parentheses to be overblown.
The reading order of the code is also _consistent,_ rather than the frequent switching between infix notation, prefix notation, and postfix notation which we have to learn and parse in most languages outside the Lisp family. This is a benefit which deceptively looks like it is _more_ complicated, despite being simpler to parse visually (and otherwise). Another example of the familiarity principle at work.
This tired trope needs to die. Yes, there are more parentheses, because lisp also uses parentheses where other languages use [] or {} or ;.
The real thing is that people from C-like languages are used to seeing different block markers for different constructs. It takes effort to read Lisp coming from other languages, because those other languages have a richer symbol vocabulary. Learning to read code without those symbols is like reading English where all punctuation has been replaced with a single space. Sure you ll get there eventually but it s a very cheap straw man argument to pretend that the only complaint people have about Lisps is the positioning of the parentheses.
I recently started using Clojure and I’ve used languages like C#, JavaScript, and Python a lot. My two cents is that a Clojure-like language should try to embrace the aesthetics of a white-space language like Python, but use the parens as clues for scopes or blocks. So much could be done with formatting rules that just make parens easier to scan without some extra IDE highlighting or something.
The best part of parens is that you can try to pick a consistent format, but ya know that sometimes doesn’t happen because everybody likes to use parens differently lol.
For example, the sum of a list (not running or cumulative sum, but total sum. Should equal 10, not 1, 3, 6, 10):
Lisp:
(+ 1 2 3 4)
J/APL[0]: +/1 2 3 4
Python: Sum = sum([1,2,3,4])
print(Sum)
They all do the same. I prefer the conciseness of J/APL and Lisp in this case, and the application of a function over the list or vector. The beauty of the REPL is that you see the result without a 'print' statement.Solving problems in these other languages will influence how you program in your base language as well, and usually for the better. I am also guilty of syntax bias. I prefer LFE (Lisp Flavoured Erlang)[3] over Elixir and Gleam. Gleam[1], another language that runs on the Erlang VM (BEAM), had a more ML syntax, but then it chose to join the syntax popularity contest and move to a more Algol/C/Rust syntax. I prefer vanilla Erlang over it too.
[0] https://www.jsoftware.com/#/ [1] https://gleam.run/ [3] https://lfe.io/
> I feel like if I used it, it would atrophy my skills in other more traditional languages.
was not the case for me at all. If you go into a text editor and remove all the parentheses, I find that's how Lisp programmers tend to see Lisp, (function argument) isn't that far from function(argument).
Learning Lisp has only improved my skills as a programmer, after getting ideas like code as data, macros, let over lambda, CLOS and the metaobject protocol. It's a simple model that to me shows how other languages have picked an abstraction and stuck with it, but Lisp has all the tools to implement those abstractions and more.
More mainstream languages are great at focusing the developer, and that makes them very practical. It is amusing though to watch many of the "new features" in languages come out even though Lisp had them years ago.
The simplicity and symmetry of the syntax is a big part of that love for me. Being able to manipulate lisp code as lisp data, using the full power of the language to do so, is just brilliant.
Janet looks lovely! Looking forward to the book.
If your skills in other languages are tied to a syntax then you never had any skills to begin with. I've used pretty much every syntax (and too many languages) out there and the only difference I've ever found is that ML syntax is nicer for automatically curried functions and LISP syntax is much nicer for meta-programming. The rest is effectively all down to paradigms, runtimes and libraries.
Lisps don't arbitrarily look weird—there's a deep, principled, elegant reason for it; Lisp code represents how the code will be evaluated in the most direct way, without relying on (some would say needlessly) complex parsing / precedence rules. There are no surprises and no arbitrary rules to learn. There are no useless semicolons to forget, and you'll never have to wonder if `+=` returns the RHS or the result of the operation (or does it even have a return value?).
You don't have this meaningless distinction where you can't directly reduce with `+` because—ugh—it's not a function, it's an operator. You just say `(reduce + [1 2 3])`.
You never have to do this ugly Ruby stuff...
words.map &:length
# or
words.map { |w| w.length }
...because methods are really just polymorphic functions, but language designers chose syntax that doesn't compose elegantly.You don't have this useless distinction between statements and expressions that limits how you can compose code. You never have to drop down to some ugly, limited tertiary expression form (`COND ? X : Y`) of `if` because—whoops—`if` is a "statement". You just write:
(println (if me? "me" "you"))
Because, duh, we wanted an `if`.What do we gain by adding all of this noise?:
if (is_me) {
println("me");
} else {
println("you");
}
Absolutely nothing. The parens on the conditional, the curly braces, the semicolons, the `else` keyword—they're essentially meaningless incantations to appease the compiler. And we've introduced an undesirable opportunity for the two branches to accidentally diverge over time.But most importantly, our code is written in the data structures of our language. Code as data means we can manipulate code as easily as we manipulate data, which means we can trivially (re-)write code with code (i.e. macros). And not shitty string generating macros, or macros that can only do a handful of sanctioned things—we can write our own control structures in couple lines of code. We can add new abstractions to the programming language from user space.
Wish the language had an `if-not` construct? You can add it with, like, 3 lines of code. Wish functions could have optional parameters? Add it. Wish it had a pattern matching functions like SML or Erlang? Cool. Java-style annotations? Logging that is fully removed when running in high performance mode? A different OO model? Multi-line string literals? String interpolation? A graph of dependent calculations that only get run when used? A more convenient way to load dependencies? It's all easily doable.
I've coded in Lisps (and a dozen other languages) for at least 20 years, and every time I have to use a non-Lisp syntax I just think "wow, these people really missed the boat". It's like having to write math in Roman numerals (would you rather calculate "D + L + IX" or "500 + 50 + 9"?); there's a better way, and that better way has elegant, recursive, underlying design principles that make the ergonomics way better.
But, yeah, it doesn't look like C code. And people seem to be really attached to their C syntax.
println(is_me ? "me" : "you")
which has exactly the same amount of symbols.Of course you can do that simple case with a tertiary operator but:
1. It's a construct that really has no reason to exist (I argue) as distinct from `if`.
2. It doesn't compose with statements.
This duality is primarily what I'm arguing against.
A better example would have been a case statement inside of the `println`:
(println
"Log in by"
(case user-id
0 "root"
1 "local admin"
(format "regular user (id: %d)" user-id)))
In C, you have to introduce a variable for no good reason (or do some non-idiomatic, ugly, nested tertiary operators that get uglier the more cases we have).And even then, you can't just say
user_name = switch { ... }
Because switch is an statement.