a lisp program suite
intro⌗
Lisp is the most powerful language there is. It is the only language that can generate programs while executing them, which can be used to create more concise programs.
Generating code and executing it is rather useful when composing programs, because it allows for elegance through reducing complexity and repetitiveness.
showcase⌗
One of my favorite languages to use is Golang. Golang is by far one of the most simple and fun languages to use. Composing software in golang feels natural, and organized.
But Golang’s downfall is its strictness on wanting to be simple. A good example of this is using SQL database in a web app.
func UserInsert(db *sql.DB, u User) error {
_, err := db.Exec("INSERT INTO Users (username, password) VALUES (?, ?)", u.Username, u.Password)
return err
}
func UserSelect(db *sql.DB, id uint) (*User, error) {
row := db.QueryRow("SELECT username,password FROM Users WHERE id=?", id)
if row.Err() != nil { return nil, row.Err() }
u := User{}
err = row.Scan(&u.Username, &u.Password)
if err != nil { return nil, err }
return &u, nil
}
func UserUpdate(db *sql.DB, id uint, user, pass string) error {
_, err := db.Exec("UPDATE Users SET username=?,password=? WHERE id=?", user, pass, id)
return err
}
func UserDelete(db *sql.DB, id uint) error {
_, err := db.Exec("DELETE FROM Users WHERE id=?", id)
return err
}
Notice how this code is very functional, and easy to read, but it is very constrained in the sense that you cannot update one field or two for example.
I mean, technically you could, however it is very very ugly. Take this snippet I have from a web portal:
func UserUpdate(db *sql.DB, id uint64, usertype *model.UserType, email, firstname, lastname, country *string, courses *model.TermCourses) error {}
The way it updates automatically is through using a channel and a map, like so:
ch := make(chan map[string]interface{}, 6)
done := make(chan struct{})
go func() {
if usertype != nil {
ch <- map[string]interface{}{"name": "type", "value": *usertype}
}
if email != nil {
ch <- map[string]interface{}{"name": "email", "value": *email}
}
if firstname != nil {
ch <- map[string]interface{}{"name": "firstname", "value": *firstname}
}
if lastname != nil {
ch <- map[string]interface{}{"name": "lastname", "value": *lastname}
}
if country != nil {
ch <- map[string]interface{}{"name": "country", "value": *country}
}
if courses != nil {
ch <- map[string]interface{}{"name": "courses", "value": *courses}
}
done <- struct{}{}
}()
And afterwards, you’d check the map for any insertions:
<-done
for len(ch) > 0 {
val := <-ch
query := fmt.Sprintf(template, val["name"])
_, err := db.Exec(query, val["value"], id)
if err != nil {
return fmt.Errorf("%w: %s", InvalidSQL, err.Error())
}
}
Do note that this method is perfectly functional, however it adds repetitiveness to your code. With Lisp, you could accomplish something similar with perhaps 30 lines of template code and 1 line of implementation code.
Check out the following Common Lisp snippet:
(defmacro string+(name &rest suffix)
`(concatenate 'string ,name ,@suffix))
(defun var+(name &rest suffix)
(read-from-string (string+ name suffix)))
(defmacro create-sql-model(tablename name fields)
(let ((insert (var+ name "insert"))
(update (var+ name "update")
(delete (var+ name "delete"))
(select (var+ name "select"))))
`(progn
(defun ,insert (user pass)
(stmt (string+ "insert into " ,tablename " (username, password) values (?,?)") ,user ,pass))
(defun ,select (id)
(stmt (string+ "select username,password from " ,tablename " where id=?") id))
(defun ,update (id user pass)
(stmt (string+ "update " ,tablename " set user=?,pass=? where id=?") ,user ,pass ,id))
(defun ,delete (id)
(stmt (string+ "delete from " ,tablename "where id = ?") ,id))
)))
Now, you could generate global functions through a simple macro call, like so: (create-sql-model "users" "user" nil)
However, this macro doesn’t have a lot of power considering that it only generates functions for a user-type model. Meaning that it does not adapt to different fields…
But with Lisp, you could do pretty much anything, so in-order to make it more useful you could copy defstruct
’s syntax:
(defstruct user (id nil :type string) (username nil :type string) (password nil :type string))
So that you can generate functional methods to invoke SQL statements, generate a structure to map SELECT results to, and create the tables from the slot types. All in one line:
(define-sql-model "users" user (id nil :type string) (username nil :type string) (password nil :type string))
repl debugging⌗
Lisp is rather convienent for a suite, because it allows foreasier debugging through mounting of a Lisp Image.
A Lisp Image is a dump of the memory in a Lisp system. Think of it like you are copying that state of the program to another computer.
Mounting other Lisp Images will greatly help debugging because you have the same environment that the user has.
Not only that, you can also re-compile a function to test its results while you are in the Lisp Image.
You could view every single variable, modify it, delete it, or do whatever you’d like.
Also this isn’t a new testing feature, no, it is quite popular in the Lisp family of languages. It has been in Steel Bank Common Lisp since 2006.
In-case it is not available, you can still invoke REPL commands through the GUI by connecting them manually. EQL5 accomplishes something of similar nature in their GUI showcase.
single file application?⌗
| Controlling complexity is the essence of computer programming. - Brian Kernighan
This is rather unproven to me and purely theortical, but restricting one’s self to single file applications is more beneficial than harmful.
Technically speaking, any language can contain a program in a single file, but it is not very practical. Languages like Golang split their parts into small internal packages, in-order to create a palettable design.
Lisp being such an expressive language would result in more concise and powerful programs. Restricting your programs to a single file wouldn’t be madness in the Lisp world.
Of course, you could use all the libraries you want or create your own. What I mean is that UI and specific business logic(not shared) should be contained in a single file.
For example, think of an image conversion suite that converts between different image formats. Per each file format you want to support, you have to create two programs: toimgformat
& fromimgformat
.
toimgformat
would read stdin for a generic image format to be used inside your suite, and would output to stdout that specific file format.
fromimgformat
would read stdin for that specific file format, and output to stdout the generic suite image format.
Your suite’s directory would like something similar to this:
suite/
├── jpg
│ ├── from.c
│ ├── misc.c
│ └── to.c
├── misc.h
└── png
├── from.c
├── misc.c
└── to.c
This is more universal than suites that are written in Lisp, no, this extends to maybe all other languages. At least POSIX-like programs.
conculsion⌗
Suites are it for me, only suites can provide novice users immense power that is usable. Creating suites in Lisp would drastically reduce the amount of hair pulling a programmer will have, because it is elegant, expressive, and extendable.