linkfly (linkfly) wrote,
linkfly
linkfly

Устройство ASDF. Подсистема определения. UNION-OF-DEPENDENCIES.

(сказаное ниже относится к версии 2.019.2)

Это 5-ая статья цикла.
Первая статья.
Архитектура ASDF.
Предыдущая статья.

Определение (сигнатура):

    (defun* union-of-dependencies (&rest deps) ...)

    Функция применяется в parse-component-form для того, чтобы установить у компонента слот in-order-to:

   (setf (component-in-order-to ret)
      (union-of-dependencies
         in-order-to
         `((compile-op (compile-op ,@depends-on))
            (load-op (load-op ,@depends-on))
)
)
)


    ... и слот do-first:

   (setf (component-do-first ret)
            (union-of-dependencies
               do-first
               `((compile-op (load-op ,@depends-on)))
)
)


    ... эти слоты будут использоваться в ф-ии do-traverse при загрузке систем, а конкретней в подсистеме: "планирование операций". Значение слота do-first необходимо для планирования операций с зависимостями компонента. А значение слота in-order-to - для переопределения порядка операций.

    Поначалу определение union-of-dependencies кажется весьма сложным. Во-первых необходимо знать, что она возвратит свой второй аргумент если опция :in-order-to (и соотв. :do-first) не была установлена. Кроме того, если depends-on равна nil - то она возвратит nil. Применяется опция :in-order-to крайне редко (а опция :do-first - вообще не документирована), была замечена в системе weblocks-prevalence (см. weblocks-prevalence.asd в https://bitbucket.org/redline6561/weblocks-dev/src). Там она была установлена в:

    :in-ordered-to ((compile-op (prepare-prevalence-op :weblocks-prevalence))
                               (load-op (prepare-prevalence-op :weblocks-prevalence)))


    Пример возвращаемого значения. Допустим функция была вызвана со следующими аргументами:

   (union-of-dependencies
      '((compile-op (prepare-prevalence-op :weblocks-prevalence))
        (load-op (prepare-prevalence-op :weblocks-prevalence)))
      '((compile-op (compile-op "file1" "file2"))
        (load-op (load-op "file1" "file2"))))

    Результат будет таким:

    ((LOAD-OP (LOAD-OP "file2" "file1")
          (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
     (COMPILE-OP (COMPILE-OP "file2" "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))


                Логика работы функции. UNION-OF-DEPENDENCIES.

Определение функции:
    
(defun* union-of-dependencies (&rest deps)
   (let ((new-tree nil))
     (dolist (dep deps)
       (dolist (op-tree dep)
         (dolist (op  (cdr op-tree))
           (dolist (c (cdr op))
             (setf new-tree
                   (maybe-add-tree new-tree (car op-tree) (car op) c)
)
)
)
)
)

      new-tree
)
)


1. На первый взгляд это кажется "мозго-взрывательным". Не будем паниковать, а просто возьмём её код и немного изменим для того чтобы представить себе как она работает (благо её вполне можно отлаживать отдельно):

(let ((deps '(((compile-op (prepare-prevalence-op :weblocks-prevalence))
               (load-op (prepare-prevalence-op :weblocks-prevalence))
)

              ((compile-op (compile-op "file1" "file2"))
               (load-op (load-op "file1" "file2"))
)
)
)
)

  (let ((new-tree nil))
    (dolist (dep deps)
      (print dep)
      (dolist (op-tree dep)
        (print op-tree)
        (dolist (op  (cdr op-tree))
          (print op)
          (dolist (c (cdr op))
            (print c)
            (setf new-tree
                  (maybe-add-tree new-tree (car op-tree) (car op) c)
)
)
)
)
)

    new-tree
)
)


Первые два выводимых элемента:

((COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
 (LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))

(COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))

    Вроде ничего сложного. Сначала итерация по аргументам, затем по каждому списку в аргументе. Если назначить уровни вложенности элементам в deps начиная с номера 1, то можно сказать, что сейчас мы вывели элементы с уровнями вложенности 1 и 2.

Дальше стратегия несколько изменяется - итерация происходит уже не по списку а по хвосту списка:

(dolist (op  (cdr op-tree))
  (print op)
  (dolist (c (cdr op))
    (print c)
    ...
)
)


И в итоге мы добираемся до элементов с уровнями вложенности 3 и 4:

(PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)

:WEBLOCKS-PREVALENCE

А здесь мы как-то модифицируем текущее дерево (которое вначале пусто) используя элементы найденные на уровнях 2, 3 и 4:

(setf new-tree
        (maybe-add-tree new-tree (car op-tree) (car op) c)
)


Теперь уберём расставленные нами ранее вызовы print и проследим с какими именно аргументами вызывается функция maybe-add-tree, изменяющая new-tree. Для этого включим её трассировку:

   (trace maybe-add-tree)

В стандартный поток вывода, будет выведены следующие результаты трассировки:

  0: (MAYBE-ADD-TREE NIL COMPILE-OP PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)
  0: MAYBE-ADD-TREE returned
       ((COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
  0: (MAYBE-ADD-TREE
      ((COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))) LOAD-OP
      PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)
  0: MAYBE-ADD-TREE returned
       ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
        (COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
  0: (MAYBE-ADD-TREE
      ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
       (COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
      COMPILE-OP COMPILE-OP "file1")
  0: MAYBE-ADD-TREE returned
       ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
        (COMPILE-OP (COMPILE-OP "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
  0: (MAYBE-ADD-TREE
      ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
       (COMPILE-OP (COMPILE-OP "file1")
        (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
      COMPILE-OP COMPILE-OP "file2")
  0: MAYBE-ADD-TREE returned
       ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
        (COMPILE-OP (COMPILE-OP "file2" "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
  0: (MAYBE-ADD-TREE
      ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
       (COMPILE-OP (COMPILE-OP "file2" "file1")
        (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
      LOAD-OP LOAD-OP "file1")
  0: MAYBE-ADD-TREE returned
       ((LOAD-OP (LOAD-OP "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
        (COMPILE-OP (COMPILE-OP "file2" "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
  0: (MAYBE-ADD-TREE
      ((LOAD-OP (LOAD-OP "file1") (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
       (COMPILE-OP (COMPILE-OP "file2" "file1")
        (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
      LOAD-OP LOAD-OP "file2")
  0: MAYBE-ADD-TREE returned
       ((LOAD-OP (LOAD-OP "file2" "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
        (COMPILE-OP (COMPILE-OP "file2" "file1")
         (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))

Как видно, всё гораздо проще чем казалось вначале - аргументы (car op-tree) и (car op) формируют "путь" по которому надо сохранить/добавить элемент c. Например, вызов формы со следующими параметрами:

    (maybe-add-tree
       '((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
         (COMPILE-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
       'COMPILE-OP
       'COMPILE-OP
       "file1")

вернёт:

    ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
     (COMPILE-OP (COMPILE-OP "file1")
                            (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))

    ... то есть по адресу compile-op/compile-op/ будет сохранён элемент "file1". Если такого адреса ещё нет,
он формируется. Если адрес есть, элемент добавляется к тому, что уже по этому адресу находится, например:

    (maybe-add-tree
       '((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
         (COMPILE-OP (COMPILE-OP "file1")
                                (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))
       'COMPILE-OP
       'COMPILE-OP
       "file2")

вернёт:

    ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
     (COMPILE-OP (COMPILE-OP "file2" "file1")
                          (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))

    Напрашивается аналогия с общеизвестным принципом хранения файлов в директориях, только здесь строго задан их уровень вложенности. Да и кстати, по факту меняется порядок зависимых элементов: "file1" и "file2" как видно поменялись местами. Почему разработчики сочли, что вначале нужно выполнять операции с последним зависимым компонентом не совсем ясно. Но этот факт, на всякий случай, стоит иметь в виду.

2. Функция maybe-add-tree довольно тривиальна, как было сказано выше: её задача в том, чтобы обеспечить
наличие элемента по указанному адресу:

(defun* maybe-add-tree (tree op1 op2 c)
  "Add the node C at /OP1/OP2 in TREE, unless it's there already.
Returns the new tree (which probably shares structure with the old one)"

  (let ((first-op-tree (assoc op1 tree)))
    (if first-op-tree
        (progn
          (aif (assoc op2 (cdr first-op-tree))
               (if (find c (cdr it))
                   nil
                   (setf (cdr it) (cons c (cdr it)))
)

               (setf (cdr first-op-tree)
                     (acons op2 (list c) (cdr first-op-tree))
)
)

          tree
)

        (acons op1 (list (list op2 c)) tree)
)
)
)


Логика работы достаточно прозрачна:
 - ищём список с головой = OP1
 - если не находим, то создаём свой путь /OP1/OP2 и кладём по этому пути элемент С.
 - если находим, то далее ищем второй элемент пути OP2
 - если не нашли, создаём список из OP2 и C и сохраняем его по адресу начинающемуся с OP1
 - если нашли, то добавляем элемент только если не получилось его найти с помощью функции find.

--------------------
Примечание к ф-ии maybe-add-tree (для ASDF 2.0.18.3).
    Однако не понятно, если содержимое (в перечеслении зависимостей) состоит из строк, то почему функция find вызывается без опции :test #'equal - это приводит к тому, что C всё-таки добавляется повторно, если мы имеем дело со строковыми элементами. То есть, выполнение формы:

(maybe-add-tree
 '((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
   (COMPILE-OP (COMPILE-OP "file1")
                              (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)
)
)

 'COMPILE-OP
 'COMPILE-OP
 "file1"
)


    ... приведёт к такому, вряд ли ожидаемому разработчиками, результату:

    ((LOAD-OP (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE))
     (COMPILE-OP (COMPILE-OP "file1" "file1")
                                (PREPARE-PREVALENCE-OP :WEBLOCKS-PREVALENCE)))

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


Исправлено в версии 2.0.18.4

-------------------
UPDATED: в версии 2.0.18.4 добавили опцию :test #'equal в вызов find в ф-ии maybe-add-tree.
-------------------
Продолжение следует ...












Tags: asdf, lisp, programming, лисп, программирование
Subscribe

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments