From: Chaitanya Gupta
Subject: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f8221f$7tc$1@registered.motzarella.org>
Hi,

I have written a simple macro (WITH-FLOW-CONTROL) to control the flow of
execution of certain clauses passed as its body. Basically the macro
takes an option and a body of clauses, and depending on the option,
executes the clauses in a certain way.

Basically its called like this -

(with-flow-control (option)
  (:get () (cache-get))
  (:source () (source-get))
  (:put (val) (cache-put val))
  (:delete () (cache-del)))


Here, option could be any symbol I define as a flow control option using
another macro - DEFINE-FLOW-CONTROL-OPTION.

e.g.

(define-flow-control-option (:normal)
  (or (call-clause :get)
      (let ((source-response (call-clause :source)))
	(call-clause :put source-response)
	source-response)))


i.e. when WITH-FLOW-CONTROL above is passed the :normal option, it first
calls CACHE-GET. If the cache response is NIL, it calls SOURCE-GET, and
the response from SOURCE-GET is passed to CACHE-PUT before being returned.

CALL-CLAUSE is a local function defined by DEFINE-FLOW-CONTROL-OPTION to
refer to the correct clause name inside WITH-FLOW-CONTROL.

The advantage of this macro is that I can have the same body of code
behaving in different ways based on what option I pass to it. To take
another example, we can have an option like :source -

(define-flow-control-option (:source)
  (let ((source-response (call-clause :source)))
    (call-clause :put source-response)
    source-response))

i.e. get direct from soure, and put the response in cache.

or

(define-flow-control-option (:get+delete)
  (let ((cache-response (call-clause :get)))
    (call-clause :delete)
    cache-response))

Get from cache, and then delete the cached value.


All good. But this is so trivial to implement, I am sure that someone
must have done it before, and much more elegantly. I have looked, but I
can't find anything like this. I don't even know if WITH-FLOW-CONTROL is
the correct name for such a macro. Can anyone enlighten me?


Thanks,


And for what its worth, here's the source of these two macros -


(defvar *flow-control-options* (make-hash-table))

(defun call-with-flow-control (option &rest clauses)
  (funcall (gethash option *flow-control-options*) clauses))


