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.