I was always interested in Lua because it was nice and small, but I felt the language itself was a quirky, with some footguns … Also interested in Clojure, but not the JVM.
In my experience Fennel fixes about 90% of the footguns of Lua. About the only ones left are “1 based array indexing” and “referring to a nonexistent variable/table value returns nil instead of being an error”, which are pretty hard to change without fundamentally changing the runtime.
There’s quite a bit of history to how fennel came to be what it is today. It is correct that Calvin (creator of Janet) started it, but it would have just been an experiment in their github if it weren’t for technomancy’s interest in reviving/expanding on it. I don’t know if it is written down anywhere, but Phil did a talk at FennelConf 2021 about the history of fennel, which is the most detailed background for those interested. https://conf.fennel-lang.org/2021
I did a survey a while back about new lisps of the past 2 decades. IIRC the only one to evolve beyond a personal project and have multiple nontrivial contributors but not use Clojure-style brackets is LFE, but LFE was released only a few months after Clojure. It’s safe to say Clojure’s influence has been enormous.
However, Janet seems to take some characteristics of Clojure out of context where they don’t make sense. For instance, Janet has if-let even tho if-let only exists in Clojure because Rich hates pattern matching. Janet also uses Clojure’s style of docstring before arglist, even tho Clojure’s reason for doing this (functions can have multiple arglists) does not apply in Janet as far as I can tell.
Although there’s also the curse of Lisp where the ecosystem becomes fragmented
The other main influence of Clojure is not syntactic at all but rather the idea that a language specifically designed to be hosted on another runtime can be an enormous strength that neatly sidesteps the fragmentation curse.
Ahh very interesting, what were the others? (out of idle curiosity)
I think I remember Carp uses square brackets too.
There’s also femtolisp, used to bootstrap Julia, but that actually may have existed before Clojure as a personal project. It’s more like a Scheme and uses only parens.
I agree the runtime is usually the thing I care about, and interop within a runtime is crucial.
Here’s the ones I found in my survey; I omitted languages which (at the time) had only single-digit contributors or double-digit commit counts, but all of these were released (but possibly not started) after Clojure:
LFE
Joxa
Wisp
Hy
Pixie
Lux
Ferret
Carp
Fennel
Urn
Janet
Maru
MAL
All of these except Urn and LFE were created by someone who I could find documented evidence of them using Clojure, and all of them except Urn and LFE use square brackets for arglists. LFE is still going as far as I can tell but Urn has been abandoned since I made the list.
I was working on this as a talk proposal in early 2020 before Covid hit and the conference was canceled. I’d like to still give it some day at a different conference: https://p.hagelb.org/new-lisps.html
Implicit quoting is when lisps like CL or Scheme treat certain data structure literal notation treats the data structure as if it were quoted despite there being no quote.
For example, in Racket you can have a vector #[(+ 2 3)], without implicit quoting this is a vector containing 5 but with implicit quoting it contains the list (+ 2 3) instead where + is a symbol, not a function. Hash tables also have this problem. It’s very frustrating. Newer lisps all avoid it as far as I know.
Not to take away from Clojure’s influence, just want to mention that Interlisp has square brackets, but with a different meaning. IIRC, a right square bracket in Interlisp closes all open round brackets.
Python has been trying to move toward a re-entrant VM for long time, with subinterpreters, etc. – I think all the global vars are viewed as a mistake. Aside from just being cleaner, it makes the GIL baked in rather than an application policy, which limits scalability.
This kind of API looks suboptimal to me. It would be nice to take something like a lua_State.
The interpreter is thread local in janet, you can actually swap interpreters on the thread too so it doesn’t stop things like rust async from working if you add extra machinery.
The main reason that I use Lua is Sol3. Lua itself is just Smalltalk with weird syntax, but the integration with C++ that you get from Sol3 is fantastic.
Janet calls this fancy serialization “marshaling,” as do many other languages, except for Python, which calls it “pickling.” This fact is not really relevant to this book at all; I just think “pickling” is a really whimsical term.
Great shout-out. Whimsical names are fun and memorable.
https://janet.guide/all has the entire contents of the book on a single page – you could print that to PDF and get a… bad approximation of an ebook. Or save the HTML locally and convert to an epub with pandoc all.html -o book.epub – formatting will probably be awful, though.
This is a good point, about six months ago I got a large-screen e-reader (PineNote) and greatly enjoy reading things on it. I wonder whether there’s a Hugo plugin to translate my blog posts into epubs.
“When someone calls a language modern, it tells you next to nothing about the language, but it tells you a fair bit about the person who said it.”
That said, Racket has a few clunky features due to its age. The class system feels very dated, and the fact that most short list operations only work on lists and not general sequence types isn’t great. The latter is somewhat addressed by the “for” family of macros but IIRC it’s something the maintainers would have done differently if they had a do over.
“When someone calls a language modern, it tells you next to nothing about the language, but it tells you a fair bit about the person who said it.”
YES! I think the term “modern” is a thought terminating cliche. What does it really mean? If you had a “modern” language, write a book about it, describe it as “modern”, and 20 years passes what does the term “modern” mean to readers?
It just shuts down conversations because no one wants argue against it.
Agreed. I’ve dug through too many used bookstores and old libraries full of books with titles like “Modern Pascal Programming For MS-DOS 4.0” to want to use it as a term.
Off topic, but Python 2.7 is frozen in time, extremely capable, and easy to build from source. Most people would turn their noses up, because you’re “not supposed” to use it. But if you have different requirements than most people (300 years), it could be a consideration.
Python 3 is also alive and well – AIs are written in it, and “know” it – so the knowledge to maintain it directly transfers
The main thing that causes rot is the package management tools. So in the past I’ve just copied .py files into my own repo, rather than using any dependency management tools. It’s very stable that way. It also helps you pick dependencies that are small, organized well, and likely to last.
300 years is a very long time. It’d obviously be possible to run python2.7 on a future computer with enough work, but it looks like a lot more work than a smaller language.
If you can think of anything to remove or evict to a library, shrinking Python 2.7 into a smaller language, then I know that the PyPy team would care about this, as they still maintain a Python 2.7 implementation for bootstrapping and cross-compiling PyPy.
I keep thinking about writing an RPython-specific Python 2.7 package manager, but I don’t think it’s a great use of time as long as there isn’t a thriving ecosystem of interpreters written in RPython.
Janet has great tooling, is trivial to embed, and is permissively licensed. The next time you’re considering an extension language or a lisp, do yourself a favor and check it out.
This is very well written and motivated :)
I was always interested in Lua because it was nice and small, but I felt the language itself was a quirky, with some footguns … Also interested in Clojure, but not the JVM.
Janet sounds interesting
In my experience Fennel fixes about 90% of the footguns of Lua. About the only ones left are “1 based array indexing” and “referring to a nonexistent variable/table value returns nil instead of being an error”, which are pretty hard to change without fundamentally changing the runtime.
Hm I’ve seen both fennel and Janet, but didn’t realize until now that both use the square brackets and braces from Clojure
That’s cool, and a sign clojure is influential. Although there’s also the curse of Lisp where the ecosystem becomes fragmented
Both languages were written by the same person if you weren’t aware
Which two?
Fennel and Janet are both from Calvin Rose.
Huh I actually didn’t know that! @technomancy seems to be the defacto maintainer since 2020 or so.
There’s quite a bit of history to how fennel came to be what it is today. It is correct that Calvin (creator of Janet) started it, but it would have just been an experiment in their github if it weren’t for technomancy’s interest in reviving/expanding on it. I don’t know if it is written down anywhere, but Phil did a talk at FennelConf 2021 about the history of fennel, which is the most detailed background for those interested. https://conf.fennel-lang.org/2021
I did a survey a while back about new lisps of the past 2 decades. IIRC the only one to evolve beyond a personal project and have multiple nontrivial contributors but not use Clojure-style brackets is LFE, but LFE was released only a few months after Clojure. It’s safe to say Clojure’s influence has been enormous.
However, Janet seems to take some characteristics of Clojure out of context where they don’t make sense. For instance, Janet has if-let even tho if-let only exists in Clojure because Rich hates pattern matching. Janet also uses Clojure’s style of docstring before arglist, even tho Clojure’s reason for doing this (functions can have multiple arglists) does not apply in Janet as far as I can tell.
The other main influence of Clojure is not syntactic at all but rather the idea that a language specifically designed to be hosted on another runtime can be an enormous strength that neatly sidesteps the fragmentation curse.
Ahh very interesting, what were the others? (out of idle curiosity)
I think I remember Carp uses square brackets too.
There’s also femtolisp, used to bootstrap Julia, but that actually may have existed before Clojure as a personal project. It’s more like a Scheme and uses only parens.
I agree the runtime is usually the thing I care about, and interop within a runtime is crucial.
Here’s the ones I found in my survey; I omitted languages which (at the time) had only single-digit contributors or double-digit commit counts, but all of these were released (but possibly not started) after Clojure:
All of these except Urn and LFE were created by someone who I could find documented evidence of them using Clojure, and all of them except Urn and LFE use square brackets for arglists. LFE is still going as far as I can tell but Urn has been abandoned since I made the list.
I was working on this as a talk proposal in early 2020 before Covid hit and the conference was canceled. I’d like to still give it some day at a different conference: https://p.hagelb.org/new-lisps.html
That link is super cool. What do you mean by “implicit quoting”?
Thanks!
Implicit quoting is when lisps like CL or Scheme treat certain data structure literal notation treats the data structure as if it were quoted despite there being no quote.
For example, in Racket you can have a vector
#[(+ 2 3)]
, without implicit quoting this is a vector containing 5 but with implicit quoting it contains the list(+ 2 3)
instead where + is a symbol, not a function. Hash tables also have this problem. It’s very frustrating. Newer lisps all avoid it as far as I know.Not to take away from Clojure’s influence, just want to mention that Interlisp has square brackets, but with a different meaning. IIRC, a right square bracket in Interlisp closes all open round brackets.
Hm although now that I look, the VM doesn’t appear to be re-entrant like Lua
https://janet.guide/embedding-janet/
Python has been trying to move toward a re-entrant VM for long time, with subinterpreters, etc. – I think all the global vars are viewed as a mistake. Aside from just being cleaner, it makes the GIL baked in rather than an application policy, which limits scalability.
This kind of API looks suboptimal to me. It would be nice to take something like a
lua_State
.The interpreter is thread local in janet, you can actually swap interpreters on the thread too so it doesn’t stop things like rust async from working if you add extra machinery.
The main reason that I use Lua is Sol3. Lua itself is just Smalltalk with weird syntax, but the integration with C++ that you get from Sol3 is fantastic.
Great shout-out. Whimsical names are fun and memorable.
Fun fact. Python used to call it marshalling, but then pickling was added as the better default option.
I love the repl idea, but on the other hand I am yearning for an epub so I can read this from the comfort of my e-reader :P
https://janet.guide/all has the entire contents of the book on a single page – you could print that to PDF and get a… bad approximation of an ebook. Or save the HTML locally and convert to an epub with
pandoc all.html -o book.epub
– formatting will probably be awful, though.This is a good point, about six months ago I got a large-screen e-reader (PineNote) and greatly enjoy reading things on it. I wonder whether there’s a Hugo plugin to translate my blog posts into epubs.
Janet is definitely the most modern looking Lisp I’ve seen.
What about Racket?
“When someone calls a language modern, it tells you next to nothing about the language, but it tells you a fair bit about the person who said it.”
That said, Racket has a few clunky features due to its age. The class system feels very dated, and the fact that most short list operations only work on lists and not general sequence types isn’t great. The latter is somewhat addressed by the “for” family of macros but IIRC it’s something the maintainers would have done differently if they had a do over.
YES! I think the term “modern” is a thought terminating cliche. What does it really mean? If you had a “modern” language, write a book about it, describe it as “modern”, and 20 years passes what does the term “modern” mean to readers?
It just shuts down conversations because no one wants argue against it.
Agreed. I’ve dug through too many used bookstores and old libraries full of books with titles like “Modern Pascal Programming For MS-DOS 4.0” to want to use it as a term.
I’m tempted to create a terrible programming language and name it “Modern” just to try to get people to stop saying this.
You could take an amalgamation of bad features from the last 30 years of “modern” languages. It would probably be a great language!
I’m curious how you would change the Racket class system? Besides the Beta features, it’s not too different from Java or Smalltalk.
Omit it entirely. Classes were a mistake.
Looking forward to reading this. I’m a big Janet fan. Good resources are an important part of a language’s approachability and adoption.
This is one of the languages in my list to look at, for ‘can it be maintained for next 300 years by a single person ?’
(to use in a future-consumable knowledge-management and genealogy-management I keep contemplating :-))
Off topic, but Python 2.7 is frozen in time, extremely capable, and easy to build from source. Most people would turn their noses up, because you’re “not supposed” to use it. But if you have different requirements than most people (300 years), it could be a consideration.
Python 3 is also alive and well – AIs are written in it, and “know” it – so the knowledge to maintain it directly transfers
The main thing that causes rot is the package management tools. So in the past I’ve just copied
.py
files into my own repo, rather than using any dependency management tools. It’s very stable that way. It also helps you pick dependencies that are small, organized well, and likely to last.It’s got quite a complicated standard lib to port to each new operating system.
Well I’d bet on POSIX lasting longer than most languages, because it’s everywhere, and clean slate OSes implement it to get a basic dev environment
CPython C code is messier than Lua C code, with lots of #ifdef, but the POSIX module is pretty small
300 years is a very long time. It’d obviously be possible to run python2.7 on a future computer with enough work, but it looks like a lot more work than a smaller language.
If you can think of anything to remove or evict to a library, shrinking Python 2.7 into a smaller language, then I know that the PyPy team would care about this, as they still maintain a Python 2.7 implementation for bootstrapping and cross-compiling PyPy.
I keep thinking about writing an RPython-specific Python 2.7 package manager, but I don’t think it’s a great use of time as long as there isn’t a thriving ecosystem of interpreters written in RPython.
Janet has great tooling, is trivial to embed, and is permissively licensed. The next time you’re considering an extension language or a lisp, do yourself a favor and check it out.
I started reading this and was immediately charmed! I think this might finally be the time I learn a lisp language 👀
Nix book next? ;-)