Or see the list of project sponsors.
Documentation | 🥺 |
Docstrings | 🥺 |
Tests | 😀 |
Examples | 🥺 |
RepositoryActivity | 🤨 |
CI | 🥺 |
Today I want to review this library because it is used in other hu.dwim.*
libraries. Understanding how does hu.dwim.def
works will help to read other libraries code.
The main concept of the hu.dwim.def
is the definer
. Definer has a name and a function which is used to macro-expand the code.
This is how usual definition can be used and what it expands to:
POFTHEDAY> (def function foo ()
(format nil "Hello from ~A!"
hu.dwim.def:-this-function/name-))
-> (defun foo ()
(symbol-macrolet ((-this-function/name- 'foo))
(format nil "Hello from ~A!"
-this-function/name-)))
POFTHEDAY> (foo)
"Hello from FOO!"
As you can see, this macro expands into a usual function definition, plus a symbol-macrolet
, useful to refer to a current function name.
When definer is called, a number of options can be passed. Options allow tuning optimization settings and export rules.
Option "o" adds declaration to maximize performance:
POFTHEDAY> (def (function o) foo ()
(format nil "Hello World!"))
-> (locally
(declare (optimize (speed 3) (debug 0) (safety 2)))
(defun foo ()
(format nil "Hello World!")))
Options "d" adds an opposite declaration to make debugging easier:
POFTHEDAY> (def (function d) foo ()
(format nil "Hello World!"))
-> (progn
(declaim (notinline foo))
(locally
(declare (optimize (speed 0) (debug 3)))
(defun foo ()
(format nil "Hello World!"))))
Also, these declaration depends on the value of the hu.dwim.asdf:*load-as-production?*
variable. When it is nil, then "o" option will lead to these two declarations:
(declaim (notinline foo))
(declare (optimize (speed 0) (debug 1)))
and "d" option will generate:
(declaim (notinline foo))
(declare (optimize (speed 0) (debug 3)))
This way, functions will be inlined only when compiled for production.
There is a separate option "i" to add (declaim (inline foo))
declaration. But it works only when hu.dwim.asdf:*load-as-production?*
is nil
and debug is turned off.
On SBCL debug level is also controlled by a level, declaimed in the REPL. To have a reproducable results you'll need to evaluate: (declaim (optimize (debug 0)))
otherwise a notinline
declaration will be added.
Another cool option is "e". It will export the function, class or other defined entity:
POFTHEDAY> (def (function e) foo ()
(format nil "Hello World!"))
-> (progn
(eval-when (:compile-toplevel :load-toplevel :execute)
(export 'foo))
(defun foo ()
(format nil "Hello World!")))
Also, a class's slots can be exported automatically:
POFTHEDAY> (def (class ea) user ()
((name :reader get-name)
(email :reader get-email)))
-> (progn
(export 'user)
(export '(get-name get-email))
(defclass user ()
((name :reader get-name)
(email :reader get-email))))
Isn't this amazing? But what is really cool, it that these options will also work with your own custom definers.
Here is how to transform a macro generating a function into a definer:
POFTHEDAY> (defmacro blah (name &body body)
`(defun ,name ()
(format nil "A function ~S was called"
',name)
,@body))
POFTHEDAY> (blah 'me)
-> (defun 'me ()
(format nil "A function ~S was called" me))
;; Now we'll make from a usual macro a new definer:
POFTHEDAY> (def (definer :available-flags "eodi") blah ()
(hu.dwim.def::function-like-definer blah))
POFTHEDAY> (def (blah eoi) me)
-> (progn
(declaim (inline me))
(locally
(declare (optimize (speed 3) (debug 0) (safety 2)))
(eval-when (:compile-toplevel :load-toplevel :execute)
(export 'me))
(defun me ()
(format nil "A function ~S was called" 'me))))
Also, you might write a definer with a body. These special variables will be available during the macro-expansion:
hu.dwim.def:-definer-
hu.dwim.def:-whole-
hu.dwim.def:-options-
hu.dwim.def:-environment-
We can define an experimental definer to see what is accessable during macro-expansion:
POFTHEDAY> (def (definer :available-flags "doe") guts ()
(format t "hu.dwim.def:-definer- = ~A~%"
-definer-)
(format t "hu.dwim.def:-options- = ~A~%"
-options-)
(format t "hu.dwim.def:-whole- = ~A~%"
-whole-)
(format t "hu.dwim.def:-environment- = ~A~%"
-environment-)
`(progn))
POFTHEDAY> (def (guts de :any-other 'option))
hu.dwim.def:-definer- = #<definer GUTS>
hu.dwim.def:-options- = (EXPORT T DEBUG T ANY-OTHER 'OPTION)
hu.dwim.def:-whole- = (DEF (GUTS DE ANY-OTHER 'OPTION))
hu.dwim.def:-environment- = #<NULL-LEXENV>
NIL
As you can see, any values can be passed into the definer besides builtin flag and you might implement whatever logic you want.
Final great thing I want to tell you about definers is that there is a registry of them. This makes all definers are easily discoverable.
Well, not so easy because you need to digg into some internals:
POFTHEDAY> (loop for definer being the hash-values
of hu.dwim.def::*definers*
for name = (hu.dwim.def::name-of definer)
for doc = (when (slot-boundp definer
'hu.dwim.def::documentation)
(hu.dwim.def::documentation-of definer))
unless doc
count 1 into undocumented
when doc
do (format t "~A -> ~S~2%"
name doc)
finally (when (> undocumented 0)
(format t "~2&Also, there are ~A undocumented definers.~%"
undocumented)))
CLASS -> "Example that exports all the class name and all the readers, writers and slot names:
(def (class eas) foo (bar baz)
((slot1 :reader readerr)
(slot2 :writer writerr :accessor accessorr))
(:metaclass fofofo))"
CONDITION -> "See the CLASS definer."
CONSTANT -> "Use like: (def (constant e :test #'string=) alma \"korte\")
test defaults to equal."
SPECIAL-VARIABLE -> "Uses defvar/defparameter based on whether a
value was provided or not, and accepts
:documentation definer parameter
for value-less defvars."
PRINT-OBJECT -> "Define a PRINT-OBJECT method using PRINT-UNREADABLE-OBJECT.
An example:
(def print-object parenscript-dispatcher ; could be (parenscript-dispatcher :identity nil)
(when (cachep self)
(princ \"cached\")
(princ \" \"))
(princ (parenscript-file self)))"
WITH-MACRO -> "(def with-macro with-foo (arg1 arg2)
(let ((*zyz* 42)
(local 43))
(do something)
(-body- local)))
Example:
(with-foo arg1 arg2
(...))"
WITH-MACRO* -> "(def with-macro* with-foo (arg1 arg2 &key alma)
(let ((*zyz* 42)
(local 43))
(do something)
(-body- local)))
Example:
(with-foo (arg1 arg2 :alma alma)
(...))"
GUTS -> "This definer shows debug information about environment
where is expanded."
Also, there are 33 undocumented definers.
To conclude, hu.dwim.def
is a great library now I'll use it in my projects!