Я не могу гарантировать, что это идиоматическая Clojure, но это функциональная версия вашего кода на Python, которая должна, по крайней мере, приблизить вас.
(def prog [
[:label :entry]
[:load 0]
[:load 1]
[:add]
[:store 0]])
(defn parse [stats]
(let [
f (fn [[out-stats labels pc] stat]
(if (= :label (first stat))
[out-stats (conj labels [(second stat) pc]) pc]
[(conj out-stats stat) labels (+ 1 pc)]))
init [[] {} 0]
]
(reduce f init stats)))
(println (parse prog))
Так что я думаю, что вы правы, что сгиб - это то, что вы хотите. Все функциональные складки объединяют коллекцию и «сводят» эту коллекцию в единое значение. Однако ничто не говорит о том, что результирующее единственное значение также не может быть коллекцией или, как в данном случае, коллекцией коллекций.
В нашем случае мы будем использовать трехпараметрическую версию Reduce - это позволяет нам указать начальное значение аккумулятора. Мы должны сделать это, потому что мы собираемся отслеживать много состояний, когда мы перебираем коллекцию байт-кодов, а версия с двумя параметрами в значительной степени требует, чтобы ваш аккумулятор был похож на элементы в списке. (ср. (reduce + [1 2 3 4])
)
При работе с функциональной складкой необходимо думать о том, что вы накапливаете, и о том, как каждый элемент в наборе входных данных способствует этому накоплению. Если вы посмотрите на свой код Python, есть три значения, которые можно обновлять при каждом повороте цикла:
- Выходные операторы (
self.code
)
- Отображение метки (
self.labels
)
- Счетчик программ (
pc
)
Во время цикла больше ничего не пишется. Таким образом, наше значение аккумулятора должно хранить эти три значения.
Этот предыдущий бит является наиболее важной частью.
Как только вы это сделаете, все остальное должно быть довольно легко. Нам нужно начальное значение аккумулятора, у которого нет кода, нет отображений меток и ПК, который начинается с 0. На каждой итерации мы будем обновлять аккумулятор одним из двух способов:
- Добавить новое отображение метки
- Добавить новый вывод программы и увеличить счетчик программы
А теперь вывод:
[[[:load 0] [:load 1] [:add] [:store 0]]
{:entry 0}
4]
Это 3-элементный вектор. Первым элементом является программа. Второй элемент - это отображение меток. Третий элемент - это следующее значение ПК. Теперь вы можете изменить синтаксический анализ, чтобы получить только два значения; это не лишено смысла. Есть причины, по которым вы, возможно, не захотите этого делать, но это больше проблема дизайна API, чем что-либо еще. Я оставлю это как упражнение читателю.
Я должен также упомянуть, что изначально я пропустил блок let и просто указал именованные значения. Я решил вытащить их, чтобы улучшить читаемость. Опять же, я не знаю, что более идиоматично. Это может быть больше соглашения для каждого проекта.
Наконец, я не знаю, действительно ли в сообществе Clojure возникли монады, но вы также можете создать монаду для разбора байт-кода и определить операции «add-Statement» и «add-label» как значения. в этой монаде. Это значительно увеличило бы сложность настройки, но упростило бы фактический код синтаксического анализа. Фактически, это позволит вашему коду анализа выглядеть довольно процедурно, что может быть, а может и не быть хорошей вещью. (не волнуйтесь, он по-прежнему функционален и не имеет побочных эффектов; монады просто позволяют скрыть подключение.) Если ваш пример Python довольно типичен для данных, которые вам нужно обрабатывать, то монады почти наверняка не требуют дополнительных затрат. С другой стороны, если вам действительно нужно управлять гораздо большим состоянием, чем указано в вашем образце, то монады могут помочь вам сохранить здравый смысл.