Learning Common Lisp

Apart from the unusual nature of Lisp as a language, they're also unusual with how you're sort of expected to learn. With most languages you might read a tutorial but your instinct is probably to open some reference docs and dive in hands on. This isn't a bad approach, but Common Lisp is geared towards and rewards the voracious reading autodidact. As the name suggests, the cookbook is a community-made reference and it's damn good for looking at examples. Learning Common Lisp from it alone you'll miss out on an overall picture of Lisp. It's not really made for that.

The usual path a successful Common Lisper takes is:

The main issue you're facing is an attempt to speedrun this process. Which I get. You just want to set up a template of sorts, but you can't do that with a Lisp (or Lisp-descendant) until you've learned at least one. You're able to do that with Go, Python, JavaScript, etc because they're all so extremely similar and roughly in the same 'family' of languages. If you're working within the same family, there's only slight differences you need to know to get something going.

Common Lisp: The REPL

SBCL provides what they call a REPL (Read Eval Print Loop), it's basically a prompt that lets you test out your program, debug variables and reload your program in real time.

When you launch SBCL, the REPL you're presented isn't merely some limited prompt. What you have is the compiler as an interactive process. Any time you start Lisp, you're launching an "image", which is all of the Lisp compiler loaded into memory and persisting as a process, instead of immediately exiting after compiling some files (i.e batch compiling). The expected workflow with Lisp is with the REPL always running, compiling individual expressions, functions (etc) and entire files as you go. Tools like SLIME/Sly in Emacs, or the recent 'mine' editor facilitate this.

Common Lisp: Expressions

The C compiler will take each character in your program and make them into expressions automatically. Line 6, int d = a * c + b; has 3 expressions, the assignment operation, the multiplication operation and the addition operation. And using operator precedence, it knows that it should multiply a * c before adding b and only then it will assign the result to d.

Common Lisp requires you to specify these statements instead, so let's look at a similar example in lisp.

In Common Lisp, everything is an expression that returns a value (or multiple as you'll eventually learn!).

#include <stdio.h>
int main() {
  int a = 6;
  int b = 7;
  int c = a + b;
  int d = a * c + b;
  printf("Result: %d", d);
  return 0;
}

The actual Lisp equivalent looks like this:

(let* ((a 6)
       (b 7)
       (c (+ a b))
       (d (+ (* a c) b)))
  (format t "Result ~D~%" d))

Or you could write:

(let* ((a 6)
       (b 7)
       (c (+ a b)))
  (format t "Result ~D~%" (+ (* a c) b)))

And just for completions sake

(defun add-multiply-add (x y)
  (+ (* x (+ x y))
     y))
;; (add-multiply-add 6 7) => 85

Because of s-expressions (the parens) and convenience, Lisp uses prefix notation. So there's no math operation precedence for the compiler to figure out. You just have normal expressions.

See https://en.wikipedia.org/wiki/Polish_notation

Common Lisp: Variables

Everything you need to know about variables is in a single chapter of Practical Common Lisp:

There is a very real practical difference (explained in the chapter) between defvar, defconstant and defparameter and you need to know them. You will have strange bugs without this knowledge.

Common Lisp: Forms

I really wanted an LSP so that I can understand where the problem is comming from as the REPL is giving me Minecraft Enchanting Table type error messages.

The error messages you got are actually really good and tell you exactly where your error is, but you don't know what 'form' means (because you haven't read Practical Common Lisp), and you didn't read the errors. Even if you had an LSP, you would get the exact same thing because the inspector/debugger is part of the language standard. It'd just look slightly prettier. Common Lisp isn't something that throws obscure unhelpful errors like C++ or something.

Look at the information SBCL told you.

The exact file:

; file: /home/pedro/wikistocks-api-templates/ws-common-lisp-template/src/ws-bot.lisp

The precise function:

; in: DEFUN REQUESTCSRFTOKEN

The problematic form:

; ((DEXADOR:GET (CONCATENATE WS_HOST "/sanctum/csrf-cookie") :COOKIE-JAR
;                   COOKIEJAR))

So, this is a nonsensical expression:

((dex:get (concatenate ws_host "/sanctum/csrf-cookie") :cookie-jar cookiejar))

You can write an even simpler case of this to get the same error:

((+ 1 2))

Function calls begin with an open-paren, followed by a symbol and any arguments, ending with a close-paren:

