The only one that comes to mind for more powerful is abstraction over control flow. Instead of delimited continuations, one can go with unification and implict control flow.
What are the increases in power you consider lisp users blind to?
(p.s. first class environments, first class macros are missing from common lisp and scheme, but not from all lisps)
But abstracting has a cost. Sometimes in terms of performance (perhaps mitigated with great effort or a sufficiently smart compiler), but always in terms of cognitive load. A elegant lisp expression is meaningless in isolation. When reading lisp code, you must recursively find and remember the definitions for each token on the page before you can understand what it does. Hopefully the author knew Naming Things is Hard and choose good names. Hopefully the names point you in the right direction. But you can't be sure unless you traverse each definition to its leaves. Lisp programmers are blind to the power and clarity of thought that comes from direct expression - all definitions visible at once, with no indirection.
A lisp programmer might look down on a Java programmer's reliance on IDEs. An IDE is a powerful tool for shoveling mountains of code, but a lisp programmer might say "I can solve this problem without mountains of code". Likewise an apl programmer might look at a lisp solution and say "I can solve this problem without defining any new terms". A forth programmer might say "I can solve this without parentheses". A C programmer might say "I can solve this without a heap". An assembly programmer might say "That entire program is equivalent to a single x86-64 instruction".
By crafting different bespoke DSLs for each new problem, lisp programmers lose the opportunity to hone one DSL to perfection. The knowledge that any problem could be solved with lisp lures them toward the fallacy that every problem should be solved with lisp.
Nah, when I write Lisp programs, I have more of an appreciation for appropriate abstractions, not needless abstractions.
> A elegant lisp expression is meaningless in isolation.
Elegance is usually in relation to something. For example,
(list 1 2 3)
Is more elegant than var list = new LinkedList<Integer>();
list.add(1);
list.add(2);
list.add(3);
> When reading lisp code, you must recursively find and remember the definitions for each token on the page before you can understand what it does.You need to do this for any language. In Java and many other languages, having a "Go to definition" IDE function is very useful.
> Lisp programmers are blind to the power and clarity of thought that comes from direct expression - all definitions visible at once, with no indirection.
Not sure what this is referring to. Do you have an example?
> A lisp programmer might look down on a Java programmer's reliance on IDEs.
Plenty of Lisp programmers use Emacs, which has a great many tools to help developers including jumping to definitions, showing documentation, running and using a step debugger for code, etc. Not sure why Lisp programmers would look down on Java programmers because of an IDE.
> By crafting different bespoke DSLs for each new problem, lisp programmers lose the opportunity to hone one DSL to perfection.
This seems to be a Lisp meme. Just because Lisp can be used to make a DSL doesn't mean all Lisp programs are macro-implemented DSLs. There's plenty of Lisp code that looks just like Java code with function, struct, object definitions and calls, except it uses parens.
Fair, but if your language keeps growing, this task is never done.
> Not sure what this is referring to. Do you have an example?
Array programmers sometimes avoid abstractions because they prefer "idioms" like these[1]. So rather than curate a library of words like:
barchart: {x>\:!|/x}
and have the programmer use the word "barchart", they instead prefer to use the definition itself. The word "barchart" has a specific meaning (here, an ascii "bar chart" of 0s and 1s, showing the relative sizes of the values of input array x), but "{x>\:!|/x}" might be useful for more than just bar charts. This idiom contains smaller idioms like "count til max" (!|/) which in turn contains "max" (|/).Being able to see the code makes it easier to explore and tweak to your specific needs. But more importantly, there are no "official" names for concepts like "count til max". That's just my personal name for it. A python programmer would call it "range". You could come up with your own name for (!|/) that makes perfect sense to you. But that name will probably be longer than its definition, and less flexible.
[1] https://github.com/JohnEarnest/ok/blob/gh-pages/examples/idi...
Maybe this example is relevant. Racket defines procedures `filter` and `map` for `list`. Also provided is `filter-map`, which I assume may satisfy your not-being-direct-expression concern about Lisp. But, `filter-map` exists for a particular reason:
> Like (map proc lst ...), except that, if proc returns #false, that element is omitted from the resulting list. In other words, filter-map is equivalent to (filter (lambda (x) x) (map proc lst ...)), but more efficient, because filter-map avoids building the intermediate list.
So it exists for performance concerns. It also exists in the "Additional List Functions and Synonyms" so it's not like it's being confused for a core, important function like `filter` or `map`. I still write Racket code where I just explicitly have something like:
(~>
(filter (lambda (x) (equal? 'some-value x)) some-list)
(map (lambda (x) (symbol->string x)) _))
without always jumping for a more concise abstraction.And, coming from other languages, DRY (don't repeat yourself) seems quite popular, so I am not sold on this being a Lisp-specific issue.
Writing it that way in the first place reduces the verbosity of the code, making it look better.
Static typing, mutable values with unique ownership.
Linear types are probably not representable in lisp (bad interaction with reified continuations, not great interaction with environment capture), that is something I miss.
This sentiment is at the heart of the blubbiness in question. Just like homoiconicity (relative to something like C) lets you say more about code in exchange for a reduced ability to implicitly move back and forth between syntax and operational semantics, a good static type system lets you say more about computations in exchange for a reduced ability to implicitly move back and forth between denotational semantics and syntax.
Lisp seems less expressive than C until you start thinking of programs as more than just sugar for machine code; ML-family languages seem less expressive until you start thinking of programs as more than just their implementations.
It's tempting to handwave away compile time detection of missing cases in pattern matching as nice but inessential. However it is something which gets leaned on very heavily where it is available. It takes a collection of failure modes out of the mind of the programmer to encourage thinking about other things.
Expressive power is probably inconsistent as a concept. Whether introducing a constraint or removing it increases expressivity depends on what one is trying to express. For example, should code that does not typecheck prevent running code which does? Depends on context - it's deeply annoying during development, but helpful to avoid checking in code which no longer works on the paths you weren't looking at.
Relatedly, I was just looking at this, mostly to see what the fuck possessed someone to do it: https://macropy3.readthedocs.io/en/latest/index.html
Origin of the phrase "Blub Paradox": http://www.paulgraham.com/avg.html