(defmacro with-flow-control ((control-option) &rest clauses)
  "CONTROL-OPTION (defined by DEFINE-FLOW-CONTROL-OPTION) controls how
CLAUSES are executed.
Each CLAUSE should be of type (CLAUSE-NAME ARGS . BODY)."
  (flet ((encapsulate-clause (clause)
	   (destructuring-bind (clause-name args &rest body)
	       clause
	     `(list ,clause-name
		    (lambda ,args
		      ,@body)))))
    `(call-with-flow-control ,control-option
				 ,@(mapcar #'encapsulate-clause clauses))))


(defmacro define-flow-control-option ((option) &body body)
  "Names an OPTION which controls how clauses are controlled inside a
WITH-FLOW-CONTROL macro."
  (let ((clauses-var (gensym)))
    `(progn
       (setf (gethash ,option *flow-control-options*)
	     #'(lambda (,clauses-var)
		 (flet ((call-clause (clause-name &rest args)
			  (let ((clause (assoc clause-name ,clauses-var)))
			    (when clause
			      (apply (second clause) args)))))
		   ,@body)))
       ,option)))



Chaitanya

From: Alex Mizrahi
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <46a4a19a$0$90274$14726298@news.sunsite.dk>
(message (Hello 'Chaitanya)
(you :wrote  :on '(Mon, 23 Jul 2007 16:35:54 +0530))
(

 CG> I have written a simple macro (WITH-FLOW-CONTROL) to control the flow
 CG> of execution of certain clauses passed as its body. Basically the macro
 CG> takes an option and a body of clauses, and depending on the option,
 CG> executes the clauses in a certain way.

unless you are doing this as excercise, you need to analyze whether such 
stuff happens often enough in your code to be encapsulated into a macro. 
does it? you've demostrated it can be used for controlling cache behaviour, 
that's good, but something other than caches?
if there's no use outside caching, it might be better to create facilities 
tied strictly to caching.

then, is it intentional that option is evaluated? is it supposed to be 
changed in run-time? if not, it's possible to generate appropriate code 
instead of doing dynamic dispatch.

next, i think what you're doing is very similar to OOP, CLOS in CL case. 
with-flow-control kinda defines "object" and it's "methods", and
define-flow-control-option defines a function that uses "object"'s 
"methods", doing dynamic dispatch.
however in your case dynamic dispatch looks more similar to C++ version than 
to CLOS, since "methods" do really belong to "object", and it uses something 
like vtbl to find correct method.
also your version is different because there are no top-level named class 
declaration -- each occurence of WITH-FLOW-CONTROL kinda defines it's own 
unnamed class. but again, it's similar to C++ -- you can define class inside 
a function in C++ (however, it's methods won't capture local variables in 
this case).

but i think these differences are not essential, and at least for this 
example with caching, it's quite easy to build a CLOS version, and it will 
even provide more advantages.

but your WITH-FLOW-CONTROL also has some advantages over CLOS -- it's 
possible to define methods on-fly, and it's also possible to do 
contract-checking in macroexpansion/compilation time (when it CLOS it will 
bang in execution-time).

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"scorn") 
From: Chaitanya Gupta
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f82e5f$d5b$1@registered.motzarella.org>
Alex Mizrahi wrote:
> (message (Hello 'Chaitanya)
> (you :wrote  :on '(Mon, 23 Jul 2007 16:35:54 +0530))
> (
> 
>  CG> I have written a simple macro (WITH-FLOW-CONTROL) to control the flow
>  CG> of execution of certain clauses passed as its body. Basically the macro
>  CG> takes an option and a body of clauses, and depending on the option,
>  CG> executes the clauses in a certain way.
> 
> unless you are doing this as excercise, you need to analyze whether such 
> stuff happens often enough in your code to be encapsulated into a macro. 
> does it? you've demostrated it can be used for controlling cache behaviour, 
> that's good, but something other than caches?
> if there's no use outside caching, it might be better to create facilities 
> tied strictly to caching.

Actually, I had initially called it WITH-CACHE-CONTROL. But then I
realized that it had had nothing to do with caching per se, so I changed
the name to WITH-FLOW-CONTROL.

> 
> then, is it intentional that option is evaluated? is it supposed to be 
> changed in run-time? 

Yes. I see that would be clearer if with-flow-control were called this way -

(with-flow-control option
  ...)

rather than

(with-flow-control (option)
  ...)

And yes, changing the option at runtime is the whole idea. Same piece of
code behaves differently, depending on what option I pass to it. Also,
by not baking the option code in the macroexpansion of
with-flow-control, I can change option's behaviour without needing to
recompile my code. And, of course, add new options on the fly.

> 
> next, i think what you're doing is very similar to OOP, CLOS in CL case. 
> with-flow-control kinda defines "object" and it's "methods", and
> define-flow-control-option defines a function that uses "object"'s 
> "methods", doing dynamic dispatch.
> however in your case dynamic dispatch looks more similar to C++ version than 
> to CLOS, since "methods" do really belong to "object", and it uses something 
> like vtbl to find correct method.
> also your version is different because there are no top-level named class 
> declaration -- each occurence of WITH-FLOW-CONTROL kinda defines it's own 
> unnamed class. but again, it's similar to C++ -- you can define class inside 
> a function in C++ (however, it's methods won't capture local variables in 
> this case).
> 
> but i think these differences are not essential, and at least for this 
> example with caching, it's quite easy to build a CLOS version, and it will 
> even provide more advantages.
> 

Hmm... I didn't give much thought to the CLOS approach. From what I can
see, while it might work well for a simple case, but when the "methods"
of each with-flow-control "object" become more complex (e.g. need to
know more context), it might become kludgy. Still, I'll think about it.
CLOS might just work out for the better.


> but your WITH-FLOW-CONTROL also has some advantages over CLOS -- it's 
> possible to define methods on-fly, and it's also possible to do 
> contract-checking in macroexpansion/compilation time (when it CLOS it will 
> bang in execution-time).

Didn't exactly understand this last para. Care to elaborate?

Thanks,

Chaitanya
From: Alex Mizrahi
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <46a4d5fb$0$90269$14726298@news.sunsite.dk>
(message (Hello 'Chaitanya)
(you :wrote  :on '(Mon, 23 Jul 2007 20:02:50 +0530))
(

 ??>> there's no use outside caching, it might be better to create
 ??>> facilities tied strictly to caching.

 CG> Actually, I had initially called it WITH-CACHE-CONTROL. But then I
 CG> realized that it had had nothing to do with caching per se, so I
 CG> changed the name to WITH-FLOW-CONTROL.

i think caching can be done in much less verbose manner. certainly cache is 
an object.
typically such object is already associated with put, get and delete 
methods, so normally you do not need to explicitly specify accessors.
so it could be:

(with-cache (cache-object cache-policy)
   (get-value-from-source))

as you see, it's much less verbose than your version. do you really need 
your version to be that verbose?
you might want that if you have custom get/put/delete for each code piece, 
but i simply cannot imagine situation when that's really needed..
typically only finite set of different caches is used in the application.

;;as a sidenote, you one might also want validation guard for cache, but 
your example didn't have it

 CG> And yes, changing the option at runtime is the whole idea. Same piece
 CG> of code behaves differently, depending on what option I pass to it.

if it's controlled globally, it's better to have a special variable keeping 
track of current caching options.
again, i don't believe that each piece of your code has some custom options 
and they are changing all the time..

 CG> Hmm... I didn't give much thought to the CLOS approach. From what I can
 CG> see, while it might work well for a simple case, but when the "methods"
 CG> of each with-flow-control "object" become more complex (e.g. need to
 CG> know more context), it might become kludgy. Still, I'll think about it.
 CG> CLOS might just work out for the better.

as i've said, i cannot imagine get/put/delete methods being special for each 
case. in most cases you can just pass parameters to cache-object, and it 
will rule this out.

also, there's an interesting thing to consider -- ContextL, that's kinda 
CLOS extension that allows to alter behaviour of objects depending on the 
context. certainly this can be done with some special variable and manual 
dispatch, but if you like macroses, that can work well in your case :).

 ??>> but your WITH-FLOW-CONTROL also has some advantages over CLOS -- it's
 ??>> possible to define methods on-fly, and it's also possible to do
 ??>> contract-checking in macroexpansion/compilation time (when it CLOS it
 ??>> will bang in execution-time).

 CG> Didn't exactly understand this last para. Care to elaborate?

if you extend your macro a bit, you can use it to ensure that all "methods" 
(:get, :put, :delete, :source) are defined in WITH-FLOW-CONTROL -- if one is 
missing it can signal error at macroexpansion time. certainly you'd need to 
extend syntax a little. for example, you can write macro-generating-macro 
(!):

(define-flow-controller WITH-CACHE-CONTROL :required-options (:get :put 
:delete :source))

(WITH-CACHE-CONTROL (option) ..)

as far as i know, it's not possible to enforce existence of specialization 
of generic functions in CLOS, so your system is advantageous in this case. 
about half-year ago i was searching for CLOS alternative with such 
property..

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"scorn") 
From: Chaitanya Gupta
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f82ock$1uc$1@registered.motzarella.org>
Alex Mizrahi wrote:
> 
> i think caching can be done in much less verbose manner. certainly cache is 
> an object.
> typically such object is already associated with put, get and delete 
> methods, so normally you do not need to explicitly specify accessors.
> so it could be:
> 
> (with-cache (cache-object cache-policy)
>    (get-value-from-source))

Actually I had precisely such a thing in mind to handle my most common
use cases... but I do need custom behaviour at a few places (something
which can be handled by WITH-FLOW-CONTROL).

> 
>  CG> And yes, changing the option at runtime is the whole idea. Same piece
>  CG> of code behaves differently, depending on what option I pass to it.
> 
> if it's controlled globally, it's better to have a special variable keeping 
> track of current caching options.
> again, i don't believe that each piece of your code has some custom options 
> and they are changing all the time..

Its not necessary that it is controlled globally (in fact, there
probably won't be any global policy). The options won't change all the
time... but I do prefer a bit of "dynamism" over performance -
especially while developing.


> 
>  CG> Hmm... I didn't give much thought to the CLOS approach. From what I can
>  CG> see, while it might work well for a simple case, but when the "methods"
>  CG> of each with-flow-control "object" become more complex (e.g. need to
>  CG> know more context), it might become kludgy. Still, I'll think about it.
>  CG> CLOS might just work out for the better.
> 
> as i've said, i cannot imagine get/put/delete methods being special for each 
> case. in most cases you can just pass parameters to cache-object, and it 
> will rule this out.

Agreed.

> 
> also, there's an interesting thing to consider -- ContextL, that's kinda 
> CLOS extension that allows to alter behaviour of objects depending on the 
> context. certainly this can be done with some special variable and manual 
> dispatch, but if you like macroses, that can work well in your case :).

I love macros ;)
Thanks for mentioning about ContextL. Will check it out.

> 
>  ??>> but your WITH-FLOW-CONTROL also has some advantages over CLOS -- it's
>  ??>> possible to define methods on-fly, and it's also possible to do
>  ??>> contract-checking in macroexpansion/compilation time (when it CLOS it
>  ??>> will bang in execution-time).
> 
>  CG> Didn't exactly understand this last para. Care to elaborate?
> 
> if you extend your macro a bit, you can use it to ensure that all "methods" 
> (:get, :put, :delete, :source) are defined in WITH-FLOW-CONTROL -- if one is 
> missing it can signal error at macroexpansion time. certainly you'd need to 
> extend syntax a little. for example, you can write macro-generating-macro 
> (!):
> 
> (define-flow-controller WITH-CACHE-CONTROL :required-options (:get :put 
> :delete :source))
> 
> (WITH-CACHE-CONTROL (option) ..)

Ok. Got the point. Its an interesting thing to explore. Thanks.


Chaitanya
From: Chaitanya Gupta
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f822rm$a6n$1@registered.motzarella.org>
Chaitanya Gupta wrote:
> All good. But this is so trivial to implement, I am sure that someone
> must have done it before, and much more elegantly. I have looked, but I
> can't find anything like this. I don't even know if WITH-FLOW-CONTROL is
> the correct name for such a macro. Can anyone enlighten me?
> 

Or, is there something in CL which could do the same thing for me,
without the need for these macros? (I don't think so, but I do think I
am Greenspunning something from CL itself - that's where the title comes
from ;) )
From: Alan Crowe
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <86bqe3ktk2.fsf@cawtech.freeserve.co.uk>
Chaitanya Gupta <····@chaitanyagupta.com> writes:

> Hi,
> 
> I have written a simple macro (WITH-FLOW-CONTROL) to control the flow of
> execution of certain clauses passed as its body. Basically the macro
> takes an option and a body of clauses, and depending on the option,
> executes the clauses in a certain way.
> 
> Basically its called like this -
> 
> (with-flow-control (option)
>   (:get () (cache-get))
>   (:source () (source-get))
>   (:put (val) (cache-put val))
>   (:delete () (cache-del)))
> 
> 
> Here, option could be any symbol I define as a flow control option using
> another macro - DEFINE-FLOW-CONTROL-OPTION.
> 
> e.g.
> 
> (define-flow-control-option (:normal)
>   (or (call-clause :get)
>       (let ((source-response (call-clause :source)))
> 	(call-clause :put source-response)
> 	source-response)))
> 

I'm struggling to analyse this suggestion. I'm getting stuck
on imagining the duplication that it eliminates. I think
that an abstract macro like this needs to presented in a
before and after format. Start by giving three examples of the
kind of boiler plate that you find wearisome, then work
through the three examples showing how your macro pares the
source code down to the essential differences, tidying away
the infrastructure code common to all three cases.

To be more particular, I'm getting stuck on whether
"(source-get)" is to be taken literally or whether is is a
place holder for a blob of code that we do not want to
duplicate. This matters because I see little wrong with
implementing the flow control with a tree of ifs that
contains duplicated calls (source-get). If however
(source-get) merely represents a larger piece of code, one
wants to factor it out. But if one wants to factor it out,
why not put it in an flet with the tree of ifs inside.

For example

(if bool1
    (if bool2
        (progn ; frag1
          (do this)
          (do that))
        (progn
          (do something else)
          (and this too)))
    (if bool3
        (progn ; frag2
          (do this)
          (do that))
        (progn
          (do something else)
          (and this too))))

Becomes

(flet ((this&that ()
         (do this)
         (do that))
       (something-else()
         (do something else)
         (and this too)))
  (flet ((chose(flag)
           (if flag
               (this&that)
               (something-else))))
    (chose (if bool1 bool2 bool3))

Notice that the question of which version is better cannot
be answered at the level of coding style. Why are frag1 and
frag2 the same? If they are necessarily the same, and
any-one who alters frag1 must remember to make the same
change to frag2 then version two expresses this and is
better. If however they are coincidently the same and it is
more likely that one will want to alter frag1 and frag2 so
that they become different, then version two is just a pain.

I feel that I'm failing to reconstruct the omitted details
of the argument for why your macro actually helps.

Alan Crowe
Edinburgh
Scotland
From: Chaitanya Gupta
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f82re5$dq4$1@registered.motzarella.org>
Alan Crowe wrote:
> 
> I'm struggling to analyse this suggestion. I'm getting stuck
> on imagining the duplication that it eliminates. I think
> that an abstract macro like this needs to presented in a
> before and after format. Start by giving three examples of the
> kind of boiler plate that you find wearisome, then work
> through the three examples showing how your macro pares the
> source code down to the essential differences, tidying away
> the infrastructure code common to all three cases.
> 

To extend the example of caches, lets say my normal flow looks like this -

(or (cache-get some-key)
    (let ((source-response (source-get)))
      (cache-put some-key source-response other-options)
      source-response))

But, things don't always work normally, so I have some other cache
policies, like get from source, or get only from cache, etc..

So I can do something like

(case cache-policy
  (:normal (or (cache-get some-key)
	       (let ((source-response (source-get)))
		 (cache-put some-key source-response other-options)
		 source-response)))
  (:source (let ((source-response (source-get)))
	     (cache-put some-key source-response other-options)
	     source-response))
  (:cache-only (cache-get some-key))
  ...
)


All's ok here. But then I need to use this piece of code in half a dozen
places (with minor differences, like (source-get), everywhere). Sure I
can copy paste this, but what happens when one of the policy changes a
bit (say, everytime I make a source-only call, increment a counter)? Or
I need to add a new policy?


As Alex pointed out, all this can be nicely encapsulated in a macro like
this -

(with-cache (cache-object cache-policy)
   (get-value-from-source))

But I do need custom behaviour at some places. And with
define-flow-control-option and with-flow-control, I can get my custom
behaviour and also have one central place from where I can control
cache-policy.

i.e. If with-cache above expands to something like this

(with-flow-control (cache-policy)
  (:get () (cache-get (cache-object->key cache-object)))
  (:source () (source-get))
  (:put (val) (cache-put (cache-object->key cache-object) val))
  ...
)

I can use it for my most common cases, but when I need more fine grained
control over what goes into :get, :source, etc. clauses above, I can just do


(with-flow-control (cache-policy)
  (:get () (cache-get some-key) (do-something))
  (:source () (do-something-else) (source-get))
  (:put (val) (cache-put (cache-object->key cache-object) val))
  ...
)



> To be more particular, I'm getting stuck on whether
> "(source-get)" is to be taken literally or whether is is a
> place holder for a blob of code that we do not want to
> duplicate. This matters because I see little wrong with
> implementing the flow control with a tree of ifs that
> contains duplicated calls (source-get). If however
> (source-get) merely represents a larger piece of code, one
> wants to factor it out. 

Yes, (source-get) represents a larger piece of code.

> But if one wants to factor it out,
> why not put it in an flet with the tree of ifs inside.


My initial version of CALL-WITH-FLOW-CONTROL was just like that, with
flow control options (cache policies), defined as various clauses of a
CASE statement. But then again, why not have an interface for defining
new options, rather than modifying a big CALL-WITH-FLOW-CONTROL
everytime you need to add or modify some option?

> 
> For example
> 
> (if bool1
>     (if bool2
>         (progn ; frag1
>           (do this)
>           (do that))
>         (progn
>           (do something else)
>           (and this too)))
>     (if bool3
>         (progn ; frag2
>           (do this)
>           (do that))
>         (progn
>           (do something else)
>           (and this too))))
> 
> Becomes
> 
> (flet ((this&that ()
>          (do this)
>          (do that))
>        (something-else()
>          (do something else)
>          (and this too)))
>   (flet ((chose(flag)
>            (if flag
>                (this&that)
>                (something-else))))
>     (chose (if bool1 bool2 bool3))
> 
> Notice that the question of which version is better cannot
> be answered at the level of coding style. Why are frag1 and
> frag2 the same? If they are necessarily the same, and
> any-one who alters frag1 must remember to make the same
> change to frag2 then version two expresses this and is
> better. If however they are coincidently the same and it is
> more likely that one will want to alter frag1 and frag2 so
> that they become different, then version two is just a pain.
> 
> I feel that I'm failing to reconstruct the omitted details
> of the argument for why your macro actually helps.

I can see the point you are trying to make, and I hope I am a bit more
clear now...


Chaitanya
From: Pascal Costanza
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <5gkc5pF3hg2vgU1@mid.individual.net>
Chaitanya Gupta wrote:
> Alan Crowe wrote:
>> I'm struggling to analyse this suggestion. I'm getting stuck
>> on imagining the duplication that it eliminates. I think
>> that an abstract macro like this needs to presented in a
>> before and after format. Start by giving three examples of the
>> kind of boiler plate that you find wearisome, then work
>> through the three examples showing how your macro pares the
>> source code down to the essential differences, tidying away
>> the infrastructure code common to all three cases.
>>
> 
> To extend the example of caches, lets say my normal flow looks like this -
> 
> (or (cache-get some-key)
>     (let ((source-response (source-get)))
>       (cache-put some-key source-response other-options)
>       source-response))
> 
> But, things don't always work normally, so I have some other cache
> policies, like get from source, or get only from cache, etc..
> 
> So I can do something like
> 
> (case cache-policy
>   (:normal (or (cache-get some-key)
> 	       (let ((source-response (source-get)))
> 		 (cache-put some-key source-response other-options)
> 		 source-response)))
>   (:source (let ((source-response (source-get)))
> 	     (cache-put some-key source-response other-options)
> 	     source-response))
>   (:cache-only (cache-get some-key))
>   ...
> )
> 
> 
> All's ok here. But then I need to use this piece of code in half a dozen
> places (with minor differences, like (source-get), everywhere). Sure I
> can copy paste this, but what happens when one of the policy changes a
> bit (say, everytime I make a source-only call, increment a counter)? Or
> I need to add a new policy?
> 
> 
> As Alex pointed out, all this can be nicely encapsulated in a macro like
> this -
> 
> (with-cache (cache-object cache-policy)
>    (get-value-from-source))
> 
> But I do need custom behaviour at some places. And with
> define-flow-control-option and with-flow-control, I can get my custom
> behaviour and also have one central place from where I can control
> cache-policy.
> 
> i.e. If with-cache above expands to something like this
> 
> (with-flow-control (cache-policy)
>   (:get () (cache-get (cache-object->key cache-object)))
>   (:source () (source-get))
>   (:put (val) (cache-put (cache-object->key cache-object) val))
>   ...
> )
> 
> I can use it for my most common cases, but when I need more fine grained
> control over what goes into :get, :source, etc. clauses above, I can just do
> 
> 
> (with-flow-control (cache-policy)
>   (:get () (cache-get some-key) (do-something))
>   (:source () (do-something-else) (source-get))
>   (:put (val) (cache-put (cache-object->key cache-object) val))
>   ...
> )

This smells like a good example for using ContextL. The idea is that 
there is basic functionality (getting something from a source) and 
different kinds of behavioral variations (getting it from a cache in 
different ways) which could be put in different :around methods.

ContextL allows you to express basic (context-independent) behavior in 
the root layer (the base program) and put behavioral variations in 
several layers. Depending on context, you can then activate and 
deactivate such layers, which results in different behaviors. As a rough 
sketch:

(define-layered-function get-it (...))

(define-layered-method get-it (...))

(deflayer cached)

(define-layered-method get-it :in-layer cached :around (...)
   (or (get-it-from-cache ...)
       (setf (get-it-from-cache ...)
             (call-next-method))))

(deflayer cache-only)

(define-layered-method get-it :in-layer cache-only :around (...)
   (get-it-from-cache ...))

(with-active-layers (cached)
   (get-it ...))

I hope you get the idea...


Pascal

-- 
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Chaitanya Gupta
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f84aui$dnb$1@registered.motzarella.org>
Pascal Costanza wrote:
> Chaitanya Gupta wrote:
>> Alan Crowe wrote:
>>> I'm struggling to analyse this suggestion. I'm getting stuck
>>> on imagining the duplication that it eliminates. I think
>>> that an abstract macro like this needs to presented in a
>>> before and after format. Start by giving three examples of the
>>> kind of boiler plate that you find wearisome, then work
>>> through the three examples showing how your macro pares the
>>> source code down to the essential differences, tidying away
>>> the infrastructure code common to all three cases.
>>>
>>
>> To extend the example of caches, lets say my normal flow looks like
>> this -
>>
>> (or (cache-get some-key)
>>     (let ((source-response (source-get)))
>>       (cache-put some-key source-response other-options)
>>       source-response))
>>
>> But, things don't always work normally, so I have some other cache
>> policies, like get from source, or get only from cache, etc..
>>
>> So I can do something like
>>
>> (case cache-policy
>>   (:normal (or (cache-get some-key)
>>            (let ((source-response (source-get)))
>>          (cache-put some-key source-response other-options)
>>          source-response)))
>>   (:source (let ((source-response (source-get)))
>>          (cache-put some-key source-response other-options)
>>          source-response))
>>   (:cache-only (cache-get some-key))
>>   ...
>> )
>>
>>
>> All's ok here. But then I need to use this piece of code in half a dozen
>> places (with minor differences, like (source-get), everywhere). Sure I
>> can copy paste this, but what happens when one of the policy changes a
>> bit (say, everytime I make a source-only call, increment a counter)? Or
>> I need to add a new policy?
>>
>>
>> As Alex pointed out, all this can be nicely encapsulated in a macro like
>> this -
>>
>> (with-cache (cache-object cache-policy)
>>    (get-value-from-source))
>>
>> But I do need custom behaviour at some places. And with
>> define-flow-control-option and with-flow-control, I can get my custom
>> behaviour and also have one central place from where I can control
>> cache-policy.
>>
>> i.e. If with-cache above expands to something like this
>>
>> (with-flow-control (cache-policy)
>>   (:get () (cache-get (cache-object->key cache-object)))
>>   (:source () (source-get))
>>   (:put (val) (cache-put (cache-object->key cache-object) val))
>>   ...
>> )
>>
>> I can use it for my most common cases, but when I need more fine grained
>> control over what goes into :get, :source, etc. clauses above, I can
>> just do
>>
>>
>> (with-flow-control (cache-policy)
>>   (:get () (cache-get some-key) (do-something))
>>   (:source () (do-something-else) (source-get))
>>   (:put (val) (cache-put (cache-object->key cache-object) val))
>>   ...
>> )
> 
> This smells like a good example for using ContextL. The idea is that
> there is basic functionality (getting something from a source) and
> different kinds of behavioral variations (getting it from a cache in
> different ways) which could be put in different :around methods.
> 
> ContextL allows you to express basic (context-independent) behavior in
> the root layer (the base program) and put behavioral variations in
> several layers. Depending on context, you can then activate and
> deactivate such layers, which results in different behaviors. As a rough
> sketch:
> 
> (define-layered-function get-it (...))
> 
> (define-layered-method get-it (...))
> 
> (deflayer cached)
> 
> (define-layered-method get-it :in-layer cached :around (...)
>   (or (get-it-from-cache ...)
>       (setf (get-it-from-cache ...)
>             (call-next-method))))
> 
> (deflayer cache-only)
> 
> (define-layered-method get-it :in-layer cache-only :around (...)
>   (get-it-from-cache ...))
> 
> (with-active-layers (cached)
>   (get-it ...))
> 
> I hope you get the idea...
> 

This seems very interesting. ContextL was pointed out earlier in the
thread too. Will definitely give it a try.

Thanks,

Chaitanya
From: Alan Crowe
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <868x96lura.fsf@cawtech.freeserve.co.uk>
Chaitanya Gupta <····@chaitanyagupta.com> writes:

> To extend the example of caches, lets say my normal flow looks like this -
> 
> (or (cache-get some-key)
>     (let ((source-response (source-get)))
>       (cache-put some-key source-response other-options)
>       source-response))
> 
> But, things don't always work normally, so I have some other cache
> policies, like get from source, or get only from cache, etc..
> 
> So I can do something like
> 
> (case cache-policy
>   (:normal (or (cache-get some-key)
> 	       (let ((source-response (source-get)))
> 		 (cache-put some-key source-response other-options)
> 		 source-response)))
>   (:source (let ((source-response (source-get)))
> 	     (cache-put some-key source-response other-options)
> 	     source-response))
>   (:cache-only (cache-get some-key))
>   ...
> )
> 
> 
> All's ok here. But then I need to use this piece of code in half a dozen
> places (with minor differences, like (source-get), everywhere). Sure I
> can copy paste this, but what happens when one of the policy changes a
> bit (say, everytime I make a source-only call, increment a counter)? Or
> I need to add a new policy?

I'm not seeing why you need a macro. What about

(defun cache-get (key &optional (policy :normal)) ;untested
  (case policy
    (:normal (or (cache-get some-key)
	       (let ((source-response (source-get)))
		 (cache-put some-key source-response other-options)
		 source-response)))
    (:source (let ((source-response (source-get)))
	       (cache-put some-key source-response other-options)
	       source-response))
    (:cache-only (cache-get some-key))
     ...))

Then the call sites are (cache-get) and 
(cache-get :cache-only) etc.

You say that you wish to avoid copy and paste, but I'm not
sure what it is that you are trying to avoid. I'm trying to
chose between two interpretations of what you wrote.

ONE) You are worried about implementing a change in policy,
for example :source must increment a counter. You don't want
to go through all the places you pasted code to, sticking in
counter code. With the cache-get code centralized in (defun
cache-get ...) you make this change in just one place. Solved.

TWO) You are worried about implementing a change in policy,
meaning that you end up with more polices. Some call sites
invoke the new policy, others don't. Now there must be
something at each call site to indicate what kind it is. You
cannot avoid going through all your call sites deciding
which policy they are to follow. However, the annotation at
the call site could be very simple. For example, add another
flag to the function.

(defun cache-get (key &optional (policy :normal) (count nil) ;untested
  (case policy
    (:normal (or (cache-get some-key)
	       (let ((source-response (source-get)))
		 (cache-put some-key source-response other-options)
		 source-response)))
    (:source (let ((source-response (source-get)))
	       (cache-put some-key source-response other-options)
               (when count (incf *counter*))
	       source-response))
    (:cache-only (cache-get some-key))
     ...))

How confident are you in your mastery of scope and extent in
Common Lisp? Special variables might be the tool for the
job. Let me try to explain what I have in mind

(defun do-stuff (normal args &optional (policy :normal))
  (case policy
    (:normal ...) 
    (:extra ...)
    (:additional ...))

Coding goes OK at first. Lots of (do-stuff 3 4), (do-stuff 5
6), the occassional (do-stuff 7 8 :extra), one or two
(do-stuff 9 10 :additional).

Later on you settle to writing

(defun handle-extra-case (foo bar)
  ... (do-stuff 11 12 :extra)...(do-stuff 13 14 :extra)
  (do-stuff 15 16 :extra)...
  ;; This is getting really tedious, I want the :extra
  ;; option every time I do-stuff inside handle-extra-case
  ... (do-stuff 17 18 :extra) ... 
  ... (do-stuff 19 20 #|oh no not again|# :extra)...)

You can do this with

(defparameter *policy* :normal)

(defun do-stuff (normal args)
  (case *policy*
    (:normal ...) 
    (:extra ...)
    (:additional ...))

Then you can bind *policy*

(defun handle-extra-case (foo bar)
  (let ((*policy* :extra))
    (do-stuff 11 12)...(do-stuff 13 14)
    (let ((*policy* :additional))
      ;; Fractions are handled differently
      (do-stuff 12/5 17/3))
    ;; back to extra
    (do-stuff 15 16)
    (let ((*policy* :normal))
      ;; back to normal for one call
      (do-stuff 17 18))
    (do-stuff 19 20)))

That is nice for bulk changes, but clunky for chopping and
changing. You can get the best of both worlds like this

(defparameter *policy* :normal)

(defun do-stuff (normal args &optional (policy *policy*))
  (case policy
    (:normal ...) 
    (:extra ...)
    (:additional ...))

(defun handle-extra-case (foo bar)
  (let ((*policy* :extra))
    (do-stuff 11 12)...(do-stuff 113 14)
    (do-stuff 12/5 17/3 :additional)
    (do-stuff 15 16)
    (do-stuff 17 18 :normal) ;we have to ask for normal
                             ;because we have bound 
                             ;the default to extra
    (do-stuff 19 20)))

I hope I'm not missing what you are aiming at achieving.

Alan Crowe
Edinburgh
Scotland
From: Alex Mizrahi
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <46a5978e$0$90269$14726298@news.sunsite.dk>
(message (Hello 'Alan)
(you :wrote  :on '(23 Jul 2007 21:39:37 +0100))
(

 AC> (defun cache-get (key &optional (policy :normal)) ;untested
 AC>   (case policy
 AC>     (:normal (or (cache-get some-key)
 AC>         (let ((source-response (source-get)))
 AC>    (cache-put some-key source-response other-options)
 AC>    source-response)))
 AC>     (:source (let ((source-response (source-get)))
 AC>         (cache-put some-key source-response other-options)
 AC>         source-response))
 AC>     (:cache-only (cache-get some-key))
 AC>      ...))

typically source-get varies, so it can be kinda

(defun cache-get (ket source-getter) ... (funcall source-getter) ...)
(cache-get "key" (lambda () (pull-some-stuff-from-db))

so macro is preffered to eliminate lambda.

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"scorn") 
From: Chaitanya Gupta
Subject: Re: WITH-FLOW-CONTROL and Greenspun
Date: 
Message-ID: <f84a0m$b74$1@registered.motzarella.org>
Alan Crowe wrote:
> 
> I'm not seeing why you need a macro. What about
> 
> (defun cache-get (key &optional (policy :normal)) ;untested
>   (case policy
>     (:normal (or (cache-get some-key)
> 	       (let ((source-response (source-get)))
> 		 (cache-put some-key source-response other-options)
> 		 source-response)))
>     (:source (let ((source-response (source-get)))
> 	       (cache-put some-key source-response other-options)
> 	       source-response))
>     (:cache-only (cache-get some-key))
>      ...))
> 
> Then the call sites are (cache-get) and 
> (cache-get :cache-only) etc.
> 
> You say that you wish to avoid copy and paste, but I'm not
> sure what it is that you are trying to avoid. I'm trying to
> chose between two interpretations of what you wrote.
> 

(source-get) represents a blob of code which will vary at each of these
sites. That's why I want a macro. Of course I can use a lambda, but I
like macros :).


> ONE) You are worried about implementing a change in policy,
> for example :source must increment a counter. You don't want
> to go through all the places you pasted code to, sticking in
> counter code. With the cache-get code centralized in (defun
> cache-get ...) you make this change in just one place. Solved.
> 
> TWO) You are worried about implementing a change in policy,
> meaning that you end up with more polices. Some call sites
> invoke the new policy, others don't. Now there must be
> something at each call site to indicate what kind it is. You
> cannot avoid going through all your call sites deciding
> which policy they are to follow. However, the annotation at
> the call site could be very simple. For example, add another
> flag to the function.
> 
> (defun cache-get (key &optional (policy :normal) (count nil) ;untested
>   (case policy
>     (:normal (or (cache-get some-key)
> 	       (let ((source-response (source-get)))
> 		 (cache-put some-key source-response other-options)
> 		 source-response)))
>     (:source (let ((source-response (source-get)))
> 	       (cache-put some-key source-response other-options)
>                (when count (incf *counter*))
> 	       source-response))
>     (:cache-only (cache-get some-key))
>      ...))
> 
> How confident are you in your mastery of scope and extent in
> Common Lisp? Special variables might be the tool for the
> job. Let me try to explain what I have in mind
> 
> (defun do-stuff (normal args &optional (policy :normal))
>   (case policy
>     (:normal ...) 
>     (:extra ...)
>     (:additional ...))
> 
> Coding goes OK at first. Lots of (do-stuff 3 4), (do-stuff 5
> 6), the occassional (do-stuff 7 8 :extra), one or two
> (do-stuff 9 10 :additional).
> 
> Later on you settle to writing
> 
> (defun handle-extra-case (foo bar)
>   ... (do-stuff 11 12 :extra)...(do-stuff 13 14 :extra)
>   (do-stuff 15 16 :extra)...
>   ;; This is getting really tedious, I want the :extra
>   ;; option every time I do-stuff inside handle-extra-case
>   ... (do-stuff 17 18 :extra) ... 
>   ... (do-stuff 19 20 #|oh no not again|# :extra)...)
> 
> You can do this with
> 
> (defparameter *policy* :normal)
> 
> (defun do-stuff (normal args)
>   (case *policy*
>     (:normal ...) 
>     (:extra ...)
>     (:additional ...))
> 
> Then you can bind *policy*
> 
> (defun handle-extra-case (foo bar)
>   (let ((*policy* :extra))
>     (do-stuff 11 12)...(do-stuff 13 14)
>     (let ((*policy* :additional))
>       ;; Fractions are handled differently
>       (do-stuff 12/5 17/3))
>     ;; back to extra
>     (do-stuff 15 16)
>     (let ((*policy* :normal))
>       ;; back to normal for one call
>       (do-stuff 17 18))
>     (do-stuff 19 20)))
> 
> That is nice for bulk changes, but clunky for chopping and
> changing. You can get the best of both worlds like this
> 
> (defparameter *policy* :normal)
> 
> (defun do-stuff (normal args &optional (policy *policy*))
>   (case policy
>     (:normal ...) 
>     (:extra ...)
>     (:additional ...))
> 
> (defun handle-extra-case (foo bar)
>   (let ((*policy* :extra))
>     (do-stuff 11 12)...(do-stuff 113 14)
>     (do-stuff 12/5 17/3 :additional)
>     (do-stuff 15 16)
>     (do-stuff 17 18 :normal) ;we have to ask for normal
>                              ;because we have bound 
>                              ;the default to extra
>     (do-stuff 19 20)))
> 
> I hope I'm not missing what you are aiming at achieving.
> 

Thanks for all the trouble. It contains some useful stuff I can think
of. But I hope you see what I am trying to get at.


Chaitanya