[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