DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on • Edited on

100 Languages Speedrun: Episode 92: newLISP

newLISP, for some reason spelled in this weird way, is another attempt at bringing Lisp into the modern age.

newLISP also makes some extremely questionable claims about figuring out "One True Solution To Memory Management", and these claims are obviously bullshit, but let's just look at the language itself first.

Hello, World!

We need to tell newLISP to exit after it finishes the program, otherwise it would drop us into REPL:

#!/usr/bin/env newlisp

(print "Hello, World!\n")
(exit)
Enter fullscreen mode Exit fullscreen mode
$ ./hello.lsp
Hello, World!
Enter fullscreen mode Exit fullscreen mode

Fibonacci

Everything is very neat, we can just use define and for. Of course you'll need some kind of parentheses colorizer to read this, but that's true for any Lisp, and VSCode already comes with parentheses colorizer builtin, you just need to turn it on.

There's no string interpolation, but we can pass a bunch of arguments to print:

#!/usr/bin/env newlisp

(define (fib n)
  (if (<= n 2)
    1
    (+ (fib (- n 1)) (fib (- n 2)))))

(for (i 1 30)
  (print "fib(" i ")=" (fib i) "\n"))
(exit)
Enter fullscreen mode Exit fullscreen mode
$ ./fib.lsp
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

Unicode

Default length function returns size in bytes, but there are separate functions for UTF8 lengths. Some string operations like upper and lower casing are Unicode-aware, some take byte addresses.

#!/usr/bin/env newlisp

(set 'a "Żółw")
(set 'b "💩")

(print (length a) "\n")
(print (length b) "\n")

(print (utf8len a) "\n")
(print (utf8len b) "\n")

(print (upper-case a) "\n")
(print (lower-case a) "\n")

(exit)
Enter fullscreen mode Exit fullscreen mode
$ ./unicode.lsp
7
4
4
1
ŻÓŁW
żółw
Enter fullscreen mode Exit fullscreen mode

FizzBuzz

Like with many Lisps, we can use cond instead of chaining ifs:

#!/usr/bin/env newlisp

(for (i 1 100)
  (print
    (cond
      ((= 0 (% i 15)) "FizzBuzz")
      ((= 0 (% i 5)) "Buzz")
      ((= 0 (% i 3)) "Fizz")
      (true i))
    "\n"))
(exit)
Enter fullscreen mode Exit fullscreen mode

Functional Programming

Let's do something really simple:

#!/usr/bin/env newlisp

(set 'a (list 1 2 3 4 5))
(set 'add10 (lambda (x) (+ x 10)))

(print a "\n")
(print (map (lambda (x) (+ x 10)) a) "\n")
(print (map add10 a) "\n")

(exit)
Enter fullscreen mode Exit fullscreen mode
$ ./functional.lsp
(1 2 3 4 5)
(11 12 13 14 15)
(11 12 13 14 15)
Enter fullscreen mode Exit fullscreen mode

OK, so far so good. Now let's try to extract that adder to a function:

#!/usr/bin/env newlisp

(define (adder n) (lambda (x) (+ x n)))

(set 'a (list 1 2 3 4 5))
(set 'add10 (adder 10))

(print a "\n")
(print (map (lambda (x) (+ x 10)) a) "\n")
(print (map add10 a) "\n")

(exit)
Enter fullscreen mode Exit fullscreen mode
$ ./functional2.lsp
(1 2 3 4 5)
(11 12 13 14 15)

ERR: value expected in function + : n
Enter fullscreen mode Exit fullscreen mode

What's going on? Dynamic scoping! So we discovered what completely disqualifies newLISP as an acceptable language.

At this point we could just stop. Dynamic scoping is incompatible with functional programming, and Lisp without functional programming is just pointless.

Wordle

But let's complete the usual set of programs with a Wordle.

#!/usr/bin/env newlisp

(define (random-element lst) (nth (rand (length lst)) lst))

(define (report-wordle guess word)
  (dotimes (i 5)
    (print
      (cond
        ((= (nth i guess) (nth i word)) "🟩")
        ((= (member (nth i guess) word) "🟨"))
        (true "🟥"))))
  (print "\n"))

; we need to seed random generator, or it will always return same number
(seed (time-of-day))

(define words (parse (read-file "wordle-answers-alphabetical.txt")))
(define word (random-element words))

(set 'guess "")
(while (!= guess word)
  (print "Guess: ")
  (set 'guess (read-line))
  (if (= 5 (length guess))
    (report-wordle guess word)
    (print "Guess must be 5 characters\n")))
(exit)
Enter fullscreen mode Exit fullscreen mode
$  ./wordle.lsp
Guess: crane
🟩🟩🟥🟥🟩
Guess: crime
🟩🟩🟥🟥🟩
Guess: crude
🟩🟩🟥🟥🟩
Guess: crepe
🟩🟩🟩🟩🟩
Enter fullscreen mode Exit fullscreen mode

The code is fairly straightforward. Notably, due to newLISP mistakenly believing it discovered "One True Solution To Memory Management", calling random-element deep-copies the whole list, turning the usually O(1) function into O(n) by memory. In this case it would be O(n) by time anyway, as Lisp insists on single linked lists, and length and nth are O(n) instead of O(1), but at least other Lisps don't copy things all the damn time.

This is fine for such tiny programs, but don't try to write anything more complicated in newLISP for sure.

Should you use newLISP?

No.

There are languages where dynamic scoping could work, but Lisp variants are not among them.

It's not even the only issue. newLISP thinks it found "One True Solution To Memory Management", but its solution is copying everything all the time. And the idea is just as bad as it sounds.

Either of these issues would disqualify a language, but newLISP suffers from both.

If you want a Lisp, there are two decent choices - Racket and Clojure.

Code

All code examples for the series will be in this repository.

Code for the newLISP episode is available here.

Top comments (0)