I've been hearing claims my entire programming career about how Lisp is supposedly "superior" to mainstream programming languages, but I've never seen a concise code example that actually demonstrates this.
For instance, it's easy to demonstrate how Rust is superior to C: Just show a short piece of code where an array is returned from a function. In C, this will involve raw pointers and manual memory management with all associated safety and security pitfalls, whereas in Rust you just return a `Vec` and everything is taken care of. Simple, obvious, real-world superiority.
How does such an example for Lisp look like? I'd love to see 10 lines of Lisp that show me something that:
Lisp is absolutely superior and you should absolutely learn it but you'll never get concise examples as to why. Only jokes and anecdotes.
How do you fix a waterlogged smartphone? Put out a bowl of rice, which attracts an Asian guy who will repair it for you.
Languages have pedigrees. If you pretend like your company is enamored with javascript you'll get people who love fedoras and call themselves Ninjas. Big teams, single function libraries, lots of code shipped - move fast and break things. Cats pawing at Macbook keyboards. Mumble rap.
If you pretend you love Haskell you'll attract mathematicians in elbow patches. Great stable code will sporadically appear once every couple of years seemingly at random. Genius solutions to neat problems that have nothing to do with what the company is actually trying to accomplish. Ents. Classical music.
If you pretend to love lisp you'll attract people who read PG essays and will quit to start their own companies. Maybe they'll help you close out some tickets in Jira before they bounce if they can get your Rube Goldberg monstrosity working on their laptop. Honey badgers and hamsters. U2.
If you pretend to love latin you might get elected PM.
If you actually learn a few orthogonal languages to cover the very finite amount of paradigms you'll eventually come to realize they are all crap.
I genuinely like Rust, but it's pretty amazing that you managed to write a comment on this article and make it about Rust. Peak HN :-)
Re Lisp's superiority - Lisp was certainly a superior language when it was devised many decades ago, but over time much of its comparative advantage has been absorbed by other languages. Starting in the 90s when productive, GC'd scripting languages like Python started being prominent this trend accelerated.
I can iterate dozens of times between #2 and #3 in the time of a single incremental production build in rust (debug builds are not useful for gathering profiling data).
Generally speaking it takes less than a second to recompile and load a source file, and it can be done while the program is running.
We all know about memoize, but let's say I want to define a global hash-map, where the keys are actual pieces of code and the value the result that would be evaluated when executed. Something like this:
Which can then be used like this in a trivial way, just passing code because code is data:
(let ((path "~/foo")
(tags (cache `(git-tags ,path))))
(format t "Tags of ~a~% ~{- ~a~%~}"
path tags))
Sure, something like this is possible in other languages, but having done macros in languages such as Rust and Nim, it involves such a verbose and syntax soupy way of dealing with the AST that I don't feel like reaching for those abstractions that often. I'd rather just write boring code, and most consider this a feature.
Nim has `quote do:` with a kind of ghetto quasiquoting as well as genAST (and other things) to lessen the burden, but it is always simpler to write boring code (and better unless you have a burning need for The Fancy).
One way to rephrase objections to "all those parens" of Lisp is that the most common style of using it makes it necessary to "write boring code 'in AST'", if you will, and not even in a very nice, commonly accepted 2-dimensional tree notation.
I always wonder how different the history of prog.langs would be if early on one of the many indent/offside rule based 2-D notations had become popular with "boring code" writers in Lisp and not eschewed by "fancy macro writers" in Lisp.
Considering Lisp was here first, shouldn't the real question be "why use Rust/C++/Python when there's Lisp?" You can't even create a real closure in Rust.
I'd love to see 10 lines of Rust that showed me something that:
#![allow(arithmetic_overflow)]
fn main() {
let x = 1073741823;
println!("x = {}", x*3);
}
# cargo build && cargo run
thread 'main' panicked at 'attempt to multiply with overflow', src/main.rs:4:24
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
To be fair, Rust is improving over time, as of I think last year you now have to explicitly have that first line to allow the overflow? This behavior is somewhat annoying to replicate in Lisp if you aren't familiar with type declarations and suppressing the debugger:
(defun main ()
(let ((x 1073741823))
(declare (type (signed-byte 32) x))
(format t "x = ~a~%" (the (signed-byte 32) (* x 32)))))
(handler-case
(main)
(simple-type-error (e)
(format *error-output* "Panicking because of ~a~%" e)
(uiop:quit 1)))
# sbcl --script main.lisp
; file: /tmp/main.lisp
; in: DEFUN MAIN
; (THE (SIGNED-BYTE 32) (* X 32))
;
; caught WARNING:
; Derived type of (* COMMON-LISP-USER::X 32) is
; (VALUES (INTEGER 34359738336 34359738336) &OPTIONAL),
; conflicting with its asserted type
; (SIGNED-BYTE 32).
; See also:
; The SBCL Manual, Node "Handling of Types"
;
; compilation unit finished
; caught 1 WARNING condition
Panicking because of Value of (* X 32) in
(THE (SIGNED-BYTE 32) (* X 32))
is
34359738336,
not a
(SIGNED-BYTE 32).
A bit more work and you could muffle the compilation time warning too. As for how important this is, I'unno, personally I prefer to have my math Just Work by default -- (expt (expt 2 64) 64) or #I((2^^64)^^64) -- and I like by default being given the chance to fix things and continue/restart via the debugger rather than panic.
This a trick question because 10 line programs are trivial. Please show me a high performance hardware-accelerated 3D game engine written in lisp. Or a web browser.
I'd say the most satisfying experiences I have with lisp are a cross between clean abstractions and light code-golfing. To call out specific approaches, I'd highlight anaphoric macros, code-walking via (symbol-)macro-let [1], and the freedom to control when expressions evaluated (generally at read-time, compile-time, or run-time) for optimization [2][3].
Yeah, there is nothing complicated about returning a static array in C.
C is a much simpler language than many people assume -- of course, it can get really hairy and complicated especially when you need to do dynamic memory management, but that's not what the GP was asking for in this case.
> whereas in Rust you just return a `Vec` and everything is taken care of
The downside is that you just performed a hidden heap allocation. If automatic memory management is desired, a garbage-collected language might have been the better choice in the first place.
I have no idea what you can or can't easily do in Rust. Here is something many languages can't do succinctly, without closures and code-as-data. From Paul Graham's book, On Lisp, modified to work with Lisp code as keys.
(defun make-dbms (db &key (test #'eql))
"Make a database, db should be a list. Test determines what keys match."
;;Three closures in a list to make a database.
(list
#'(lambda (key)
(rest (assoc key db :test test)))
;;add
#'(lambda (key val)
(push (cons key val) db)
key)
;;delete
#'(lambda (key)
(setf db (delete key db :test test :key #'first))
key)))
(defun lookup-dbms (key db)
"Return the value of an entry of db associated with the key."
(funcall (first db) key))
(defun add-dbms (key val db)
"Add a key and value to db."
(funcall (second db) key val))
(defun del-dbms (key db)
(funcall (third db) key))
which uses a Lisp to define itself. This means roughly that if you understand enough Lisp to understand this program (and the little recursive offshoots like eval-cond), there is nothing else that you have to learn about Lisp. You officially have read the whole language reference and it is all down to libraries after that. Compare e.g. with trying to write Rust in Rust where I don't think it could be such a short program, so it takes years to feel like you fully understand Rust.
Indirectly this also means that lisps are very close at hand for “I want to add a scripting language onto this thing but I don't want to, say, embed the whole Lua interpreter” and it allows you to store user programs in a JSON column, say. You also can adapt this to serialize environments so that you can send a read-only lexical closure from computer to computer, plenty of situations like that.
Aside from the most famous, you have things like this:
1. The heart of logic programming is also only about 50 lines of Scheme if you want to read that:
3. The object model available in Common Lisp was more powerful than languages like Java/C++ because it had to fit into Lisp terms (“the art of the metaobject protocol” was the 1991 book that explained the more powerful substructure lurking underneath this object system), so a CL programmer could maybe use it to write a quick sort of aspect-oriented programming that would match your needs.
> 3. The object model available in Common Lisp was more powerful than languages like Java/C++ because it had to fit into Lisp terms (“the art of the metaobject protocol” was the 1991 book that explained the more powerful substructure lurking underneath this object system), so a CL programmer could maybe use it to write a quick sort of aspect-oriented programming that would match your needs.
In addition to that, Lisps are sufficiently flexible that before CLOS itself was developed people were extending Lisp in Lisp to try out different object oriented models that fed into what became CLOS. That's hard to accomplish in most other languages, if it's even possible without going to a third party tool or digging into the compiler itself.
Lisp is both low-level and high-level at the same time. Common Lisp has more than the power of modern Python and Go combined, and the language is concise - it has about a tenth of Python's size.
Just as examples, Common Lisp supports arbitrarily-long integers, low-level bitwise operations like popcount, rational and imaginary numbers as well as, say, POSIX file operations or easy calling into C functions. It has things like list comprehensions, pattern matching, and dictionaries, and full Unicode support since a long time.
It supports both procedural and functional-style programming, and is, like Rust, a child of the language families which stem from the Lambda calculus, where everything is an expression. The latter is an extremely valuable property because you can replace any expression in lisp code with a function or its value, even if it is an if-statement.
It has still facilities which other languages do not have, like built-in support for symbols, which are used similar to interned strings and keywords in Python.
At the same time, Common Lisp is extremely mature. For example, it is possible to define and use error handlers, which is a generalization of exceptions, and is useful in library code. Or while other languages have only local and global variables, nothing in-between, Lisp allows to define parameters, which are global values, that however can be modified in the scope and call stack of a certain function call, similar as environment variables can be inherited and changed in sub-processes of a program.
And it compiles to quite fast native code, which Python can't.
Here is an introduction to Racket, which is a dialect of Scheme - I think it shows quite nicely the uniformity and simplicity of Lisps: https://docs.racket-lang.org/quick/
Lisp is functional programming. You can leverage the functional programming paradigms to write more correct code.
Also Lisp makes it easier to not repeat yourself. It's shorter to create functions, even macros, leading to the ominous "DSL" rabbit hole.
Another superpower is that it has no predefined keywords or operators. So you can redefine everything to whatever unicode you want. Including other languages and writing systems. It's a lot harder to write a programming language/compiler that uses another natural language idiomatically using something more similar to C or Python.
In about one line of Lisp we can make an object with referential cycles in it, without declaring that we would like to abandon safety. We can have that object printed in a notation from which a similar object will be recovered, with the same cycles in the same places. All of this matters in practice.
My relatively amateur take is that the REPL and the debugging experience seem powerful. I would like to know how they compete with other languages.
The REPL is the center of everything and it enables you to change functions on a running program. Tracing a function (shows function arguments on every call) is a simple as calling trace(function-name).
If a program crashes, it does not really crash... it enters some debug mode which offers possible resolutions, including change the function that failed.
The REPL can trivially show you the assembly code of individual functions. You can also add declarations for each function with hints for the compiler (and then check the size of the resulting assembly).
I believe this series of articles highlights some of the debugging features.
i can program my numerical-heavy program in SBCL with much better interactivity and debugging than python can offer and with a much much better performence
as far as writing the actual code, lisp syntax allows me to perform structural editing which to me is just on another level
but as with all things in life, you should try before you buy
"For instance, it's easy to demonstrate how Rust is superior to C: Just show a short piece of code where an array is returned from a function. In C, this will involve raw pointers and manual memory management with all associated safety and security pitfalls, "
Typedef the array, return that. One pitfall, it's not resizable.
Well, you need to think that LISP was a thing already before 1960. The only competitor at the time was FORTRAN. Even C was introduced more than 10 years later.
LISP had garbage collection and was designed for symbolic manipulation. Given that programs were just list of symbols, it was fully meta, from the beginning. This gave it a raw power that was decades ahead of time. Even nowadays, this malleability give Lisp languages the power to provide as libraries things than in most languages would require the modification of the language itself. For example, there is library for Clojure (a popular modern Lisp which runs on the Java and Javascript virtual machines) that adds type support. Think about that. Yes, I know. Mypy adds types to Python, but in the case of Clojure the language and runtime didn't need to be touched at for the library to work. You can basically do whatever you want as a library, because the core of the language is so powerful.
Another distinct characteristic of Lisps was the possibility to treat programs as living things you can just "talk to" through the REPL. Programs are developed in a more "conversational" way than with languages such as Java, Rust, etc. Some would say that this is a superpower and other would say that it's of marginal value. It just depends on the personal preferences.
Now, we are in 2022. Obviously, languages have evolved a lot and they are ridiculously more advanced that FORTRAN. Lisps don't have a clear killer feature that can't be found in some other languages and actually they normally lack some convenient things. There is no type system for Lisps that it's practical, convenient and with tooling support. That is a big disadvantage on an era where programs are big beasts normally done by several people mostly gluing together a bunch of libraries with big APIs that you need to explore somehow. Typed languages with accompanying IDE tooling (i.e. having a language server) offer a much quicker and effective way to develop than spending the day reading API docs.
Now, should you learn a Lisp in 2022? Well, I think there are some advantages of doing so. They have a lot of historical value and their simplicity and power are quite instructive, I'd say. Playing a bit with some Scheme (or Racket) can be very fun. If you are curious about Lisps and also functional programming, I'd suggest learning some Clojure. It's a very nice language and it really changes how you think about things, especially if you haven't been doing "hard" functional programming before.
Clojure general approach and concrete libraries as Reitit, Malli or Specter really can change how you look at things and give you a deeper understanding of other characteristics of your other languages of choice. It's a bit like learning some Japanese if you are a German or French speaker. It can help you understand, for example, how unnecessarily complex your verbal system is and how unnecessarily complex Japanese numbering system is. If you are a fish, it's difficult to understand what water is unless you get out of it. Maybe Lisps can be this breath of fresh air.
SBCL probably does more type checking that one thinks. It catches many useful type errors and warnings, especially since we get them instantly, after we compile a function with a keyboard shortcut.
Then we have the new Coalton library, that brings ML-like type checking on top of CL.
(and yes CL still has killer features, and no one brings all of them together!)
Amazing! Ferris is loved and Tölva's art at that site is so nice and funny. Very cool and appropriate that she's also managed to have "Rust" as part of her name.
Hylang is a great way to dip your toes into Lisp style languages IMHO since you have the entire python ecosystem at your fingertips. It was very eye opening to rewrite some scripts in Hy. I'm not sure if I'm ultimately a fan of lisp, but I had a lot of fun learning Hy a while back after discovering it in another HN post.
Giving people the ability to create more useless unsupported DSL in a world where people think YAML dialects in the CI was a good idea will damage Python in the long run.
And I say that while I wished I could use macro several times in Python because the syntax was lacking.
I'm likened to agree. As much fun as syntactic macros in python would be, and despite all the doors it would open, it would really kick up the potential complexity. Heck, folks were complaining about pattern matching and the walrus operator.
I mean maybe the council will go with it, but I'm bearish.
I do really like the idea of jit macros and zero-overhead decorators though.
I’m contemplating pushing for it in 3.12, but it’s probably more than a year’s worth of work to implement and shepherd the PEP through. So 3.13 is more likely.
And I’m not sure it won’t get shot down anyway. It’s a big step for Python.
Much like Clojure is lisp on the Java virtual machine, Hy is a lisp on Python. There are definitely plenty of ways in which lisps vary and I've heard Hy tends to follow some Pythonisms more closely than other lisps may follow their host languages.
Hy (or “Hylang”) is a multi-paradigm general-purpose programming language in the Lisp family. It’s implemented as a kind of alternative syntax for Python. Hy provides direct access to Python’s built-ins and third-party Python libraries, while allowing you to freely mix imperative, functional, and object-oriented styles of programming.
For instance, it's easy to demonstrate how Rust is superior to C: Just show a short piece of code where an array is returned from a function. In C, this will involve raw pointers and manual memory management with all associated safety and security pitfalls, whereas in Rust you just return a `Vec` and everything is taken care of. Simple, obvious, real-world superiority.
How does such an example for Lisp look like? I'd love to see 10 lines of Lisp that show me something that:
1. I can't easily do in, say, Rust.
2. Actually matters in practice.
How do you fix a waterlogged smartphone? Put out a bowl of rice, which attracts an Asian guy who will repair it for you.
Languages have pedigrees. If you pretend like your company is enamored with javascript you'll get people who love fedoras and call themselves Ninjas. Big teams, single function libraries, lots of code shipped - move fast and break things. Cats pawing at Macbook keyboards. Mumble rap.
If you pretend you love Haskell you'll attract mathematicians in elbow patches. Great stable code will sporadically appear once every couple of years seemingly at random. Genius solutions to neat problems that have nothing to do with what the company is actually trying to accomplish. Ents. Classical music.
If you pretend to love lisp you'll attract people who read PG essays and will quit to start their own companies. Maybe they'll help you close out some tickets in Jira before they bounce if they can get your Rube Goldberg monstrosity working on their laptop. Honey badgers and hamsters. U2.
If you pretend to love latin you might get elected PM.
If you actually learn a few orthogonal languages to cover the very finite amount of paradigms you'll eventually come to realize they are all crap.
If you want to code, code. Don't talk.
{ ⊃ 1 ω ∨ . ∧ 3 4 = +/ +⌿ 1 0 ‾1 ∘.θ 1 - ‾1 Φ″ ⊂ ω }
Re Lisp's superiority - Lisp was certainly a superior language when it was devised many decades ago, but over time much of its comparative advantage has been absorbed by other languages. Starting in the 90s when productive, GC'd scripting languages like Python started being prominent this trend accelerated.
For a nice discussion of this see https://norvig.com/Lisp-retro.html
1. Gather assembly level profiling information
2. Redefine & recompile a function
3. Gather new assembly level profiling
I can iterate dozens of times between #2 and #3 in the time of a single incremental production build in rust (debug builds are not useful for gathering profiling data).
Generally speaking it takes less than a second to recompile and load a source file, and it can be done while the program is running.
not just functions, but whole classes too
We all know about memoize, but let's say I want to define a global hash-map, where the keys are actual pieces of code and the value the result that would be evaluated when executed. Something like this:
Which can then be used like this in a trivial way, just passing code because code is data: Sure, something like this is possible in other languages, but having done macros in languages such as Rust and Nim, it involves such a verbose and syntax soupy way of dealing with the AST that I don't feel like reaching for those abstractions that often. I'd rather just write boring code, and most consider this a feature.One way to rephrase objections to "all those parens" of Lisp is that the most common style of using it makes it necessary to "write boring code 'in AST'", if you will, and not even in a very nice, commonly accepted 2-dimensional tree notation.
I always wonder how different the history of prog.langs would be if early on one of the many indent/offside rule based 2-D notations had become popular with "boring code" writers in Lisp and not eschewed by "fancy macro writers" in Lisp.
I'd love to see 10 lines of Rust that showed me something that:
1. I can't easily do in Lisp.
2. Actually matters in practice.
[1]: https://letoverlambda.com/index.cl/guest/chap5.html#sec_
[2]: Compiler macro chapters aren't free, but I guess read-time is covered here https://letoverlambda.com/index.cl/guest/chap4.html#sec_1
[3]: https://irreal.org/blog/?p=809 / https://web.archive.org/web/20140711171755/symbo1ics.com/blo...
C is a much simpler language than many people assume -- of course, it can get really hairy and complicated especially when you need to do dynamic memory management, but that's not what the GP was asking for in this case.
Deleted Comment
The downside is that you just performed a hidden heap allocation. If automatic memory management is desired, a garbage-collected language might have been the better choice in the first place.
Indirectly this also means that lisps are very close at hand for “I want to add a scripting language onto this thing but I don't want to, say, embed the whole Lua interpreter” and it allows you to store user programs in a JSON column, say. You also can adapt this to serialize environments so that you can send a read-only lexical closure from computer to computer, plenty of situations like that.
Aside from the most famous, you have things like this:
1. The heart of logic programming is also only about 50 lines of Scheme if you want to read that:
https://github.com/jasonhemann/microKanren/blob/master/micro...
2. Hygienic macros in Rust probably owe their existence to their appearance in Lisps.
C2 asks the same question here: https://wiki.c2.com/?LispShowOffExamples with answers like
3. The object model available in Common Lisp was more powerful than languages like Java/C++ because it had to fit into Lisp terms (“the art of the metaobject protocol” was the 1991 book that explained the more powerful substructure lurking underneath this object system), so a CL programmer could maybe use it to write a quick sort of aspect-oriented programming that would match your needs.
4. Over there a link shows how in 16 LOC you can implement a new domain-specific language to define and run finite state machines: http://www.findinglisp.com/blog/2004/06/automaton-cleanup.ht...
In addition to that, Lisps are sufficiently flexible that before CLOS itself was developed people were extending Lisp in Lisp to try out different object oriented models that fed into what became CLOS. That's hard to accomplish in most other languages, if it's even possible without going to a third party tool or digging into the compiler itself.
Any recommendations on good resources for learning Lisp to a degree that this program is understandable?
Just as examples, Common Lisp supports arbitrarily-long integers, low-level bitwise operations like popcount, rational and imaginary numbers as well as, say, POSIX file operations or easy calling into C functions. It has things like list comprehensions, pattern matching, and dictionaries, and full Unicode support since a long time.
It supports both procedural and functional-style programming, and is, like Rust, a child of the language families which stem from the Lambda calculus, where everything is an expression. The latter is an extremely valuable property because you can replace any expression in lisp code with a function or its value, even if it is an if-statement.
It has still facilities which other languages do not have, like built-in support for symbols, which are used similar to interned strings and keywords in Python.
At the same time, Common Lisp is extremely mature. For example, it is possible to define and use error handlers, which is a generalization of exceptions, and is useful in library code. Or while other languages have only local and global variables, nothing in-between, Lisp allows to define parameters, which are global values, that however can be modified in the scope and call stack of a certain function call, similar as environment variables can be inherited and changed in sub-processes of a program.
And it compiles to quite fast native code, which Python can't.
Here is an introduction to Racket, which is a dialect of Scheme - I think it shows quite nicely the uniformity and simplicity of Lisps: https://docs.racket-lang.org/quick/
Deleted Comment
Also Lisp makes it easier to not repeat yourself. It's shorter to create functions, even macros, leading to the ominous "DSL" rabbit hole.
Another superpower is that it has no predefined keywords or operators. So you can redefine everything to whatever unicode you want. Including other languages and writing systems. It's a lot harder to write a programming language/compiler that uses another natural language idiomatically using something more similar to C or Python.
The REPL is the center of everything and it enables you to change functions on a running program. Tracing a function (shows function arguments on every call) is a simple as calling trace(function-name). If a program crashes, it does not really crash... it enters some debug mode which offers possible resolutions, including change the function that failed. The REPL can trivially show you the assembly code of individual functions. You can also add declarations for each function with hints for the compiler (and then check the size of the resulting assembly).
I believe this series of articles highlights some of the debugging features.
https://malisper.me/debugging-lisp-part-1-recompilation/
There is also a story called "debugging code from 60 million miles away".
Other things that I did not manage to explore yet are the macros, which allows you to create your own domain specific languages.
My general impression so far is that it is a really powerful language, for lonely hackers. :)
as far as writing the actual code, lisp syntax allows me to perform structural editing which to me is just on another level
but as with all things in life, you should try before you buy
Typedef the array, return that. One pitfall, it's not resizable.
LISP had garbage collection and was designed for symbolic manipulation. Given that programs were just list of symbols, it was fully meta, from the beginning. This gave it a raw power that was decades ahead of time. Even nowadays, this malleability give Lisp languages the power to provide as libraries things than in most languages would require the modification of the language itself. For example, there is library for Clojure (a popular modern Lisp which runs on the Java and Javascript virtual machines) that adds type support. Think about that. Yes, I know. Mypy adds types to Python, but in the case of Clojure the language and runtime didn't need to be touched at for the library to work. You can basically do whatever you want as a library, because the core of the language is so powerful.
Another distinct characteristic of Lisps was the possibility to treat programs as living things you can just "talk to" through the REPL. Programs are developed in a more "conversational" way than with languages such as Java, Rust, etc. Some would say that this is a superpower and other would say that it's of marginal value. It just depends on the personal preferences.
Now, we are in 2022. Obviously, languages have evolved a lot and they are ridiculously more advanced that FORTRAN. Lisps don't have a clear killer feature that can't be found in some other languages and actually they normally lack some convenient things. There is no type system for Lisps that it's practical, convenient and with tooling support. That is a big disadvantage on an era where programs are big beasts normally done by several people mostly gluing together a bunch of libraries with big APIs that you need to explore somehow. Typed languages with accompanying IDE tooling (i.e. having a language server) offer a much quicker and effective way to develop than spending the day reading API docs.
Now, should you learn a Lisp in 2022? Well, I think there are some advantages of doing so. They have a lot of historical value and their simplicity and power are quite instructive, I'd say. Playing a bit with some Scheme (or Racket) can be very fun. If you are curious about Lisps and also functional programming, I'd suggest learning some Clojure. It's a very nice language and it really changes how you think about things, especially if you haven't been doing "hard" functional programming before.
Clojure general approach and concrete libraries as Reitit, Malli or Specter really can change how you look at things and give you a deeper understanding of other characteristics of your other languages of choice. It's a bit like learning some Japanese if you are a German or French speaker. It can help you understand, for example, how unnecessarily complex your verbal system is and how unnecessarily complex Japanese numbering system is. If you are a fish, it's difficult to understand what water is unless you get out of it. Maybe Lisps can be this breath of fresh air.
Then we have the new Coalton library, that brings ML-like type checking on top of CL.
(and yes CL still has killer features, and no one brings all of them together!)
They are both drawn by Karen Rustad Tölva!
https://www.aldeka.net/
I doubt anyone uses hy in production for completely other reasons, would love to be proven wrong.
And I say that while I wished I could use macro several times in Python because the syntax was lacking.
I mean maybe the council will go with it, but I'm bearish.
I do really like the idea of jit macros and zero-overhead decorators though.
Python already has 'enough' metaprogramming support, the last thing we need is a sudden fad of libraries defining their own gratuitous DSLs
And I’m not sure it won’t get shot down anyway. It’s a big step for Python.
https://github.com/Calysto/calysto_scheme
It's not blazing fast, but it's Scheme -- not paren-y syntactic sugar around Python.
https://docs.hylang.org/en/stable/whyhy.html
Shortened from there:
Hy (or “Hylang”) is a multi-paradigm general-purpose programming language in the Lisp family. It’s implemented as a kind of alternative syntax for Python. Hy provides direct access to Python’s built-ins and third-party Python libraries, while allowing you to freely mix imperative, functional, and object-oriented styles of programming.