2013-03-18 3 views
5

Я нахожусь в ситуации, когда мне нужно объединить несколько предикатов в один. Есть ли стандартный способ сделать это, что-то похожее на compliment?Булевы функторы в lisp

Предположим, что существует несколько простых предикатов (например is-fruit-p, is-red-p, grows-on-trees-p и т.д.), а также список объектов, из которых подмножество должны быть отфильтрованы с использованием более одного предиката. Какой лучший способ добиться этого, чем следующее:

(remove-if #'is-fruit-p 
      (remove-if #'is-red-p 
         (remove-if #'grows-on-trees-p list-of-objects))) 
+4

'(DEFUN комплимент (лицо и факультативный (поток т)) (формат поток "Здравствуйте, ~ а, вы действительно великолепно смотритесь сегодня!" Человек))' – Svante

ответ

3

Я не уверен, что такая функция доступна из коробки. Если вам нужно объединить функции, которые вы можете определить во время компиляции, вы можете написать макрос для этого. Если вам нужно динамически обнаруживать функции предикатов, вы можете написать функцию для этого, которая будет зацикливать список функций и накапливать результаты до ложного состояния.

Макрос может выглядеть следующим образом:

(defmacro combine-predicates (combine-func &rest preds) 
    (let ((x (gensym))) 
    `(lambda (,x) (,combine-func ,@(loop for p in preds 
         collecting `(funcall ,p ,x)))))) 

И вы можете использовать его как этот

(remove-if (combine-predicates and 
           #'is-fruit-p 
           #'is-red-p 
           #'grows-on-trees-p) obj-list) 
+1

Спасибо, что вы думаете об удалении 'funcall' и '# '' s? – zzandy

+0

Я не уверен, как funcall влияет на производительность, поэтому, если вы хотите, вы можете удалить funcall. На самом деле я волновался, что передача (лямбда (х) (...)) не сработает, но это произошло, по крайней мере, в sbcl. Но прохождение # '(лямбда (х) (...)) не было. Если вы не используете старый синтаксис лямбды, я думаю, что funcalls можно удалить. Если вы не хотите использовать некоторую переменную времени компиляции для установки функций предиката во время компиляции. – JustAnotherCurious

+0

Обратите внимание, что если вы используете Metatilities, эта функция уже существует как 'conjoin'. – Hugh

2
(let ((predicates '(zerop evenp))) 
    (remove-if (lambda (item) 
       (some (lambda (fn) (funcall fn item)) 
        predicates)) 
      '(0 1 2 3 4 0 1 2 3 4))) 
5

Вы уверены, что специальный синтаксис действительно поможет? Рассмотрим следующий

(lambda (x) 
    (and (is-fruit-p x) 
     (or (grows-on-tree-p x) 
      (is-red-p x)))) 

и теперь чуть более общий

(lambda (x) 
    (and (is-fruit-p x) 
     (or (grows-on-tree-p x) 
      (eq (color x) 'red)))) 

или

(lambda (x) 
    (and (is-fruit-p x) 
     (or (grows-on-tree-p x) 
      (eq (color x) desired-color)))) ; desired-color captured lexical 

Даже если вы строите специальный синтаксис для предикаты вы думаете, добавленная сложность языка стоит жесткость, которую вы получите? Например, вы собираетесь определить предикат #'weights-exactly-five-ounces-p? Как насчет #'weights-up-to-and-including-six-and-half-ounces-p?

Если вы начинаете нуждаться в параметрическом предикате и используете для этого лямбда-формы, то с помощью комбайнера вы будете писать больше кода, чем не использовать его, потому что для каждого параметрического термина потребуется оболочка (lambda (x) ...). Что еще более важно, что код будет также сложнее читать (в дополнение к необходимости изучения специального нового макроса для комбинации предикатов).

ИМО может иметь смысл писать и/или комбинаторы, если вы переданы в предикаты, и вам нужно передать предикаты кому-то еще ... но не для написания кода, который вы использовали в примере; для этого я бы написать

(remove-if (lambda (x) (or (is-fruit-p x) 
          (is-red-p x) 
          (grows-on-trees-p x))) 
      list-of-objects) 

Меньше писать, меньше читать, ничего лишнего, чтобы узнать, тривиальное параметризовать.

Предположим, например, что вы хотите список фруктов того же цвета, что и у вас (в mine) и с таким же весом или, возможно, более тяжелым ...

(remove-if-not (lambda (x) (and (is-fruit-p x) 
           (eq (color x) (color mine)) 
           (>= (weight x) (weight mine)))) 
       objects) 
+0

Я сомневаюсь что-то вроде '(remove-if (либо is-red-p is-fruit-p) list)' трудно прочитать. Кроме того, я не против использовать код, похожий на один из ваших примеров, просто у меня есть biggish 'loop', где мне нужно использовать' remove-id' с 'is-ap', с' is-bp' и с обоими 'is-ap' _and_' is-bp', и это дало мне представление о том, что я мог бы использовать некоторое сокращение. – zzandy

+0

Не могли бы вы подробно рассказать о том, как параметризовать ваш последний пример? – zzandy

3

Подход с использованием первого класса функций:

(defun complement (func) 
    (lambda (x) (not (funcall func x)))) 

(defun conjoin (pred1 pred2) 
    (lambda (x) (and (funcall pred1 x) (funcall pred2 x)))) 

(defun disjoin (pred1 pred2) 
    (lambda (x) (or (funcall pred1 x) (funcall pred2 x)))) 

Из которого вы можете производить

(remove-if (conjoin #'is-fruit-p (conjoin #'is-red-p #'grows-on-trees-p)) list-of-objects) 
4

функции высшего порядка, как disjoin и conjoin доступны в quicklisp устанавливаемым alexandria библиотеки.

CL-USER> (ql:quickload "alexandria") 
... 
CL-USER> (remove-if (alexandria:disjoin #'zerop #'oddp #'minusp) 
        '(0 -1 1 -2 2)) 
=> (2) 
+0

Спасибо, это, наверное, лучший ответ. – zzandy

Смежные вопросы