Аналог точечных пар для сопоставления с образцом в Clojure - PullRequest
2 голосов
/ 17 февраля 2012

Схема (и CL) имеет точечные пары, где оба элемента ячейки cons указаны явно (например, (1 . 2)), а не неявно (например, (1 2), который читается как (1 . (2 . nil))).

Я натолкнулся на эту головоломку , где точечные пары используются в сопоставлении с образцом для захвата хвоста списка в сопоставляемом объекте, например ::10000

(pmatch '(foo . (? pvar)) '(foo bar baz))
;;      => ((pvar bar baz))

Здесь '(foo . (? pvar)) - шаблон, а '(foo bar baz) - объект, сопоставленный с шаблоном. foo в шаблоне - это литерал, тогда как (? pvar) - это переменная шаблона, которая соответствует (bar baz) и связывает символ pvar с этим соответствием. Функция pmatch возвращает список ассоциаций переменных шаблона и сопоставленных значений.

Если бы шаблон был '(foo (? pvar)), совпадение было бы неудачным, потому что baz не соответствовало бы ничему в шаблоне.

Я реализовал головоломку в Clojure, и я пропускаю все тестовые случаи JRM, кроме точки с точечной парой. Я пытаюсь выяснить, как, возможно, поддерживать шаблон с точечной парой.

Вот мое текущее решение:

