[alexandria-devel] Review cycle 2: SWITCH, ESCWITCH, and CSWITCH

Pascal J. Bourguignon pjb at informatimago.com
Mon Apr 5 15:37:04 UTC 2010


On 2010/04/05, at 14:54 , Gustavo wrote:

> Hello,
>
> I thought that the whole purpose of switch was to compare some value  
> to the result of evaluated forms. I don't think it is nice to impose  
> a restriction that avoid using evaluated variables and forms, and I  
> didn't quite understand why it should.

Yes.  There should be a clear difference between SWITCH and CASE or  
COND.

The problems of CASE are:
- it takes only literals (not even constant variables, so you'd have  
to use #.+cst+)
- it uses EQL.

The problem of COND is that:
- it doesn't compare a single expression, you have to bind it and  
rewrite it in each test.



> As for allow either an atom or a list of forms, I don't like it very  
> much, but it should be more or less ok. Some particular cases should  
> be analyzed first, though, like
>
> (switch (something)
>   ('some-symbol ...)
>   (*some-variable* ...)
>   ...)
>
> It could lead to a wrong interpretation unless you explicitly test  
> if the first symbol of your list is quote. Another solution is to  
> require the user to write more parenthesis
>
> (switch (something)
>   (('some-symbol) ...)
>   (*some-variable* ...)
>   ...)
>
> An alternative to avoid this and other problems (e.g. excessive  
> parenthesis) is to allow the use of some key, like:
>
> (switch (something)
>   ((:any value1 value2 ...) ...)
>   ((car some-list) ...)
>   ...)
>
> In my opinion, this would be much better. Using the key :member  
> instead of :any would be a good option too.

I don't like adding such "syntax" when a pair of parentheses would  
just do, or even better, if we can come out with a regular syntax that  
doesn't need special treatments.


With respect to the treatment of the clauses, the question is at what  
time should they be evaluated.  In the case of CASE, it's at read-time  
and this is the problem of CASE.  In the case of COND, it's at run-time.

We have the choice between compilation-time (coarsely) and run-time.

At compilation time we would interpret a constant symbol as its value,  
but all the clause keys would be taken otherwise as constants  
(literals).

At run-time we would admit any expression.

I think that run-time evaluation of the keys would be more  
discriminating from CASE so it would be good, and on the other hand,  
if the macro or compiler see that all the keys are actually literals,  
it can still do compilation time optimizations, so it wouldn't be too  
bad an option.



If we choose run-time evaluation, there is the question of the order  
of evaluation.  Of course, the keys would be evaluated in sequence to  
stay with CL evaluation principles. But do we want short-cut  
evaluation, or can we imagine an algorithm where evaluating all the  
keys at once, and then finding the branch would be more efficient?  
(eg. if we could evaluate and select in parallel).  In which case we  
would also have to deal with duplicate values for the keys, possibly  
in different branches.   Currently, I don't see that we could provide  
anything better than testing sequentially, and therefore, short-cut  
evaluating the keys would be expected. (This is the current COND-like  
behavior).



Given a sensible default for :test, I find it unexpected to expect to  
surround the expression in parentheses.  Since all the clauses are  
lists, I would propose to leave the keyword options at the same level:

    (<switch-operator>  <expression> [ :test <test-expression> ]
       <clauses>)



At first, I'd agree that the :key argument is useless.  Even for  
clause keys, we can write as easily:

   (<switch-operator> (k expr) :test (lambda (expr key) (test expr (k  
key)))
      <clauses>)

but then we could argue that :key would be useful, if applied to both  
the expression and the clause keys.  The form above would be written:

   (<switch-operator> expr :test (function test) :key (function k)
      <clauses>)




Finally, concerning the list of keys per clauses, by the same  
reasoning as for :key, and given the choice fo run-time evaluation of  
the clause keys, I'd tend to think that we can restrict ourselves to a  
single value. If the user wants to check against a list of keys, she  
can do it with a specific :test funtion:

(switch item :test (function member)
    ('(1 2 3)  ...)
    ((list (get-one) (get-another)) ...)
    ((list *a-single-one*) ...)
    (otherwise ...))


Also, given run-time evaluation, I'd accept only OTHERWISE for the  
default clause, and leave T as a normal constant symbol to be  
evaluated and tested against, because T is a common value in lisp  
programs, while OTHERWISE is not.

At first I wouldn't make it an error to include an OTHERWISE clause in  
ESWITCH or CSWITCH, just a warning.  For me, ESWITCH means "I cover  
all the cases", and if I use OTHERWISE to do so, good for me!  On the  
other hand, I would understand the choice of making it an error. In  
this case, please add a mention to use SWITCH instead of ESWITCH/ 
CSWITCH in the error message.


In conclusion, I would like:

<switch-operator>  <expression> [[ :test <test-expression> | :key <key- 
expression> ]]
    <clauses>)

<switch-operator> ::= SWITCH | ESWITCH | CSWITCH .
<expression>      ::= <form> .  -- evaluating to any lisp object
<test-expression> ::= <form> .  -- evaluating to a function designator  
taking (at least) two arguments,
                                 -- returning a generalized boolean.
                                 -- The default is (function EQL).
<key-expression>  ::= <form> .  -- evaluating to a function designator  
taking (at least) one argument,
                                 -- returning a object to be tested in  
place of its arguments.
                                 -- The default is (function IDENTITY).

<clauses> ::= | <clause> <clauses> .
<clause>  ::= ( OTHERWISE <forms> ) .
<clause>  ::= ( <key-form> <forms> ).

It would evaluate the <expression>, the <test-expression> if present,  
and then each <key-form> in turn, until a clause is selected.  Then  
the following clause-keys won't be evaluated.

The order in which the test and key functions are called is  
unspecified, but they should be called at most once per source  
argument. (ie. key is called once for the expression, and once for  
each of the clause key forms that are evaluated; test is called once  
for each key form that is evaluated until it returns true).

The otherwise clause needs not be the last one, but its forms are  
evaluted only when all the tests returned NIl.



I put 'function designator', but it could be 'funcallable'.  I don't  
know if funcallable CLOS objects are designators of functions.  But my  
point is that the function forms are evaluated.  I would reject:  
(switch x :test string= ...).




SCASE could be written:

(switch <string-expression> :test (function string=)
   ("abc" ...)
   ("def" ...)
   ...
   (otherwise ...))

Note that when all the strings and the test function are literals  
(known at compilation-time), special run-time optimizations may apply.


-- 
__Pascal Bourguignon__
http://www.informatimago.com







More information about the alexandria-devel mailing list