It warms my heart to know that astronauts are using LISP in space because they want to. On the other hand, I wonder how practical it is. I've more or less converted from LISPy langs to Python for real work because I was tired of re-inventing wheels and wanted to use libraries like Pandas that truly have no equals in the functional world.
Edit: Question: How many LISPers truly use homoiconicity? Can you share any uses of it in real world software?
I've been very impressed with Racket's standard library. While yes, for certain domains, you'll go further faster with Python and its libraries, Racket has got a surprising amount of stuff in it and it's well-documented!
(I bring up Racket because DrScheme has since become DrRacket, and the aside on the article explains that the astronaut in question was working through How to Design Programs, which uses Racket as it's teaching language. Also, this is Shriram Krishnamurthi's blog, and he's one of Racket's principle creators.)
Scheme is a (very nice) standardized programming language, with many different implementations and dialects.
DrRacket (previously called DrScheme, before rebranding) is a kind of IDE specialized for teaching intro-to-programming.
DrRacket primarily supports various dialects of Scheme, including a series of ones that incrementally add language features, as a student progress through a textbook.
You can also implement your own programming languages so that they work with DrRacket, and researchers sometimes build other tools on top of DrRacket.
One unfortunate thing is that, for decades, many students have been taught using Scheme or DrRacket -- and were given the impression that Scheme is a toy language, suitable only for homework exercises.
Inside DrRacket you can use the Student languages that are somewhat simplified, or you can use the full version of Racket or even Typed Racket if you like typed languages.
I think the main problem is that debugging is enabled by default. You get a nice error exactly in the spot of the error, but the program is much slower. (I usually use DrRacket with debugging disabled, and when some error appears I enable debugging and run the program again.)
drscheme was a tool made by a groupe of lisp/schemers from academia I believe (felleisen et al.) but with time they started to hold different views on what a good language/good scheme should be, they made racket which is more functional by default (gross simplication). DrScheme thus became DrRacket. But it's vastly the same idea.
Clojure fills this gap rather nicely, and you can always fall back on Java libraries, if necessary. If you haven't given it a try, I recommend it. Rich Hickey has put a good deal of thought into its design.
Furthermore, you will have a much easier time finding companies that use Clojure than any other Lisp (and where they don't, you stand a better chance of convincing management to allow it at companies that make heavy use of languages that target the JVM).
I actually find Racket easier to work with than some mainstream languages. It just gets all the basics right. You don't need huge libraries like Pandas because the base language is reasonably fast and comes equipped with the powerhouse "for" macros that make most data processing easy.
Macros are used all over the place in Racket and implement a lot of the core language. One of my favorite ones lately is the threading/pipeline macro.
I think JS has been debating about this for several years now, but implementing this in Racket is not only easy (available as threading-lib if you don't want to write it) but better than if it were a core language construct. It can be better since using a syntax-parameter, you can rename any of the "keywords" necessary to implement said macro as a user of said macro if you want to.
Also, since Racket macros track source line information, you don't need to choose between good error messages and performance.
I also feel like Racket strikes a nice balance between dynamic and static. Function and variable names being misspelled is a compile time error, though types aren't checked at compile time. However, Racket had a very sophisticated contract system to help address that as well. If you really want something more thorough, there is even Typed Racket which has easy interop with the rest of Racket and is actually a sound type system unlike TypeScript. (There is apparently a way to bypass the runtime checks when calling untyped code, but this is done for performance and is uncommon, not like with TS where "any" lets you do whatever you want).
I should also mention that while Typed Racket is a full language and does some pretty involved stuff besides just basic macros, people have also been working on a library that lets you create new type systems at the macro level by specifying the type reduction rules.
One final advantage of macros in Racket's case is that by implementing language features as macros, it is easier to see what the overhead of some particular feature is. Looking at fully expanded Racket code is like looking at the disassembly in C except names are mostly preserved, stuff just moves around and new stuff names come in as well.
It's not bullet proof, but I've seen people describe reader macros and macros for semi advanced stuff on old mailing list. To them it was a natural too to turn annoyances into natural pleasurable work (think not having to deal with xml/html as strings). There's a couple of papers on some japanese research lab that showed how to turn your common lisp code into pascal with the generic pretty printer.
These sort of things seem to be trivial in the lisp world, whereas until a few years ago it was alienating topics for most of the industry (since we had the transpilers fever and jsx for instance)
Really? I would think both R and Julia covers much the same functionality as Pandas. I don’t know R well enough but Julia can certainly be used in almost exactly the same ways as LISP with respect to being functional.
There are even REPL plugins which lets you write Julia code using LISP syntax.
I suspect the fact that Chuck Moore, the inventor of Forth, started his career at the SAO [2] and worked at NRAO later. The other one is the extreme compactness of the runtime, and his chip designs that got "radiation hardened" versions. The RTX2010 was used in ESA's Philae/Rosetta mission [3].
[2] In Forth, The Early Days, Moore writes "At MIT [1958], John McCarthy taught an incredible course on LISP. That was my introduction to recursion, and to the marvelous variety of computer language. Wil Baden has noted that LISP is to Lambda Calculus as Forth is to Lukasewcleicz Postfix".
> Question: How many LISPers truly use homoiconicity?
Every time someone uses a Lisp macro they're taking advantage of homoiconicity. Homoiconicity is a characteristic of the language, not something somebody "uses."
Even for hobbyist stuff, Lisp’s lack of libraries can be frustrating. I wanted to build some useful things as I was teaching myself Common Lisp, but there either was no library at all for what I wanted to do, or on Quicklisp there was just some bare-bones library that offered some meagre
functionality but warned that most things hadn’t been written yet.
I guess thats what something like clojure is supposed to fix? Interop with Java to give you access to that world? Not sure how that plays out in the real world though
Interop with Java to give you access to that world?
Yes but it's a big time sink, you waste time thinking on how to wrap library, and for some libs you end hitting some blockers cause Clojure's interop is not that smooth, ending up writing two wrappers, one in Java(to make the lib usable from Clojure without pulling your out) and one in Clojure.
And I'm talking about ad-hoc use of Java libs, writing a more idiomatic wrapper is a lot work. The Clojure ecosystem is pretty much abandonware at this point, even for high profile libraries.
In languages with big ecosystems, you simply just use the lib and get work done, no cognitive overhead, no time sink.
In my experience, quite well, though I have not had to reach for Java libraries that often, either because Clojure equivalents exist, or because someone else had already put a wrapper around the Java library.
(import '(java.time Duration Instant))
(import '(java.util Date))
;; static method call with / (slash)
(def now-instant (Instant/now))
;; instance method call with dot. First arg is the instance
(def five-minutes-ago (.minus now-instant (Duration/ofMinutes 5))
(def now-date (Date/from now-instant))
Implementing interfaces and extending classes are a bit more involved, but I haven't had to do much of that in my experience, and when I have had to, that's been pretty good, too.
Here's an example implementing the AWS Lambda RequestStreamHandler interface
Showcasing Clojure's Java interop with the java.time library is IMO pretty disingenuous. That's a pretty good modern library with a great API conformed completely of immutable classes, most Java libs are not like that.
I can only show things I use, and these are things I do every day. (I also wanted a succinct example to post on the forum that people might be familiar with.) Please do share counter examples, as I'm genuinely interested.
One of the more complicated things I've had to do was parseBest as part of DateTimeFormatter, which takes a TemporalQuery as an argument. It has a solution like this:
I've also worked with JDBC libraries to wrap driver-specific features. JDBC drivers have been around for a long time, and driver-specific stuff can vary in how well it's designed. I've also used jna through Java from Clojure when I was just starting out, which wasn't all that hard for a newbie to the language.
From your comments in other Clojure-related threads we've participated in, it sounds like your experience is much different from mine, but often lacks in specific examples. Without seeing those, it's hard to know what you're describing. If you think Clojure isn't the bees knees, that's perfectly fine. I think readers would benefit from knowing specifics of what you've found frustrating.
If you're using proxy, you pay a large performance penalty, and you can't call protected superclass methods or access protected superclass fields without using reflection.
gen-class is a truly awful thing to use.
Even when type hinted, defrecord fields all have type Object.
primitive type hinting, and corners like the type hinting in deftype/reify (where you have to either type hint everything or nothing)
Having to type hint with ^"[Lcom.example.project.package.Class;" is foul.
No niceties like SAM conversion, No integration with java.util.function interfaces.
There are some things that aren't as easy. And sometimes it's a better choice to drop into Java to provide a shim between the package you're interacting with and Clojure to make the interop easier, or when performance matters.
Regarding hinting for defrecord fields and deftype/reify, do you have references where this has been a problem for you? It's not something I've run into, but that's just my experience.
As for java.util.function interfaces, I just did this within the past couple of weeks, which worked for my use case:
For SAM conversions in general, there's https://github.com/ajoberstar/ike.cljj . I haven't used it myself, but perhaps the pattern used there would work in you.
It sounds like what you've worked on has been relatively performance sensitive, which can definitely a thing. So perhaps some of these interop suggestions won't work for you.
Again, thanks for taking the time to be more explicit about what you've found frustrating.
My take on this is that Clojure provides host (Java) interop, not replacement. Part of that is if you're doing really Java-y things, such as extending abstract classes, that's probably better done in Java. These class-based inheritance/extension concepts aren't core to what Clojure is about. Polymorphism and extension is addressed by other means in Clojure.
A comparison: I know little about Kotlin, but I wouldn't be surprised if it has a better story about Java class extension and interface implementation as I think one of its goals is to be in some ways a better Java. I don't think that's a goal that Clojure shares. If those are features you are looking for, Clojure may not be a good fit.
Per the article’s side bar, the astronaut was working through “How to Design Programs”, a book by several of the Racket contributors, including the article’s author, Shriram Krishnamurthi. As far as teaching languages go, scheme does have the advantage of having almost no punctuation to mess up. And DrRacket is a wonderfully beginner-friendly environment.
I guess Hacker News might qualify as a real-world use of scheme/homoiconicity? I believe it runs on Arc, which runs on Racket. https://github.com/wting/hackernews
Probably not many. Due to the need for performance, everything that matters will end up being compiled to a more efficient representation. Functions and complex data types could be represented as lists but no real world Lisp implements them that way anymore since it's better to have a dedicated function or hash table types.
In Racket it still gets used all the time with macros. Macros are can be used like a customizable JIT compiler if you need the performance. In Racket's case, you can even run the expansion step separately like a compiler since it separates macros out as a distinct execution phase.
Functions aren't represented as lists at runtime, but you still get a lot of control via continuations. You can even serialize continuations to send them over the network or save it to disk. That does have security implications, but it is flexible and convenient.
Homoiconicity has nothing to do with actually storing code as lists when compiled. However, Common Lisp source code is defined in terms of lists (with text being a separate serialisation thing) and every time you use a macro, or backticks, you depend on such definition enabling homoiconicity.
Edit: Question: How many LISPers truly use homoiconicity? Can you share any uses of it in real world software?