I experience that even on newer lisps like Clojure, maybe even more so thanks to all the extra symbols. But surprisingly, I can pick up a Rust/Swift/etc. source code and quickly understand what it does, even if I don't fully know said languages.
Example: Even a simple calculator in Scheme looks terribly confusing to me:
http://www.lambdanative.org/
The ##flonum? is a "Primitive Procedure", see here:
https://gambitscheme.org/latest/manual/#Primitive-Procedures
which has no place in an application program like this; it's hard to imagine why the author of the calculator didn't just use Gambit's flonum? function. (In standard Scheme, the inexact? function would be used.)The code is using Gambit-specific functions with a fl prefix like fl+, fl/ and fl, which provide floating-point-specific arithmetic functions. Standard Scheme doesn't do that; it just has +, / and for all numbers and mixtures of numbers.
It looks like the author of the code has a naive view of floating-point; he or she seems to be under the impression that if you multiply a number by 1E10, then add 0.5, take the floor and divide by 1E10, that it will thereby truncated to 10 digits after the decimal after which there are reliably zeros, such that number->string will never produce more than 10 digits after the decimal. That would be true in a decimal-based floating-point representation, not in a binary one.
So that whole expression (number->string (fl/ (flfloor (fl+ (fl* (flo n) 1.0e10) 0.5)) 1.0e10)) is doing something dodgy that should be done in an entirely different way.
It also uses longer function names and single letter variable names. That's a strange mix.
One problem with the code though is the layout/formatting. This is not what I would expect as a Lisp user.
I would expect to be able to see more block structure from the code layout:
(define (number->neatstring n)
(if (not (##flonum? n))
(number->string n)
(let* ((s (number->string
(fl/ (flfloor (fl+ (fl* (flo n) 1.0e10) 0.5))
1.0e10)))
(sl (string-length s))
(b (substring s 0 1))
(e (substring s (- sl 1) sl)))
(string-append (if (string=? b ".") "0" "")
(if (string=? e ".")
(substring s 0 (- sl 1))
s)))))
If I have a function call with 2 args I don't want: (long-function-name long-expression-1
long-expression-2)
because that's what macros with a body would be formatted.I would want either
(long-function-name
long-expression-1
long-expression-2)
or (long-function-name long-expression-1
long-expression-2)
Both tell me that both expressions are on the same level syntactically and semantically: both are function arguments.
I have seen very new programmers put three or four blank lines in a row over and over again, just so that they have the visual space to let them think about a small fraction of their code at a time without getting distracted by the code before and after. Lisp programmers tend to make their functions smaller, simpler, and purer instead, and end up with blank lines only between top–level forms such as functions.
Scheme has longer-function-names than Common Lisp.
Also Scheme use a more functional style and skip the intermediate variables. Sometimes the name of the intermediate variables are like self documentation and make the code easier to read.
Lisps are sometimes bemoaned for having terse identifiers in the language core like rplacd, pairlis, mapcar, cdr, nconc, setq, ...