Serapeum is an excellent CL library, with lots of utilities. You should check it out. It provides a case
-like macro, to use on enums, that warns you at compile-time if you handle all the states of that enum.
Example with ecase-of
from its README. First we define the enum:
(deftype switch-state ()
'(member :on :off :stuck :broken))
We write a function that does something depending on the state of a switch object. We use ecase-of
against this enum type to not miss any state.
Below we only check two states, so we'll get a warning (at compile-time, with a C-c C-c
on Slime: the feedback is… instantaneous). Imagine that (state switch)
is a function call that gets the current state of the "switch" variable passed to the function:
(defun flick (switch)
(ecase-of switch-state (state switch)
(:on (switch-off switch))
(:off (switch-on switch))))
=> the warning:
; caught WARNING:
; Non-exhaustive match: (MEMBER :ON :OFF) is a proper subtype of SWITCH-STATE.
; There are missing types: ((EQL :STUCK) (EQL :BROKEN))
; in: DEFUN FLICK
; (CL-USER::STATE CL-USER::SWITCH)
And a nice message too.
The syntax is the following:
(ecase-of switch-state state
(:state (do-something))
(:state (do-something)))
where switch-state
is the enum-type defined above, state
is the current value we are testing.
Another example: you think you fixed the previous example with the following version, but it has a bug. Serapeum catches it:
(defun flick (switch)
(ecase-of switch-state (state switch)
(:no (switch-off switch)) ;; <---- typo!
(:off (switch-on switch))
((:stuck :broken) (error "Sorry, can't flick ~a" switch))))
=>
; caught WARNING:
; (MEMBER :NO) is not a subtype of SWITCH-STATE
We see another possible syntax:
(ecase-of … …
((:stuck :broken) (do-something)))
we can group and handle states together.
Union-types
And we can do the same with union-types, using etypecase-of
:
(defun negative-integer? (n)
(etypecase-of t n
((not integer) nil)
((integer * -1) t)
((integer 1 *) nil)))
=> Warning
; caught WARNING:
; Can't check exhaustiveness: cannot determine if (OR (NOT INTEGER)
; (INTEGER * -1)
; (INTEGER 1 *)) is the same as T
and indeed, we are handling ]∞, -1] U [1, ∞[
(defun negative-integer? (n)
(etypecase-of t n
((not integer) nil)
((integer * -1) t)
((integer 1 *) nil)
((integer 0) nil)))
=> No warning: handle 0.
This all happens at compile time, when we define functions, and the feedback is immediate, thanks to the live Lisp image.
Go play with it!
(ql:quickload "serapeum")
Top comments (0)