[pro] style question about types, coercion, expectations for function parameters
Tobias C Rittweiler
tcr at freebits.de
Fri Jun 1 10:22:08 UTC 2012
In article <4FC7BD7C.7050205 at acceleration.net>,
Ryan Davis <ryan at acceleration.net> 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
> ))
Hi Ryan!
As others have pointed out the use of designators is widely used
both in the Common Lisp language itself, and in real world usage.
Here's my experience which matches yours:
* As Stelian has pointed out, lifting the ETYPECASE into
a separate function e.g. EMAIL or EMAIL-DESIGNATOR is
usually a good idea.
* If user of the code can meaningfully extend the notion of
what an email is, then it's a good idea to also export
a generic function COERCE-EMAIL-DESIGNATOR which is
supposed to take a user's object and turn it into something
that the above function EMAIL-DESIGNATOR can understand.
If CLIENT is a class, users can already subclass that one
for extension. However:
Often you have a thing that simply contains a CLIENT (e.g.
a SESSION), and you might want to be able to just pass the
SESSION object to send-email to stand for its CLIENT slot.
In that case COERCE-EMAIL-DESIGNATOR would be the right thing.
* You may also want to define a type EMAIL in your case,
and declaim the ftype of your functions to take those.
Or use something like DEFINE-API which does exactly that
in a more succinct way. You can find it in named-readtables,
or sequence-iterators.
E.g. in your example:
(deftype email () `(or string unsigned-byte client))
(define-api send-email (to from subject body)
(email email string string => (values R1 R2)) ; R1,R2
(let ((to (email-designator to)) ; return
(from (email-designator from))) ; types
...)
This has two advantages: 1. C-c C-d d will show you the
function's argument types (unfortunately fully expanded in case
of SBCL. It would be nice if it stored the original types and
gave access to it.) 2. passing a wrong type can be caught at
compile time if that type is known at compile time.
E.g. writing (send-email :A :B ...) will result in a warning
at compile-time.
3. Combined with good docstrings, you can produce nice documentation
of the code automatically. The Documentation for named-readtables
and sequence-iterators were entirely auto-generated. Combined
with hyperdoc and slime-hyperdoc, you can get access to
documentation and argument types directly from within Slime very
conveniently.
T
More information about the pro
mailing list