Lisp parentheses are the exact same way, in my experience. You hate them until you spend a few weeks writing a Lisp and then they just fade into the background, never to bother you again. Then once you learn paredit, it becomes painful to use other languages again! (And now with parinfer, I think about them even less still) Incidentally, in my experience, Clojure typically has the same amount of parentheses as typical OO languages, in some cases fewer, its just that the opening paren is moved one word to the left.
Also, to anybody new to Lisp: try an editor with rainbow parentheses.
I've gone through several period of straight Lisp parens, and several periods of using a preprocessor. I believe in committing to muscle memory before deciding; I've oscillated back and forth between Querty and Dvorak, for example. I'm more committed to psychological experiments than anyone who claims the parentheses haters are newbies who haven't tried.
Study counting in higher mammals: One finds that "one, two, three, many" is core to all of us, and higher systems are strapped on like the multiple layers of vision mechanisms. Humans can't deal with the tiger tails at the end of lisp expressions. But machines can? Yes, but machines can also handle inferred parentheses. The complainers about parentheses complainers want off-the-shelf support. Real programmers write their own support.
Lisp looks best with indentation to help infer parentheses. Use a pipe "|" to open a parenthesis that auto-closes at the next ")" or the end of the line. Use a dollar "$" (borrowed from Haskell) to open a parenthesis that auto-closes when the indentation returns. Lisp written this way is stunningly beautiful poetry, cleaner than any of the dozens of languages I've programmed in. The remaining parentheses actually matter, so one pays attention to them.
I've spent years each way, back and forth. Inferring parentheses is better, and one can write any tool to follow this syntax.
Can't or won't?
I'm not being facetious, here. I just can't imagine any reason one _couldn't_ work with it, once the syntax is learnt. There's nothing about lisp-like syntax that's inherently hard to learn.
Why is lisp special?
Once the brain grasps this, a whole layer of complexity just vanishes. Furthermore, not having to use punctuation between items is just heavenly. There's so much less noise compared to Python, and Python isn't even a particularly noisy language.
(But whitespace sensitivity has always struck me more as a way to enforce specific whitespace conventions than anything else, and even as far back as Pascal I've been religious about maintaining indention in code. It's easy to do with a decent editor (or not) and helps the readability immeasurably.)
I studied Lisp and Scheme at university and that’s where it had to remain for me: academically entertaining mind puzzles in data structures and algorithms. It’s just not productive or easily graspable from one coder to another.
The following does exactly what you'd expect, and there are even cooler ones like `some->` or `as->`.
(->> some-nums
(map square-root)
(filter even?)
set
count)
This is what lisp buys you, dead simple rules for syntax, with the option to add syntax with macros. If you made a mistake in syntax design, deprecate the lib and make a new macro, it need not be a feature of core language for all eternity.There's a reason for it, for sure, but it's surely a part of the learning curve.
Remember that what is being passed through the threading macro is not the request, but the handler function itself. Each middleware takes the old handler, wraps itself over it to do things before and after the old handler. The threading macro is not a representation of the path your request will take, it is a way to build up a chained function that represents your final handler, which will then receive the request. Suddenly the ordering makes perfect sense :P
The parallelism isn't actually that powerful, and in 2019 there's very little to set it apart. The building blocks are great, e.g. immutability everywhere, but few of the built-in primitives are actually usable (hidden unconfigurable thread pools etc). Stack traces still regularly horrible. Libraries are regularly abandoned. ClojureScript integrating with npm etc is more stress than you'd like. Core development is haphazard and proudly so. Types are nice, and spec is still up in the air, and slow, and not widely implemented, and will probably be rewritten several more times before being abandoned. Hiring is harder than a dozen other languages.
All that said, there is still amazing stuff happening. I hold out some hope that Dragan's work might spark a bigger stats/ML community on top of Clojure, for example:
Every day I can still write code and think "yay, that's cute", and be thankful for the dynamism and clarity it can enable. But I wouldn't recommend it to anybody, for anything really, and I feel sad realising that.
As far as the stack traces go, I have a hard time understanding why tooling like Cider's stacktrace filtering isn't wider spread: in Cider, when a stack trace pops up, it's one or two clicks to hide most of the things I don't care about and, yet, that information is still available for people working on the compiler or with Java interop.
In terms of stack traces, it's not just the location in your code that you care about, it's the nature of the error. In lieu of spec being ubiquitous or anybody really checking their arguments ever, you're still left looking at 20 lines of stack beyond your own code to realise you've passed the incorrect type or structure somewhere. And even with spec you need additional libraries to actually yield human error messages in a lot of cases.
I feel the opposite. I avoid any language without a Lisp-like syntax, since its absence is a serious handicap which unnecessarily complicates code and makes it less readable.
I also much prefer the Lisp idiom of using meaningful function and variable names, vs the one-letter names and crypitic operators that are so common in Haskell, OCaml, and SML.
Macros, while easy to abuse, are kind of a game-changer; the ability to add new semantics to a language make it borderline impossible for me to go to any language that doesn't have a good macro system.
I started with Clojure, which I still really enjoy, but due to how much I grew to love the `define-syntax` system in Chicken Scheme, that's become my latest weapon of choice.
As for the operators: they are a visual language. We do not speak our punctuation aloud in English, and we use small marks to guide the reader's eye. Often, Haskell operators as similar, and there is a definite pattern.
f $ x means "f applied to x"
f <$> x means "map f over x"
In general, the <> around an operator lifts it to work over a structure. Similarly:
x & f means "f applied to x"
x <&> f means "map f over x"
For the applicative operators like <star>, star> and <star (where "star" should be an asterisk but I can never work out the formatting on this forum), the operators "point" at the values being retained. And so on.
Indeed, if you really want fun, try Hackett: a Haskell variant implemented in and for Scheme.
Plus, many editors without any plugins can jump between matching braces; so moving around the code is very quick compared to Python (unless you have a Python plugin that has block movement shortcuts).
I'm not letting that stand ;-)
In my experience Python's whitespace generally removes sources of scope/block errors compared to parenthesis and curly-based languages
> unless you have a Python plugin that has block movement shortcuts
Not sure what this means. The only support I need in an editor is the ability to use tab and shift tab on blocks of text to change the indentation level. It's visually obvious when code is correctly indented.
Not when the beginning of the code block isn't on screen.
And more to the earlier point, configuring your editor to jump to the beginning from the end or the end from the beginning of a particular block of code going to be trickier than the equivalent in even vim for lisp, which is simply %
(And vim isn't even a particularly good editor for lisp, but out of the box unconfigured, it's better at lisp than it is at python.. % isn't even lisp specific functionality, lisp just happens to use balanced parenthesis which is something nearly every serious text editor happens to handle in a reasonably robust way, because balanced parens are generally useful in many languages, including python!)
The length of your code blocks terrify me. :)
If you want scary, check out GNU's implementation of `cat`. I think they hit ten nested indentation levels. Though, because that's C, you can at least jump between the beginning and end of each level by having your editor jump to the matching {}.
In Emacs you can use C-M-f and C-M-b to move back and forth over / select paren/bracket/brace delimited blocks, or in vi you can use %, both with 0 configuration in a language-unaware state. If you're editing C or Lisp or whatever, these generic commands are useful. Editors don't tend to have move-to-next-less-indented-line (and similar) commands outside of Python modes or similar, in my experience, and even if they did, they'd need a bit more work than that to work properly because of indenting subexpressions.
Not always, in my experience. I’ve lost many hours to debugging only to realise some piece of code was misindented. It doesn’t happen often and usually when code is being moved around, but when it does, its been painful.
I almost buy that it encourages simpler blocks, since complicated ones are impossible to parse out rather quickly. But my later reviews shows it doesn't remove them.
You still get the great “execute this block of code in the repl” with the parens too.
It's really not that bad! Here are some insights into the parens!
1. I've converted Java code over to Clojure and found that it has less parens per file.
2. They're still there, they're just in a different place. `System.out.println("")` vs `(println "")`
3. There are some great tools to help with the Parens. If you're using Intellij, Pareninfer and Rainbow parens are two extremely helpful tools.
Indentation is significant.
Just replace '(' with space/tab and ')' with "deindent". Done.
I prefer to let this idea of required indentation to FORTRAN.