Tomasz Wegrzanowski
Posted on February 17, 2022
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.
Hello, World!
You can install Janet with brew install janet
.
#!/usr/bin/env janet
(print "Hello, World!")
$ ./hello.janet
Hello, World!
FizzBuzz
#!/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 a1..100
loop - 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
Unicode
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
Fibonacci
Defining functions is easy, and it's readable enough if your editor has matching parentheses coloring. There's no string interpolation, but at least print
takes multiple arguments and doesn't put any spaces between them, so it's tolerable.
#!/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
Wordle
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 functions like "random element" or even "random integer" so we have to build our own - this is a more common problem, for example JavaScript has the same issue, but it's still bad
- 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
\r
there - 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 ownstring/char-at
-
prin
is likeprint
without extra newline, which isn't too bad, just some interesting naming - more weirdly
printf
also appends extra newline, and that sure goes against expectations -prinf
is likeprint
without extra newline - it makes sense as far as functionality goes, but that naming is sure weird - 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?
No.
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 npm
or 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.
Code
All code examples for the series will be in this repository.
Posted on February 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.