Make a Lisp [6] The journey continues...

03 Apr 2020

JKL - the journey continues

As described in a separate post, I’ve decided to continue the programming project I started in 2019 by using my Lisp implementation - JKL - for some application development. Specifically I decided to re-implement one of the classic AI systems described in the book Paradigms of Articial Intelligence Programming: Eliza, the first chatbot.

Part of the challenge is that I can’t directly code in JKL 1.0 the Eliza algorithms provided in Paradigms. Specifically, Paradigms decribes Eliza using Common Lisp. However, although JKL is a Lisp-like language, it is actually based on the teaching language MAL, which itself is derived from Clojure - a variant of Lisp developed after Paradigms was written. Because MAL is a minimal language intended to showcase Lisp implementation techniques, not to support serious application development, it lacks a ‘standard library’ of functions. Furthermore, data structures in JKL, like those in Clojure and MAL, are by default persistent and immutable, compared with those in Common Lisp whose variables can be modified or redefined once created.

For these reasons, I have had to

Where there was a choice between these two approaches, I opted for ‘maintain-Clojure-consistency’ as my overarching principle.

This post - which will continue to evolve during this project - describes the enhacements to JKL 1.0. Some of the more complex enhancements are covered in their distinct ‘deep dive’ posts, namely:

Handling semantic variations

Atoms

As already stated, data structures in JKL are immutable and persistent. The exceptions are referred to as atoms - values that are created using the atom function and updated using swap. In contrast, in Common Lisp, atom is a function that returns T for anything that isn’t a cons (a list cell), and where (atom) returns T.

Because I’m prioritizing Clojure compliance, I had to write an atomic? function that has the same effect as Common Lisp’s atom. The resultant JKL code is as follows:

(def! atomic? (fn* (x)
	(if (sequential? x)
		(if (empty? x)
			true
			false)
		true)))

New JKL functionality

The and macro

I added an and macro using JKL’s existing or macro as a model. The semantics of and in JKL match that of Clojure, namely that (and) returns true but otherwise evaluates all of its arguments until any one returns false or nil. The value of the last non-false argument is returned. The resultant code is

(defmacro! and (fn* (& and-args)
	(if (empty? and-args)
		true
		(if (= 1 (count and-args))
			(if (first and-args)
				(first and-args)
				false)
			(let* (and-var (gensym))
				`(let* (~and-var ~(first and-args))
					(if ~and-var
						(and ~@(rest and-args))
						~and-var)))))))

Improved map implementation

In MAL, the map function takes a function f and a single sequence s, and then applies f to the successive elements of s. However, Common Lisp’s mapcar and Clojure’s map function can handle multiple sequences. I therefore extended map in JKL to have similar semantics. Specifically, given a function f and one or more sequences, the revised map returns a list such that each element j is the result of applying f to element j of each of the argument sequences. The arity of f must match the number of argument sequences. map stops as soon as any of the argument sequences is exhausted. Some examples are as follows:

JKL> (map (fn* (x) (symbol? x)) (list 1 (quote two) "three"))
(false true false)
JKL> (def! fArgs2 (fn* (a b) (list a b)))
<fn (a b) (list a b)>
JKL> (map fArgs2 '(1 2 3) '(p q y))
((1 p) (2 q) (3 y))
JKL> (map fArgs2 '(1 2) '(p q y))
((1 p) (2 q))
JKL> (map fArgs2 '(1 2 3) '(p q y) 4)
Eval error: map '<fn (a b) (list a b)>' given non-sequence 4
In REPL
JKL> (map fArgs2 '(1 2 3))
Eval error: More parameters (bindings) than supplied values (expressions): (a b) ... (1)
In REPL

Miscellaneous new builtin JKL functions added for Common Lisp compatability

I also added several Common Lisp / Clojure functions to JKL as built-in functions implemented using C# (the language in which JKL itself is implemented). The new functions are as follows:

Other updates and fixes

I made several other enhancements based on experience using JKL. Some of these were made before I started the current Eliza project, but I’ve only just got around to writing them up now. In approximately chronological order, I: