Подтвердить что ты не робот

Homoiconicity, Как это работает?

Может ли кто-нибудь предложить статьи, объясняющие концепцию Homoiconicity, особенно используя Clojure. Почему Clojure является гомоиконным, но его трудно сделать на других языках, таких как Java?

4b9b3361

Ответ 1

Прежде чем переходить к некоторым вещам, я хотел бы добавить еще один ответ, вот еще одна ссылка - часть, связанная с гомоконичностью, довольно короткая, но Rich Hickey делает объяснение! Канал 9 имеет это приятное видео с Ричем Хики и Брайаном Бекманом, говорящим о Clojure. Concurrency, по понятным причинам, основной фокус, но гомоконичность действительно дает свой (короткий) момент времени экрана, в течение которого Rich прекрасно объясняет взаимодействие между read (функция, которая преобразует конкретный синтаксис, записанный программистом на внутреннее представление, построенное из списков и т.д.) и eval. У него есть эта хорошая диаграмма, показывающая, как eval никогда даже не знает, что код, который он оценивает, исходит из read, работающего в текстовом файле... Артур уже объяснил суть этого, но эй, посмотри это в любом случае, это очень приятное видео!


Отказ от ответственности: я буду упоминать Java и Python в качестве примеров ниже следующей горизонтальной полосы. Я хочу пояснить, что следующее - это всего лишь примерный пример того, почему я думаю, что может быть сложно сделать homoiconic, Lisp -строчную макроуровню Java или Python; это просто академическое упражнение, и я не хочу рассматривать вопрос о том, есть ли какая-то причина, чтобы попытаться в первую очередь. Кроме того, я не хочу подразумевать, что синтаксис языка с макросами стиля Lisp должен содержать явные разделители для древовидных структур; Дилан (безразмерный Lisp?), По-видимому, обеспечивает контрпример. Наконец, я использую макросы стиля Lisp, потому что я рассматриваю только макросы стиля Lisp. Например, у языка Forth есть другое средство макросов, которое я действительно не понимаю, за исключением того, что я знаю его, чтобы включить злой классный код. По-видимому, расширения синтаксиса могут быть реализованы несколькими способами. С этим в сторону...


Я хотел бы затронуть вторую часть вашего вопроса - как многие языки программирования считаются не гомохимическими? Я должен затронуть семантику Lisp в процессе, но так как Нильс уже предоставил ссылки на хорошие источники информации о термине "homoiconic", и Артур описал цикл чтения → macro expand → compile как показано в Clojure, я буду строить это в дальнейшем. Чтобы начать все, позвольте мне процитировать отрывок из Алана Кей (извлеченный из статьи Википедии, которая также ссылается на исходный источник):

[...] Интерактивный Lisp [...] и TRAC [...] оба являются "homoiconic" тем, что их внутренние и внешние представления по существу одинаковы.

(Эти [...] биты спрятали много текста, но суть не изменилась.)

Теперь задайте себе вопрос: что такое внутреннее представление Java Java?... Ну, это даже не имеет смысла. Компилятор Java имеет определенное внутреннее представление Java, а именно абстрактное синтаксическое дерево; для построения "гомозвуковой Java" мы должны были бы сделать это представление АСТ первоклассным объектом в Java и разработать синтаксис, который позволит нам напрямую писать АСТ. Это может оказаться довольно сложным.

Python представляет собой пример негомиконического языка, который интересен тем, что в настоящее время он поставляется с набором инструментов для управления АСТ в форме модуля ast. Документы для этого модуля явно указывают, что АСТ Python могут меняться между релизами, что может или не может обескураживать; Тем не менее, я полагаю, что трудолюбивый программист может взять модуль ast, разработать синтаксис (возможно, основан на S-выражении, возможно, на основе XML) для непосредственного описания АСТ Python и построить парсер для этого синтаксиса в обычном Python с использованием ast, тем самым сделав твердый первый шаг на пути к созданию гомоязычного языка с семантикой Python. (По-моему, я натолкнулся на диалект Lisp, компилирующий байт-код Python некоторое время назад... Интересно, может ли это делать что-то подобное на каком-то уровне?)

Даже тогда проблема остается в извлечении конкретных выгод от такого рода гомоконичности. Он рассматривается как выгодное свойство членов семейства языков Lisp, потому что он позволяет нам писать программы, которые пишут дальнейшие программы, среди которых наиболее важные макросы. Теперь , в то время как макросы включены одним способом из-за того, что так легко манипулировать внутренним представлением кода Lisp в Lisp, они также включены не менее важным способом при выполнении Lisp модель: a Lisp - это просто набор форм Lisp; они обрабатываются функцией Lisp eval, которая отвечает за определение значений выражений и вызывает соответствующие побочные эффекты в правильное время; семантика Lisp является в точности семантикой eval. Вопрос о том, как все работает внутренне, чтобы сохранить эту смысловую иллюзию, будучи достаточно быстрым, представляет собой деталь реализации; a Lisp система обязана выставить функции eval программисту и действовать так, как если бы Lisp программы обрабатывались этой функцией.

В современных системах Lisp входит в контракт eval, что он выполняет дополнительную фазу предварительной обработки, в ходе которой макросы расширяются до оценки кода (или компиляции и работы, в зависимости от ситуации). Это конкретное средство не является необходимой частью системы Lisp, но ее легко подключить к этой модели исполнения! Кроме того, я задаюсь вопросом, не является ли это не единственной моделью выполнения, которая делает типы макроформаций Lisp управляемыми, что означало бы, что любой язык, требующий включения макросов Lisp -style, должен был бы использовать аналогичную модель исполнения. Моя интуиция подсказывает мне, что это действительно так.

Конечно, как только язык записывается в нотации, непосредственно параллельной его АСТ, и использует Lisp -подобную модель исполнения с функцией/объектом оценщика, нужно задаться вопросом, не случайно ли это другой диалект Lisp... даже если его синтаксис AST-параллелизма основан на XML. Дрожь

Ответ 2

Когда я изучал Lisp, идея гомокиновости имела смысл, когда я узнал, что Lisp "скомпилирован" в две фазы, чтение и компиляция, и код представлен одной и той же структурой данных для обоих:

  • сначала вы думаете о s-выражении в голове
  • тогда вы вводите s-expression как символы в файле
  • тогда читатель переводит символы в файл в s-выражения. Это не компиляция программы, просто построение структур данных из символов, это часть фазы чтения.
  • тогда читатель смотрит на каждое из выражений и решил, являются ли они макросом, и если это так, выполняется макрос для создания другого s-выражения. поэтому в этот момент мы перешли от s-выражений к символам в s-выражения, а затем из s-выражений в разные s-выражения.
  • эти s-выражения затем скомпилируются в файлы .class, которые могут запускаться jvm, это вторая фаза "компиляции".

Итак, это довольно много s-выражений на всем пути от вашего мозга до файла .class. вы даже пишете s-выражения, которые пишут s-выражения. поэтому вы можете сказать, что "код - это данные" или "код - это данные", потому что это звучит лучше.

Ответ 3

Вся идея "гомоконичности" немного запутана и не очень хорошо вписывается в Lisp. Внутренние и внешние представления не совпадают в Lisp. Внешнее представление основано на символах в файлах. Внутреннее представление основано на данных Lisp (числа, строки, списки, массивы,...) и не является текстовым. Как это так же, как персонажи? Существуют внутренние представления, которые не имеют соответствующих внешних представлений (например, компилировать код, замыкания,...).

Основное различие между Lisp и многими другими языками программирования состоит в том, что Lisp имеет простое представление данных для исходного кода, которое не основано на строках.

Очевидно, что код может быть представлен как строки в текстовых языках программирования. Но в Lisp источник может быть представлен в терминах примитивных структур данных Lisp. Внешнее представление основано на s-выражениях, которые представляют собой простую модель для представления иерархических данных в виде текста. Внутренняя модель представляет собой представление, основанное на списках и т.д.

То, что получает оценщик: внутренние представления. Не от 1 до 1 версий текстового ввода, а проанализировано.

Базовая модель:

  • READ переводит внешние s-выражения в данные
  • EVAL принимает формы Lisp в виде данных Lisp и оценивает их
  • PRINT переводит Lisp данные во внешние s-выражения

Обратите внимание, что READ и PRINT работают для произвольных Lisp данных, которые имеют печатное представление и читатель, а не только для форм Lisp. Формы по определению являются допустимыми выражениями на языке программирования Lisp.

Ответ 4

Здесь короткая программа для символического дифференцирования. Это пример использования LISP собственного кода. Попробуйте перевести его на другой язык, чтобы понять, почему LISP хорош для такого рода вещей.

;; The simplest possible symbolic differentiator

;; Functions to create and unpack additions like (+ 1 2)
(defn make-add [ a b ] (list '+ a b))
(defn addition? [x] (and (=(count x) 3) (= (first x) '+)))
(defn add1   [x] (second x))
(defn add2   [x] (second (rest x)))

;; Similar for multiplications (* 1 2)
(defn make-mul [ a b ] (list '* a b))
(defn multiplication? [x] (and (=(count x) 3) (= (first x) '*)))
(defn mul1   [x] (second x))
(defn mul2   [x] (second (rest x)))

;; Differentiation. 
(defn deriv [exp var]
  (cond (number? exp) 0                                                              ;; d/dx c -> 0
        (symbol? exp) (if (= exp var) 1 0)                                           ;; d/dx x -> 1, d/dx y -> 0
        (addition? exp) (make-add (deriv (add1 exp) var) (deriv (add2 exp) var))     ;; d/dx a+b -> d/dx a + d/dx b
        (multiplication? exp) (make-add (make-mul (deriv (mul1 exp) var) (mul2 exp)) ;; d/dx a*b -> d/dx a * b + a * d/dx b
                                        (make-mul (mul1 exp) (deriv (mul2 exp) var)))
        :else :error))

;;an example of use: create the function x -> x^3 + 2x^2 + 1 and its derivative 
(def poly '(+ (+ (* x (* x x)) (* 2 (* x x))) 1))

(defn poly->fnform [poly] (list 'fn '[x] poly))

(def polyfn  (eval (poly->fnform poly)))
(def dpolyfn (eval (poly->fnform (deriv poly 'x))))

Ответ 5

Это кажется почти очевидным, но первые источники могут быть:

http://en.wikipedia.org/wiki/Homoiconicity

http://c2.com/cgi/wiki?DefinitionOfHomoiconic

Гомоконичность объясняется в целом, и вы также можете найти исходные источники. Как объясняется с помощью примера Lisp, он не так далеко от Clojure.