[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