There is a misconception that Lisp is a dynamically typed language and doesn’t offer compile-time type checking. Lisp being the programmable programming language that is of course not true.

(defun meh (p1)

(declare (fixnum p1))

(+ p1 3))

(defun meh2 (p1)

(declare (fixnum p1))

(+ p1 "8"))

Gets you this:

2 compiler notes: typecheck-demo.lisp:7:3:

note: deleting unreachable code

warning:

Constant "8" conflicts with its asserted type NUMBER.

See also:

SBCL Manual, Handling of Types [:node] Compilation failed.

You can declare pretty much all the things that you would in regular statically typed languages. Function interface:

(defun meh3a (p1)

(+ p1 3))

(declaim (ftype (function (fixnum) t) meh3a))

(defun meh3b ()

(meh3a "moo"))

==>

2 compiler notes: typecheck-demo.lisp:13:3:

note: deleting unreachable code

warning:

Constant "moo" conflicts with its asserted type FIXNUM.

See also:

SBCL Manual, Handling of Types [:node] Compilation failed.

Or you can pull the declaration in front so that the definition of first function is already checked against the type declaration when the function is defined:

(declaim (ftype (function (string) t) meh4a))

(defun meh4a (p1)

(+ p1 3))

(defun meh4b ()

(meh4a "moo"))

2 compiler notes: typecheck-demo.lisp:18:3:

note: deleting unreachable code

warning:

Derived type of P1 is

(VALUES STRING &OPTIONAL),

conflicting with its asserted type

NUMBER.

See also:

SBCL Manual, Handling of Types [:node] Compilation failed.

;; note that this is the first function failing

;; the second one compiles fine.

Now, obviously this isn’t the most convenient syntax. But that is easy to fix in a programmable programming language that has compile-time computing.

So what we want for this demo is the ability to put the types of the arguments directly into the argument list. A function definition would look like this:

(defunt meh5c ((int p1) (int p2))

(+ p1 p2))

(meh5c 1 2) ; ==> 3

So we have a new name instead of defun and we want to write one piece of compile time computation that takes care of both the actual defun and declaring the types. After we write that macro we just want to write functions in the new syntax.

Here is a macro that does that:



"defun with optional type declarations"

`(progn

(declaim (ftype

(function

,(let (declares)

(dolist (arg args)

(push

(if (listp arg)

(if (equalp (string (first arg)) "int")

'fixnum

(first arg))

t)

declares))

declares)

t) ,name))

(defun ,name

,(loop for arg in args

collect

(if (listp arg)

(second arg)

arg))

, (defmacro defunt (name (&rest args) &body body)"defun with optional type declarations"`(progn(declaim (ftype(function,(let (declares)(dolist (arg args)(push(if (listp arg)(if (equalp (string (first arg)) "int")'fixnum(first arg))t)declares))declares)t) ,name))(defun ,name,(loop for arg in argscollect(if (listp arg)(second arg)arg)) @body )))

I’ll explain a bit more in a bit, let’s just test this thing:

;; simple use with no type declaration

(defunt meh5a (p1 p2)

(+ p1 p2)) ;; one declared type, not the other

(defunt meh5b ((int p1) p2)

(+ p1 p2)) ;; make sure this works

(defun meh5btest (p1)

(+ p1 "8")) (defunt meh5c ((int p1) (int p2))

(+ p1 p2))

Compiling meh5test results, as expected, in

typecheck-demo.lisp:81:3:

note: deleting unreachable code

warning:

Constant "8" conflicts with its asserted type NUMBER.

See also:

SBCL Manual, Handling of Types [:node] Compilation failed.

Okay, so you wrote that macro once and from then on you can just use it. The macro looks a bit hard to read for programmers who are not yet used to Lisp. I can tell you that I wrote that macro, instead this entire post, while waiting on a flight going from the AmberMD dev meeting to the European Lisp Symposium.

It’s quick. And there are competent debugging facilities. I will demonstrate the debugging later.

Being able to debug compile time computing is critical. Can you imagine single-stepping through a C preprocessor macro, or through C++ template evaluation? Or use debugging printf (otherwise known as the only real debugger) in C or C++ at compile time?

Here is the same macro with some comments:



"defun with optional type declarations"

;; backtick enters "code echoing", do not

;; evalluate when macro is run

`(progn

;; this macro emits two definitions to

;; the compiler:

;; 1 - the declaim type declarations

;; 2 - the defun

(declaim (ftype

(function

;; comma inside backtick means

;; "execute when macro is run"

,(let (declares)

(dolist (arg args)

(push

(if (listp arg)

;; also translate for C people, but case

;; independent

(if (equalp (string (first arg)) "int")

'fixnum

(first arg))

t)

declares))

;; return this list, which is integrated

;; into the code. You see that is why we

;; have so many parenthesis. Because what

;; is a passive data list here turns into

;; code, without any reformatting.

;; Read below for a step-by-step explanation

;; of what is going on

declares)

t) ,name))

(defun ,name

;; use the loop macro here for the same purpose we

;; manually collected the args with dolist and

;; push above

,(loop for arg in args

collect

(if (listp arg)

(second arg)

arg))

,

;; the result of the above macro is then fed into the compiler.

;; So we use three levels of evaluation time here:

;; 1 - when the macro is run

;; 2 - the output of the macro call, which is fed into the compiler

;; 3 - run time, when you actually call the resulting function (defmacro defunt (name (&rest args) &body body)"defun with optional type declarations";; backtick enters "code echoing", do not;; evalluate when macro is run`(progn;; this macro emits two definitions to;; the compiler:;; 1 - the declaim type declarations;; 2 - the defun(declaim (ftype(function;; comma inside backtick means;; "execute when macro is run",(let (declares)(dolist (arg args)(push(if (listp arg);; also translate for C people, but case;; independent(if (equalp (string (first arg)) "int")'fixnum(first arg))t)declares));; return this list, which is integrated;; into the code. You see that is why we;; have so many parenthesis. Because what;; is a passive data list here turns into;; code, without any reformatting.;; Read below for a step-by-step explanation;; of what is going ondeclares)t) ,name))(defun ,name;; use the loop macro here for the same purpose we;; manually collected the args with dolist and;; push above,(loop for arg in argscollect(if (listp arg)(second arg)arg)) @body )));; the result of the above macro is then fed into the compiler.;; So we use three levels of evaluation time here:;; 1 - when the macro is run;; 2 - the output of the macro call, which is fed into the compiler;; 3 - run time, when you actually call the resulting function

The concept behind this is:

a single macro call can issue several new statements. We use this to emit both the declare and the defun, from a single user-issued definition

inside the macro you can control which code runs at macro call time and what gets emitted to the compiler. Keep in mind that you have the full language at your disposal at both times. One reason why these macros can be a bit hard to read is that you explicitly need to change between the different evaluation times, and the code has the same syntax. A C preprocessor macro or a C++ template use different languages at compile and run time, so it is a bit more clear what is evaluated when. Of course you cannot use your usual library at compile time like you can in Lisp

unless you do say otherwise the code inside the macro runs at macro expansion time. The return value is what is fed into the compiler. The return value much be a list, a list of Lisp language statements. That is why in Lisp you have to use list syntax for code. Got that? It is important. You construct this nested-parenthesis thing that is a list at compile time, and you feed it into the compiler, so the list becomes code.

a tick (‘) or a backtick (`) leaves things as lists and does not evaluate it at macro call time. That is how you return a list (which is data, not evaluated) from the macro (which is then fed into the compiler).

A comma (,) can be used inside a backticked (`) block to switch evaluation time back to macro call time. Your comma block also returns lists, and they become integrated into the backticked block.

The ,@ construct removes one list nesting, so it turns ((foo bar)) into (foo bar). You often need that when you return collections of items from the macro’s own variables or comma block. This is getting into the depths of macros and some obscure syntax, but it is not particularly hard to get right given the debug tools.

So, if you want to write such macros: