2010-02-19 5 views
36

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

+0

Homoiconicity является признаком, что язык может или не может иметь, но это не то, что вы можете добавить (без создания нового языка). Это не сложно сделать в Java - Java просто не гомоиконный (он гетероиконический). Это то же самое, что и с автомобилями - вы можете модифицировать subaru impreza, чтобы он стал грузовиком-монстром; но это не грузовик-монстр, и полученный автомобиль больше не субару. – MatthewRock

ответ

22

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

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


Я хотел бы обратиться к второй части вашего вопроса - как это, что языки программирования наиболее считаются не быть homoiconic? Мне придется прикоснуться к семантике Лиспа в процессе, но поскольку Нильс уже предоставил ссылки на хорошие источники информации о термине «homoiconic», и Артур описал цикл компиляции read-> macro expand-> как найденный в Clojure, я буду строить на этом в дальнейшем. Для того, чтобы начать вещи, позвольте мне процитировать отрывок из Алана Кея (извлеченный из статьи Википедии, которая также ссылки на оригинальный источник):

[...] Интерактивное LISP [...] и [ПРОФ. ..] оба являются «homoiconic» тем, что их внутреннее и внешнее представления по существу одинаковы.

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

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

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

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

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

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

1

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

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

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

Homoiconicity объясняется в целом, и вы также можете найти инициированные источники. Как объясняется, используя пример Lisp, это не так далеко от Clojure.

+0

Итак, если я попробую пример Википедии в Java. 1. Java-программа имеет класс с полем String, который сам по себе является программой java. (правильно экранированный) 2. Манипулируйте его с помощью Regex, чтобы заменить некоторую подстроку, такую ​​как COS на SIN 3. Сделайте вызов внешней программе (java-компилятор), которая будет выполнять вышеупомянутую модифицированную строку, которая записывается в файл. 4. Результатом будет выполнение программы, которая была строкой в ​​родительском классе. 5. Вы также можете взять поле Строка в качестве входного сигнала в основном методе родительского класса. Значит ли это, что Java тоже гомоикон? –

+0

Несомненно, представление байт-кода файлов классов в Java не такое же, как синтаксис для Java-кода ... –

+0

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

6

Когда я изучал Lisp идея homoiconicity имеет смысл, когда я узнал, что Лисп «составлена» в два этапа, чтение и составление и код представляется с той же структурой данных для обоих из них:

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

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

+1

На самом деле это вводит в заблуждение, подразумевая, что расширение макросов происходит внутри читателя.Обратите внимание, как '(eval '(когда true (println: foo) (println: bar)))' работает так, как ожидалось, хотя читатель явно не может калечить структуру цитированных списков '' (когда true (println: foo) (println : бар)) '. Стандартная номенклатура различает время чтения (поток символов -> преобразование структур данных) и время макроразложения (преобразование структуры данных). Факт остается фактом: макрораспределение происходит до компиляции, хотя это ключевой момент для понимания макросов. –

+0

спасибо, что указал. Я удалил комментарий о том, что это часть читателя. –

+0

Читатель не переводит символы в s-выражения. Читатель переводит s-выражения в данные. –

4

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

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

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

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

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

  • ЧТЕНИЯ переводит внешние S-выражение в данные
  • EVAL принимает лисповские формы в виде лисповских данных и оценивает их
  • ПЕЧАТИ переводит лисповские данные во внешние з-выражения

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

3

Вот короткая программа для символической дифференциации. Это пример 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)))) 
+0

Не могли бы вы объяснить это чуть больше? – Ali

+0

Я немного занят на данный момент, но он пришел из этого: http://www.learningclojure.com/2010/02/clojure-dojo-4-symbolic-differentiation.html, который является частью четвертой серии «dojos» для начинающих clojure. Если вы не получите бит clojure, то чтение более ранних может помочь (сначала выясните, что такое додзе). Если математика является проблемой, тогда прочитайте учебную математическую книгу о дифференциации, которая доходит до правила продукта. –

0

Как Rainer Joswig указывает, есть веские основания сомневаться в полезности идеи homoiconicity, и является ли Лиспах фактически Homoiconic.

Исходное определение центров homoiconiticy на сходство между внутренним и внешним представлениями языка.Канонический пример - Lisp с его s-выражениями.

Существует (по крайней мере) два объекта с этим определением и выбором примера.

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

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

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

Я писал об этом более подробно в статье под названием Don't say “Homoiconic”

Смежные вопросы