On Thu, 2012-05-31 at 14:50 -0400, Ryan Davis wrote: [...]
I'd like some more opinions on a pattern that has cropped up. One of the problems we were having was quickly determining what a function expected for it's arguments. As a somewhat contrived example, SLIME helpfully would tell me that #'send-email wanted (to from subject body), but then it was left to me to guess what values I should pass in. In real code this was frequently non-trivial, and we'd be hand-tracing to figure out where the parameter was used to figure out what it should be. Should "to" be a string, a CLOS Client object, or the database ID of a Client? The answer we arrived at was "yes":
(defun send-email (to from subject body) (let ((to (etypecase to (string to) ((integer 0) (email (fetch-client to))) (client (email to)) ))) ;; ... more code ))
The "to" parameter can be anythings that can be mapped to an email address. It is send-email's job to send email, and it will figure it out based on whatever you provide. If it can't do it, it'll tell you. Usually the etypecase is pulled into it's own function, and we have something like:
(defun send-email (to from subject body) (let ((to (coerce-email to))) ;; ... more code ))
I've started using this idiom too: for a type FOO, have, in addition to the assembling constructor that is make-instance, a coercing constructor having the same name as the type itself, which can be elided with a clever use of inlining and type declarations. The CL standard has some instances of this, e.g. with pathnames: cl:pathname coerces a pathname-designator(pathnamem, string or file stream) and cl:make-pathname assembles from components.
Example:
(declaim (inline email)) (defun email (email-designator) (etypecase email-designator (string email-designator) (unsigned-byte (email-of (fetch-client email-designator))) (client (email-of email-designator))))
(declaim (inline send-email)) (defun send-email (to from subject body) (let ((to (email to)) (from (email from))) (%send-email to from subject body)))
(defun send-site-warning (to) (declare (type string to)) (send-email to "admin@site.com" "Warning" "Bandwidth quota reached"))
The creation of the above wrapper - send-email that checks type and coerces then calls %send-email - can also be easily automated with some macrology