Что происходит, когда флет находится внутри рекурсивной функции? - PullRequest
1 голос
/ 14 июля 2020

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

(defun eps-closure (states transition-rule)
  (flet ((trace-eps-onestep (states transition-rule)
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (let ((next (trace-eps-onestep states transition-rule)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
          next))))

Ответы [ 3 ]

5 голосов
/ 14 июля 2020

Для меня это нормально. Это типичная локальная лексическая функция.

кажется, что каждый раз во время рекурсии вспомогательная функция определяется независимо

Это не имеет значения в скомпилированном коде, и функция в любом случае не переопределил . Функциональный объект не создается, и не происходит присвоения символу. Компилятор может даже решить встроить его.

Интерпретируемый код (с использованием интерпретатора s-выражений) может иметь некоторые накладные расходы на выполнение оператора FLET на каждой итерации, но для скомпилированного кода это не имеет большого значения, поскольку компиляция обычно выполняется один раз заранее .

Чтобы сделать код более модульным с помощью функций, есть способы:

  • точно так же, как в в вашем примере определите локальную функцию. Я бы даже сохранил параметры, даже если их можно не указывать, если они находятся в лексической области видимости. При желании объявить встроенную локальную функцию. Сохранение параметров упрощает рефакторинг кода и служит для документирования аргументов функции, делая их явными.

  • определить ее как глобальную функцию и передать ей все аргументы в вызове позже. Часто эти функции называются вспомогательными функциями, например %trace-eps-onestep (с использованием % в качестве префикса для глобальных функций, которые не предполагается вызывать напрямую) или аналогичных. Иногда это предпочтительнее, так как это упрощает независимое отслеживание вспомогательной функции. Но некоторые реализации могут также отслеживать локальные функции по отдельности. делает форму DEFUN отличной от верхнего уровня и не позволяет компилятору файла переносимо распознавать ее как определение глобальной функции во время компиляции файла.

    Пример использования Компилятор SBCL

    * (defun add42 (n)
        (flet ((do-it (n)
                 (+ n 42)))
          (let ((x (do-it n)))
            (if (> x 100)
                :i-dont-do-it
                x))))
    
    * (disassemble #'add42)
    ; disassembly for ADD42
    ; Size: 68 bytes. Origin: #x22661D81                          ; ADD42
    ; 81:       498B4510         MOV RAX, [R13+16]                ; thread.binding-stack-pointer
    ; 85:       488945F8         MOV [RBP-8], RAX
    ; 89:       488B55F0         MOV RDX, [RBP-16]
    ; 8D:       BF54000000       MOV EDI, 84
    ; 92:       FF1425C000B021   CALL QWORD PTR [#x21B000C0]      ; GENERIC-+
    ; 99:       488BC2           MOV RAX, RDX
    ; 9C:       488945E8         MOV [RBP-24], RAX
    ; A0:       BFC8000000       MOV EDI, 200
    ; A5:       FF1425E800B021   CALL QWORD PTR [#x21B000E8]      ; GENERIC->
    ; AC:       488B45E8         MOV RAX, [RBP-24]
    ; B0:       488BD0           MOV RDX, RAX
    ; B3:       41BB0FC04E20     MOV R11D, #x204EC00F             ; :I-DONT-DO-IT
    ; B9:       490F4FD3         CMOVNLE RDX, R11
    ; BD:       488BE5           MOV RSP, RBP
    ; C0:       F8               CLC
    ; C1:       5D               POP RBP
    ; C2:       C3               RET
    ; C3:       CC10             INT3 16                          ; Invalid argument count trap
    NIL
    

    Как видно из сгенерированного машинного кода x86-64, не происходит никаких переопределений .

1 голос
/ 15 июля 2020

Достаточно очевидный способ сделать что-то вроде этого - определить хвостовую рекурсивную l oop внутри любых локально определенных функций, которые вы хотите:

(defun eps-closure (initial-states transition-rule)
  (flet ((trace-eps-onestep (states)
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (labels ((eps-closure-loop (states)
               (let ((next (trace-eps-onestep states)))
                 (if (set-difference next states)
                     (eps-closure-loop states)
                   next))))
      (eps-closure-loop initial-states))))

Теперь совершенно ясно, что существует только одно определение trace-eps-onestep. Обратите внимание, что я также воспользовался возможностью удалить второй аргумент из всех вызовов, поскольку это всегда один и тот же объект, и я переименовал аргументы, чтобы сделать, надеюсь, больше смысла.

Мне нравится этот вид трюка big-global-definition-with-a-bunch-of-local-functions-внутри этого трюка, поскольку это означает, что из чтения кода совершенно ясно, что они являются вспомогательными функциями только для использования глобальной функцией.

В этом конкретном случае trace-eps-onestep вызывается ровно из одного места и на самом деле вообще не имеет причин для существования. Хороший компилятор, вероятно, полностью его оптимизирует, но я думаю, что следующий код в любом случае более понятен:

(defun eps-closure (initial-states transition-rule)
  (labels ((eps-closure-loop (states)
             (let ((next (remove-duplicates
                          (flatten
                           (append
                            states
                            (mapcar
                             (lambda (state)
                               (transition-state state :eps transition-rule))
                             states))))))
               (if (set-difference next states)
                   (eps-closure-loop next)
                 next))))
    (eps-closure-loop initial-states)))

Наконец, такая хвостовая рекурсивная локальная функция не очень естественна в CL ( хотя я очень часто программирую вот так!): мне кажется, что-то вроде следующего более понятно:

(defun eps-closure (initial-states transition-rule)
  (loop for states = initial-states then next
        for next = (remove-duplicates
                    (flatten
                     (append
                      states
                      (mapcar
                       (lambda (state)
                         (transition-state state :eps transition-rule))
                       states))))
        if (null (set-difference next states))
        return next))

Я не тестировал ни одну из этих функций (все они компилируются, но определения отсутствуют).

0 голосов
/ 14 июля 2020

Вы можете поместить FLET вокруг DEFUN, чтобы он не попал в глобальную область.

(flet ((trace-eps-onestep (states transition-rule)
          (remove-duplicates
           (flatten
            (append
             states
             (mapcar
              (lambda (state) (transition-state state :eps transition-rule))
              states))))))
  (defun eps-closure (states transition-rule)
    (let ((next (trace-eps-onestep states transition-rule)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
        next))))

Или вы можете определить его локально, как в исходном коде. Нет необходимости передавать ему параметры, поскольку локальная функция имеет доступ ко всем локальным переменным. В этом случае разумно переопределять его при каждой рекурсии, потому что переменные разные.

(defun eps-closure (states transition-rule)
  (flet ((trace-eps-onestep ()
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (let ((next (trace-eps-onestep)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
          next))))
...