Рассмотрим примеры:
(mapcar (lambda (x)
(case x
(1 :one)
(2 :two)
(3 :three)))
(list 0 1 2 3 4 5))
(mapcar (lambda (x)
(typecase x
(number :number)
(string :string)
(symbol x)))
(list :foo 1 :bar 2 "baz" 3))
Видно, что (lambda (x) (case x ...)) и (lambda (x) (typecase x ...) — шаблонный код. Попробуем избавиться от него.
Подобный boilerplate давно известен. Например, в OCaml есть конструкция совмещающая сопоставление с шаблоном (match expr with ...) с записью λ функции.
Например, такой код:
List.map (function x -> match x with
| 1 -> "one"
| 2 -> "two"
| _ -> "otherwise") [0; 1; 2; 3; 4; 5]
совершенно свободно заменяется на:
List.map (function 1 -> "one"
| 2 -> "two"
| _ -> "otherwise") [0; 1; 2; 3; 4; 5]
В библиотеке optima, которая реализует pattern matching как в функциональных языках, в пакете optima.extra имеются макросы lambda-match, lambda-ematch, lambda-cmatch которые именно эту проблему шаблонного кода и решают.
Но для конструкций сопоставления, таких как {c,e}case, {c,e}typecase и {c,e}switch из alexandria подобных макросов не предусмотрено. Исправим это. Пусть подобного рода макросы называются «lambda over matching» макросами, для идентификации.
«lambda over case»:
(defmacro lambda-case (&body clauses)
(with-gensyms (keyform)
`(lambda (,keyform)
(case ,keyform
,@clauses))))
С «lambda over typecase» макросом не всё так просто. В большинстве случаев понадобится доступ к сопоставляемому значению (x, в изначальном примере). В OCaml есть возможность связывать переменные с сопоставленными фрагментами или со всей сопоставляемой структурой (частый случай), например:
List.map (function
| (1 | 2) as x -> x + 1
| 3 -> 30
| _ -> 99) [0; 1; 2; 3; 4; 5]
Есть варианты: можно «пробросить» it как в классических анафорических макросах или, например, явно задать связывание, как в макросах навроде named-lambda. Я остановлюсь на первом варианте:
(defmacro lambda-typecase (&body clauses)
`(lambda (it)
(typecase it
,@clauses)))
Теперь изначальные примеры будут выглядеть вот так:
(mapcar (lambda-case
(1 :one)
(2 :two)
(3 :three))
(list 0 1 2 3 4 5))
(mapcar (lambda-typecase
(number :number)
(string :string)
(symbol it))
(list :foo 1 :bar 2 "baz" 3))
Сейчас как в OCaml за тем лишь исключением, что в OCaml этот синтаксический сахар «прибит гвоздями» к языку, а в CL расширен макросами и, естественно, такой подход применим к любым конструкциям сопоставления.
Конечно, для симметрии, имеет смысл определить lambda-ccase, lambda-ecase и lambda-ctypecase, lambda-etypecase. А ещё придумать, как написать lambda-switch макрос, а конкретнее, как «пробросить» предикат и функцию-трансформатор (test и key аргументы) в switch.