From: Don Geddis
Subject: CLOS dispatch on dynamic variables
Date: 
Message-ID: <m3r891b6jm.fsf@maul.geddis.org>
I'd appreciate some coding advice.

Here's the issue: I often use dynamic variables as a way to simplify code.
If a large set of functions are all roughly parameterized by a few variables,
it's often easier to set some dynamic variables than to carry them along in
every argument list.

Similarly, CLOS can simplify code by allowing each function (method) to only
work on one type of data at a time.

I think that I'd like to do both, which is roughly to do CLOS method
dispatching based on the value of a dynamic variable.  I don't think this is
directly possible.  Am I just confused here, or is there a standard way of
addressing this situation?

To be more concrete, let's look at a hypothetical set of functions:

(defun f1 (arg outtype)
  (cond ((numberp arg) (f2 (1+ arg) outtype))
        ((symbolp arg) (f2 (symbol-name arg) outtype))
        (t (error)) ))
(defun f2 (arg outtype)
  (cond ((numberp arg)
         (cond ((eq outtype :text) (format t "A number: ~D~%" arg))
               ((eq outtype :html) (format t "A number: ~D<br>" arg)) ))
        ((stringp arg)
         (cond ((eq outtype :text) (format t "A string: ~S~%" arg))
               ((eq outtype :html) (format t "A string: ~S<br>" arg)) ))
        (t (error)) ))

One method of simplifying the code is by using a dynamic variable for
the "output type" concept.  Then the parameter lists for F1 and F2 become
simpler:

(defvar *outtype*)
(defun f1 (arg)
  (cond ((numberp arg) (f2 (1+ arg)))
        ((symbolp arg) (f2 (symbol-name arg)))
        (t (error)) ))
(defun f2 (arg)
  (cond ((numberp arg)
         (cond ((eq *outtype* :text) (format t "A number: ~D~%" arg))
               ((eq *outtype* :html) (format t "A number: ~D<br>" arg)) ))
        ((stringp arg)
         (cond ((eq *outtype* :text) (format t "A string: ~S~%" arg))
               ((eq *outtype* :html) (format t "A string: ~S<br>" arg)) ))
        (t (error)) ))

A different way of simplifying the original code is to use CLOS methods, to
avoid (some of) the COND statements:

(defmethod f1 ((arg :number) outtype)
  (f2 (1+ arg) outtype) )
(defmethod f1 ((arg :symbol) outtype)
  (f2 (symbol-name arg) outtype) )
(defmethod f2 ((arg :number) outtype)
  (cond ((eq outtype :text) (format t "A number: ~D~%" arg))
        ((eq outtype :html) (format t "A number: ~D<br>" arg)) ))
(defmethod f2 ((arg :string) outtype)
  (cond ((eq outtype :text) (format t "A string: ~S~%" arg))
        ((eq outtype :html) (format t "A string: ~S<br>" arg)) ))

So finally, my original question: it would seem great if I could combine
these two code simplifications, and be able to do CLOS dispatch on the
dynamic value of *outtype*, but without including it in the explicit
argument lists.  That would wind up with something vaguely like this
(which is not real code):

(defvar *outtype*)
(defmethod f1 ((arg :number)) (f2 (1+ arg)))
(defmethod f1 ((arg :symbol)) (f2 (symbol-name arg)))
(defmethod f2 ((arg :number) &aux (*outtype* (eql :text)))
  (format t "A number: ~D~%" arg) )
(defmethod f2 ((arg :number) &aux (*outtype* (eql :html)))
  (format t "A number: ~D<br>" arg) )
(defmethod f2 ((arg :string) &aux (*outtype* (eql :text)))
  (format t "A string: ~S~%" arg) )
(defmethod f2 ((arg :string) &aux (*outtype* (eql :html)))
  (format t "A string: ~S<br>" arg) )

Is there some way of getting my ideal code structure?  (Yes, I know I could
put *OUTTYPE* explicitly in the calls to F2 inside of F1.  Part of the point
is that in my ideal structure you don't have to, and F1 need never know that
F2 is specialized on *OUTTYPE*.)

Has anyone else ever had this kind of coding desire?  Or have I just confused
myself somehow?

Thanks,

        -- Don