(defn pattern-variable? [pv]
  (when (seq? pv)
    (let [[qmark var] pv]
     (and (= (count pv) 2)
          (= qmark '?)
          (or (symbol? var)
              (keyword? var)))))

(defn pattern-variable [pv]
  (second pv))

(defn pmatch
  ([pat obj] (pmatch pat obj {}))
  ([pat obj binds]
     (cond (not (coll? pat))
           (when (= pat obj) binds)
           (pattern-variable? pat)
           (assoc binds (pattern-variable pat) obj)
           (seq? pat) (let [[pat-f & pat-r] pat]
                      (when (seq? obj)
                        (when-let [binds (pmatch pat-f (first obj) binds)]
                          (pmatch pat-r (next obj) binds))))
           :else nil)))

Так, как я могу поддерживать шаблоны, которые соответствуют остальной части объекта в Clojure без точечных пар?

1 Ответ

6 голосов
/ 17 февраля 2012

(Правка: добавлено немного более длинное, но значительно более четкое совпадение импл + демо. Оригинал остается ниже горизонтального правила.)

Одним из решений было бы введение другой нотациидля обозначения переменной, которая должна быть сопоставлена ​​с хвостом последовательности, или «переменная после точки».Другим вариантом было бы зарезервировать & в качестве специального символа в шаблонах с требованием, чтобы за ним следовала только одна переменная шаблона для сопоставления с остальной частью выражения / объекта, которая должна быть последовательностью.Я расскажу о первом подходе ниже.

Здесь я позволил себе сменить нотацию так, чтобы ~foo - это регулярное вхождение переменной foo, а ~@foo - вхождение в хвост.(Можно разрешить сопоставление ~@ с подпоследовательностями, возможно, сопоставить минимальный начальный фрагмент последовательности, если таковой имеется, так, чтобы остаток можно было сопоставить с остальной частью шаблона; я просто скажу, что это выходит за рамкиэтот ответ, однако.; -))

Обратите внимание, что на самом деле это разные вхождения одной и той же переменной, т. е. существует только один тип переменной, поскольку не делается различий между привязками, возникающими из ~ -occurrence и bindings, возникающие из ~@ -occururrence.

Также обратите внимание, что примеры в посте, на который вы ссылались, не проверяют попытки повторно связать ранее связанную переменную (например, try (pmatch '(~x ~x) '(foo bar)), (pmatch '((? x) (? x)) '(foo bar)) в оригинальном синтаксисе).Приведенный ниже код возвращает nil в таких случаях, как и в случае неудачи сопоставления по другим причинам.

Сначала демонстрация:

user> (pmatch '(foo ~pvar1 ~pvar2 bar) '(foo 33 (xyzzy false) bar))
{pvar2 (xyzzy false), pvar1 33}
user> (pmatch '(~av ~@sv) '(foo bar baz))
{sv (bar baz), av foo}
user> (pmatch '(foo ~pvar1 ~pvar2 bar) '(foo 33 false bar))
{pvar2 false, pvar1 33}
user> (pmatch '(foo ~pvar bar) '(quux 33 bar))
nil
user> (pmatch '(a ~var1 (nested (c ~var2))) '(a b (nested (c d))))
{var2 d, var1 b}
user> (pmatch '(a b c) '(a b c))
{}
user> (pmatch '(foo ~pvar1 ~pvar2 bar) '(foo 33 (xyzzy false) bar))
{pvar2 (xyzzy false), pvar1 33}
user> (pmatch '(foo ~@pvar) '(foo bar baz))
{pvar (bar baz)}
user> (pmatch '(~? quux) '(foo quux))
{? foo}
user> (pmatch '~? '(foo quux))
{? (foo quux)}
user> (pmatch '(? ? ?) '(foo quux))
nil

Вот совпадение:

(defn var-type [pat]
  (when (seq? pat)
    (condp = (first pat)
      'clojure.core/unquote :atomic
      'clojure.core/unquote-splicing :sequential
      nil)))

(defn var-name [v]
  (when (var-type v)
    (second v)))

(defmulti pmatch*
  (fn [pat expr bs]
    (cond
      (= :atomic (var-type pat))        :atom
      (= :sequential (var-type pat))    nil
      (and (seq? pat) (seq? expr))      :walk
      (not (or (seq? pat) (seq? expr))) :exact
      :else                             nil)))

(defmethod pmatch* :exact [pat expr bs]
  (when (= pat expr) bs))

(defmethod pmatch* :atom [v expr bs]
  (if-let [[_ x] (find bs (var-name v))]
    (when (= x expr) bs)
    (assoc bs (var-name v) expr)))

(defmethod pmatch* :walk [pat expr bs]
  (if-let [[p] pat]
    (if (= :sequential (var-type p))
      (when (and (seq? expr) (not (next pat)))
        (if-let [[_ xs] (find bs (var-name p))]
          (when (= xs expr) bs)
          (assoc bs (var-name p) expr)))
      (when-let [[x] expr]
        (when-let [m (pmatch* p x bs)]
          (pmatch* (next pat) (next expr) m))))))

(defmethod pmatch* nil [& _] nil)

(defn pmatch
  ([pat expr] (pmatch pat expr {}))
  ([pat expr bs] (pmatch* pat expr bs)))

А вот оригинальная монолитная версия:

(defn pmatch
  ([pat expr] (pmatch pat expr {}))
  ([pat expr bs]
     (letfn [(atom-var? [pat]
               (and (seq? pat) (= 'clojure.core/unquote (first pat))))
             (seq-var? [pat]
               (and (seq? pat) (= 'clojure.core/unquote-splicing
                                  (first pat))))
             (v [var] (second var))
             (matcha [a e bs]
               (if-let [[_ x] (find bs (v a))]
                 (and (or (= x e) nil) bs)
                 (assoc bs (v a) e)))
             (matchs [s e bs]
               (when (seq? e)
                 (if-let [[_ xs] (find bs (v s))]
                   (or (= xs e) nil)
                   (assoc bs (v s) e))))]
       (when bs
         (cond
           (atom-var? pat)
           (matcha pat expr bs)

           (seq-var? pat)
           (matchs pat expr bs)

           (and (seq? pat) (seq? expr))
           (if-let [[p] pat]
             (if (seq-var? p)
               (matchs p expr bs)
               (when-let [[x] expr]
                 (when-let [m (pmatch p x bs)]
                   (recur (next pat) (next expr) m))))
             (when-not (first expr)
               bs))

           (not (or (seq? pat) (seq? expr)))
           (when (= pat expr)
             bs)

           :else nil)))))
...