There are thousands of Lisps out there, let's try a less known one, Janet. Mostly because it's named after a character from one of the best shows of all times, that's pretty much the kind of objective scientific criteria I use for choosing languages for the series.
You can install Janet with
brew install janet.
#!/usr/bin/env janet (print "Hello, World!")
$ ./hello.janet Hello, World!
#!/usr/bin/env janet # FizzBuzz in Janet (loop [n :range [1 101]] (print (cond (zero? (% n 15)) "FizzBuzz" (zero? (% n 5)) "Buzz" (zero? (% n 3)) "Fizz" n)))
It's just another Lisp, of course with tiny differences:
- comments with standard
- yet another looping syntax -
(loop [n :range [1 101]] ...)for a
- about half the languages have correct ranges, and about half the languages are off-by-one, and Janet is off-by-one
(cond)has fewer parentheses than in most other Lisps
There isn't any, and Janet is quite explicit about it. This isn't really acceptable in 2022, Unicode is everywhere.
#!/usr/bin/env janet (print (string/ascii-upper "Żółw")) (print (string/ascii-lower "Żółw")) (print (length "Żółw"))
$ ./unicode.janet ŻółW Żółw 7
Defining functions is easy, and it's readable enough if your editor has matching parentheses coloring. There's no string interpolation, but at least
#!/usr/bin/env janet (defn fib [n] (if (<= n 2) 1 (+ (fib (- n 1)) (fib (- n 2))))) (loop [n :range [1 31]] (print "fib(" n ")=" (fib n)))
$ ./fib.janet fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=5 fib(6)=8 fib(7)=13 fib(8)=21 fib(9)=34 fib(10)=55 fib(11)=89 fib(12)=144 fib(13)=233 fib(14)=377 fib(15)=610 fib(16)=987 fib(17)=1597 fib(18)=2584 fib(19)=4181 fib(20)=6765 fib(21)=10946 fib(22)=17711 fib(23)=28657 fib(24)=46368 fib(25)=75025 fib(26)=121393 fib(27)=196418 fib(28)=317811 fib(29)=514229 fib(30)=832040
Let's try to do something more complex - Wordle. And wow, Janet is failing to provide even the most basic standard library functions.
#!/usr/bin/env janet (math/seedrandom (os/cryptorand 16)) (defn file/readlines [path] (string/split "\n" (string/trim (slurp path)))) (defn math/randint [min max] (+ min (math/floor (* (math/random) (+ 1 (- max min)))))) (defn array/random [array] (get array (math/randint 0 (- (length array) 1)))) (defn readline  (string/trimr (file/read stdin :line) "\n")) (defn string/contains [str c] (not (nil? (string/find c str)))) (defn string/char-at [str n] (string/slice str n (+ n 1))) (def words (file/readlines "wordle-answers-alphabetical.txt")) (def word (array/random words)) (defn report-wordle [guess word] (loop [i :range [0 5]] (prin (cond (= (get word i) (get guess i)) "🟩" (string/contains word (string/char-at guess i)) "🟨" "🟥"))) (print)) (while true (prin "Guess: ") (def guess (readline)) (if (= 5 (length guess)) (report-wordle guess word) (print "Guess must be 5 letters long")) (if (= guess word) (break)))
$ ./wordle.janet Guess: stair 🟥🟨🟨🟥🟥 Guess: acute 🟩🟥🟥🟨🟨 Guess: agent 🟩🟩🟩🟩🟩
Let's do it step by step, or more like fail by fail:
- randomness isn't pre-seeded. If you don't manually seed, you'll get the same results every time. In real life you pretty much never want this.
- you can't even seed with
(os/clock), as that returns a float, and it type errors
- there weren't any examples of a fresh seed in documentation, but after some experimentation I found out that
(os/cryptorand 16)works as a seed
- there's no "read line from user (without newline)", so we need to do that as well - I'm not sure if it would work on Windows or if we'd have a stray
- there's no "string/contains", so we need to build it ourselves
(get string index)returns a number not a string - so we can't do
(string/find (get guess i) word)as that would be a type error - and so we need to write our own
- more weirdly
printfalso appends extra newline, and that sure goes against expectations -
- at least on the upside we can do
(while true ... (break)), which a lot of Lisps don't support, so that's nice
But overall, it's been quite painful. And of course this Wordle only supports ASCII. Want a Wordle with Unicode characters? You might just as well delete Janet and install a better programming language.
Should you use Janet?
Lack of support for Unicode pretty much disqualifies any language from serious use, and standard library was lacking even the most basic functionality.
Any real language needs to support things like "here's an URL, fetch that, parse the JSON, and give me that key from it" in a few lines, because our lives are valuable, and we don't have time to waste reimplementing basic stuff like reading a line from a file. Am I saying that all programming languages with tiny standard libraries are trash? Unless they have an easy way to fill in the gaps in standard library like
luarocks then yes they are all trash.
I checked a bunch of Lisps in this series, and there are two decent choices among them - Racket and Clojure. I wouldn't recommend any other.
All code examples for the series will be in this repository.
Top comments (0)