_______________________________________________________________________________
Don Geddis                    http://don.geddis.org              ···@geddis.org

From: Gabe Garza
Subject: Re: CLOS dispatch on dynamic variables
Date: 
Message-ID: <87smthfacr.fsf@ix.netcom.com>
Don Geddis <···@geddis.org> writes:

> [Mega-Snip]
>
> So finally, my original question: it would seem great if I could combine
> these two code simplifications, and be able to do CLOS dispatch on the
> dynamic value of *outtype*, but without including it in the explicit
> argument lists.

You could always use some light macros to hide the argument:

(defpackage *outtype*-dispatched-methods)

(defmacro def-*outtype*-dispatched-method (*outtype*-type &rest args)
  (let* ((function-name (car args))
	 (method-name (intern (string function-name)
			      :*outtype*-dispatched-methods))
	 (arg-name (gensym))
         (old-lambda-list (find-if #'listp args))
	 (lambda-list (cons `(,arg-name ,*outtype*-type)
                             old-lambda-list))
         (modified-args 
           (substitute lambda-list
		       old-lambda-list
		       (cons method-name (cdr args))
		       :count 1)))
    `(progn
       (defmethod ,@modified-args)
       (defun ,function-name (&rest args)
	 (apply  ',method-name *outtype* args)))))


(def-*outtype*-dispatched-method integer foo (arg)
  (format t "~%*OUTTYPE* is an integer!~%"))

(def-*outtype*-dispatched-method string foo (arg)
  (format t "~%*OUTTYPE* is a string!~%"))
    
* (setf *outtype* 3)
3
* (foo 'blah)
*OUTTYPE* is an integer!
NIL
* (setf *outtype* "foo")
"foo"
* (foo 'blah)
*OUTTYPE* is a string!
NIL
* 

You'd obviously need something a bit more elaborate if you used such
methods in more then one package and didn't want name collisions...

> Has anyone else ever had this kind of coding desire?  Or have I just
> confused myself somehow?

You should probably give more information about what you want to use
this for if you want good answers for that question.  Personally, I'd
probably just bite the bullet and explicitly put it in the argument
list, unless I was using going to be using it *a lot*.

Gabe Garza
From: Gabe Garza
Subject: Re: CLOS dispatch on dynamic variables
Date: 
Message-ID: <87of45fa0y.fsf@ix.netcom.com>
Gabe Garza <·······@ix.netcom.com> writes:

>          (old-lambda-list (find-if #'listp args))

Doh.  Change this to:

           (old-lambda-list (find-if #'listp (cdr args)))

To avoid losing on setf methods.

Gabe Garza
From: Gabe Garza
Subject: Re: CLOS dispatch on dynamic variables
Date: 
Message-ID: <87he9xf9th.fsf@ix.netcom.com>
Gabe Garza <·······@ix.netcom.com> writes:

> You'd obviously need something a bit more elaborate if you used such
> methods in more then one package and didn't want name collisions...

And I realized too late that this loses on setf methods. I guess the
name should be changed to DEF-*OUTTYPE*-DISPATCHED-NON-SETF-METHOD. :)

Next time I'll just stop at "you could always use macros to hide the
argument".

Gabe Garza
From: Tim Bradshaw
Subject: Re: CLOS dispatch on dynamic variables
Date: 
Message-ID: <ey3wuitt5gb.fsf@cley.com>
* Don Geddis wrote:
> I think that I'd like to do both, which is roughly to do CLOS method
> dispatching based on the value of a dynamic variable.  I don't think
> this is directly possible.  Am I just confused here, or is there a
> standard way of addressing this situation?

I'd do this (indeed, I do this) with small wrapping functions.  For
instance I have something like this:

(defun prompt-for-file-name (prompt &rest args &key &allow-other-keys)
  (apply #'interface-prompt-for-file-name
         *weld-default-user-interface*
         prompt args))

(defgeneric interface-prompt-for-file-name (interface prompt &key))

(defmethod interface-prompt-for-file-name ((interface ...) prompt ...)
  ...)

PROMPT-FOR-FILE-NAME is exported and documented as the function to
call, INTERFACE-PROMPT-FOR-FILE-NAME is exported and documented as the
GF to extend.  In the case above (which is lightly massaged from real
code) PROMPT-FOR-FILE-NAME is declared inline.

There are many variants on this, for instance you can have optional
arguments in the wrapper which default to the value of specials, which
are then made required (and hence specialisable) args to the GF that
does the work.

--tim
From: Frode Vatvedt Fjeld
Subject: Re: CLOS dispatch on dynamic variables
Date: 
Message-ID: <2h65qdm5hz.fsf@vserver.cs.uit.no>
Don Geddis <···@geddis.org> writes:

> [..] So finally, my original question: it would seem great if I
> could combine these two code simplifications, and be able to do CLOS
> dispatch on the dynamic value of *outtype*, but without including it
> in the explicit argument lists.

I don't think this would be a very good idea, aesthetically
speaking. Technically, perhaps this could be implemented with an
appropriate method-combination. Couldn't you just do something like
this?

  (funcall *outtype-function* arg)

-- 
Frode Vatvedt Fjeld
From: james anderson
Subject: Re: CLOS dispatch on dynamic variables
Date: 
Message-ID: <3E7ADDDE.1C118E81@setf.de>
Don Geddis wrote:
> 
> [] it would seem great if I could [] be able to do CLOS dispatch on the
> dynamic value of *outtype*, but without including it in the explicit
> argument lists.  That would wind up with something vaguely like this
> (which is not real code):
> 
> (defvar *outtype*)
> (defmethod f1 ((arg :number)) (f2 (1+ arg)))
> (defmethod f1 ((arg :symbol)) (f2 (symbol-name arg)))
> (defmethod f2 ((arg :number) &aux (*outtype* (eql :text)))
>   (format t "A number: ~D~%" arg) )
> (defmethod f2 ((arg :number) &aux (*outtype* (eql :html)))
>   (format t "A number: ~D<br>" arg) )
> (defmethod f2 ((arg :string) &aux (*outtype* (eql :text)))
>   (format t "A string: ~S~%" arg) )
> (defmethod f2 ((arg :string) &aux (*outtype* (eql :html)))
>   (format t "A string: ~S<br>" arg) )
> 

one could do something on this order

? 
(define-method-combination enumerated-special-binding (variable-name)
                           ((named-methods * :required t))
  (let ((form `(ecase ,variable-name
                 ((nil))
                 ,@(reduce #'nconc
                           (mapcar #'(lambda (method)
                                       (mapcar #'(lambda (qualifier)
                                                   `(,qualifier (call-method ,method ())))
                                               (method-qualifiers method)))
                                   named-methods)))))
    (pprint form)
    form))

ENUMERATED-SPECIAL-BINDING
? (defParameter *outtype* nil)

*OUTTYPE*
? (defGeneric special-f (arg stream)
  (:method-combination enumerated-special-binding *outtype*)
  (:method :text ((arg number) stream) (format stream "A number: ~D~%" arg))
  (:method :html ((arg number) stream) (format stream "A number: <code>~D</code>" arg))
  (:method :text ((arg string) stream) (format stream "A string: ~S~%" arg))
  (:method :html ((arg string) stream) (format stream "A string: <code>~s</code>" arg)))
  
#<STANDARD-GENERIC-FUNCTION SPECIAL-F #x65217AE>
? (special-f 1 *trace-output*)

(ECASE *OUTTYPE*
  ((NIL))
  (:HTML (CALL-METHOD #<STANDARD-METHOD SPECIAL-F :HTML (NUMBER T)> NIL))
  (:TEXT (CALL-METHOD #<STANDARD-METHOD SPECIAL-F :TEXT (NUMBER T)> NIL)))
NIL
? (special-f "test" *trace-output*)


(ECASE *OUTTYPE*
  ((NIL))
  (:HTML (CALL-METHOD #<STANDARD-METHOD SPECIAL-F :HTML (STRING T)> NIL))
  (:TEXT (CALL-METHOD #<STANDARD-METHOD SPECIAL-F :TEXT (STRING T)> NIL)))
NIL
? (let ((*outtype* :html))
  (special-f 1 *trace-output*)
  (special-f "test" *trace-output*))

A number: <code>1</code>A string: <code>"test"</code>
NIL
? (let ((*outtype* :text))
  (special-f 1 *trace-output*)
  (special-f "test" *trace-output*))

A number: 1
A string: "test"
NIL
?