100 Languages Speedrun: Episode 95: Janet

taw

Tomasz Wegrzanowski

Posted on February 17, 2022

100 Languages Speedrun: Episode 95: Janet

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!")
Enter fullscreen mode Exit fullscreen mode
$ ./hello.janet
Hello, World!
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

It's just another Lisp, of course with tiny differences:

  • comments with standard #
  • yet another looping syntax - (loop [n :range [1 101]] ...) for a 1..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"))
Enter fullscreen mode Exit fullscreen mode
$ ./unicode.janet
ŻółW
Żółw
7
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode
$ ./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
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode
$ ./wordle.janet
Guess: stair
🟥🟨🟨🟥🟥
Guess: acute
🟩🟥🟥🟨🟨
Guess: agent
🟩🟩🟩🟩🟩
Enter fullscreen mode Exit fullscreen mode

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 own string/char-at
  • prin is like print 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 like print 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.

Code for the Janet episode is available here.

💖 💪 🙅 🚩
taw
Tomasz Wegrzanowski

Posted on February 17, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related