(dex:get (concatenate ws_host "/sanctum/csrf-cookie") :cookie-jar cookiejar)

Also, had you developed your code in an interactive manner, you would have gotten the error as soon as you compiled the individual function, rather than be surprised when you go to compile the file. The Lisp workflow is:

write individual function -> compile the function -> look to see any errors -> test it in the repl -> loop

There's good reason for this.

Project Scaffolding: Lay of the land

I've completed templates for: python, rust, go and typescript/bun.

A typical project in Common Lisp looks something like this:

some-lisp-project/
├── README.md
├── some-lisp-project.asd
└── src/
    ├── package.lisp
    ├── fanshen.lisp
    └── foo.lisp

Project Scaffolding: ASDF

The .asd file defines one or more 'system' definitions. A system is Lisp file ending .asd that specifies a collection of Lisp source files and dependencies, and in what order to compile said source files. It can contain other metadata as well like license information, homepage URL, maintainer contact info, etc. But the most basic way of using and thinking about the .asd file is the Lisp version of a 'Makefile'.

The .asd filename comes from the tool that makes all this building possible: ASDF (Another System Definition Facility). Most Lisp implementations, like SBCL, come with ASDF included which you shouldn't need to load.

How ASDF works is not as a separate binary or program, but as a library with functions that you can use. If someone wanted to compile some-lisp-project then at the REPL they would (asdf:load-system :some-lisp-project). For your purposes, the .asd file should contain your dependencies, like cl-cookie, dexador, and cl-dotenv. You don't want those in your main source files like you currently have.

As a quick reference example of an .asd file:

(asdf:defsystem some-lisp-project
  :serial t
  :components ((:file "package")
               (:module "src"
                :components ((:file "foo")
                             (:file "fanshen"))))
  :depends-on (:library-one
               :library-two))

See https://lispcookbook.github.io/cl-cookbook/systems.html

Project Scaffolding: package.lisp

Conventionally, package.lisp contains 'package' definitions. What Common Lisp calls a package is known as a namespace or module in other languages.

In package.lisp you'll define a package with defpackage. Then in your source files (say, in src/foo.lisp) at the very top you'll put (in-package :some-package-name).

You're already using some packages:

But you haven't defined your own. A minimal example:

(defpackage :foo (:use :cl))

See: https://lispcookbook.github.io/cl-cookbook/packages.html

Project Scaffolding: Quicklisp

You know what libraries you want to use but how do you get them? For Common Lisp the most widely used and easiest solution is Quicklisp. This is the 'pip' of Common Lisp.

(It should be noted that Quicklisp is not part of the language standard. And it's definitely not included with SBCL or any compiler for that matter).

You know how I said you would use (asdf:load-system :some-lisp-project)? That was only partially true. Being able to load a system depends on you 1.) having some-lisp-project locally on your computer and 2.) having the dependencies of some-lisp-project (listed in the .asd file) locally on your computer.

What you will actually do 99% of the time is (ql:quickload :some-lisp-project) at the REPL as it will fetch the dependencies and then call (asdf:load-system ...) for you.

See:

Tooling: mine

Package QL does not exist.

Package QL does not exist.?

Package QL does not exist.?????

You very likely either didn't run the setup wizard or ran the setup wizard but failed to understand the implications of not loading Quicklisp automatically, which it asks if you want to do or not. Run the setup and choose to load Quicklisp automatically.

You need to slow down, read, and understand.

In my rage, I decided to look up "Popular Programs Made in Lisp", I found the awesome-cl site which has a lot of tools and libraries made in lisp for lisp users and I found Category: Common Lisp Software on Wikipedia which is a list of software made in Common Lisp.

Common Lisp isn't popular, surprise surprise. True enough, people haven't written enough applications in it but Wikipedia is a shit way of finding out what's been made with Common Lisp.

If we're talking about games, look at Kandria, which is a fairly large 2D RPG sold on steam and open source as well, using the Trial game engine. The creator of the game wrote the engine as well:

Or how about a browser:

https://github.com/atlas-engineer/nyxt/

A text editor?

https://github.com/lem-project/lem

An OS

https://github.com/froggey/Mezzano

And more CL apps and shit:

I won't claim that this is good enough, much much more user-facing CL apps should be written. I know you wrote that in rage, but that criticism in particular wasn't in good faith at all.