Гомоиконность - Homoiconicity

В компьютерном программировании , homoiconicity (от греческих слов гомо- означает «то же» и значок означает «представление») является свойством некоторых языков программирования . Язык является гомоиконным, если программа, написанная на нем, может обрабатываться как данные с помощью языка, и, таким образом, внутреннее представление программы может быть выведено, просто читая саму программу. Это свойство часто резюмируют, говоря, что язык рассматривает «код как данные».

В гомоиконическом языке первичным представлением программ также является структура данных в примитивном типе самого языка. Это делает метапрограммирование проще, чем на языке без этого свойства: отражение в языке (изучение сущностей программы во время выполнения ) зависит от единой однородной структуры, и ему не нужно обрабатывать несколько различных структур, которые могли бы появиться в сложном синтаксисе. Гомиконические языки обычно включают полную поддержку синтаксических макросов , что позволяет программисту кратко выражать преобразования программ.

Часто цитируемым примером является Лисп , который был создан для упрощения манипуляций со списками и где структура задается S-выражениями, которые принимают форму вложенных списков, и может управляться другим кодом Лиспа. Другими примерами являются языки программирования Clojure (современный диалект Lisp), Rebol (также его преемник Red ), Refal , Prolog и, совсем недавно, Julia .

История

Первоисточником является документ Macro Instruction Extensions of Compiler Languages , согласно раннему и влиятельному документу TRAC, A Text-Handling Language :

Одна из основных целей разработки заключалась в том, чтобы сценарий ввода TRAC (то, что вводит пользователь) был идентичен тексту, который руководит внутренними действиями процессора TRAC. Другими словами, процедуры TRAC должны храниться в памяти в виде строки символов в точности так, как пользователь вводил их с клавиатуры. Если процедуры TRAC сами развивают новые процедуры, эти новые процедуры также должны быть изложены в том же сценарии. Процессор TRAC в своем действии интерпретирует этот сценарий как свою программу. Другими словами, программа-переводчик TRAC (процессор) эффективно преобразует компьютер в новый компьютер с новым программным языком - языком TRAC. В любое время должна быть возможность отображать программную или процедурную информацию в той же форме, в какой процессор TRAC будет воздействовать на нее во время ее выполнения. Желательно, чтобы внутреннее представление кода символа было идентично или очень похоже на представление внешнего кода. В существующей реализации TRAC внутреннее символьное представление основано на ASCII . Поскольку процедуры и текст TRAC имеют одинаковое представление внутри и вне процессора, термин «гомоиконный» применим от слова «homo», означающего одно и то же, и значка, означающего «представление».

[...]

Следуя предложению Маккалоу, WS , основанному на терминологии Пирса, Макилроя из CS. MD, "Расширения макросов для языков компилятора", Comm. ACM, стр. 214–220; Апрель 1960 г.

Алан Кей использовал и, возможно, популяризировал термин «гомоиконический», используя его в своей докторской диссертации 1969 года:

Заметной группой исключений из всех предыдущих систем являются Интерактивный LISP [...] и TRAC. Оба функционально ориентированы (один список, другая строка), оба разговаривают с пользователем на одном языке, и оба являются «гомоиконными» в том смысле, что их внутреннее и внешнее представления по существу одинаковы. У них обоих есть возможность динамически создавать новые функции, которые затем могут быть доработаны по желанию пользователя. Их единственный большой недостаток в том, что написанные на них программы выглядят как письмо царя Бурнибуриаха к шумеру, написанное вавилонской клиникой! [...]

Использование и преимущества

Одним из преимуществ гомоиконности является то, что расширение языка новыми концепциями обычно становится проще, поскольку данные, представляющие код, могут передаваться между мета- уровнем и базовым уровнем программы. Абстрактное синтаксическое дерево функции может быть составлено и манипулировать ими в виде структуры данных в мета - слое, а затем оценивал . Может быть намного проще понять, как манипулировать кодом, поскольку его легче понять как простые данные (поскольку формат самого языка является форматом данных).

Типичной демонстрацией гомиконности является метакруговой оценщик .

Методы реализации

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

Такие языки, как Lisp и его диалекты, такие как Scheme , Clojure и Racket, используют S-выражения для достижения гомоиконности.

Другие языки, которые считаются гомиконическими, включают:

В Лиспе

Lisp использует S-выражения как внешнее представление данных и кода. S-выражения можно читать с помощью примитивной функции Lisp READ. READвозвращает данные Лиспа: списки, символы , числа, строки. Примитивная функция Lisp EVALиспользует код Lisp, представленный как данные Lisp, вычисляет побочные эффекты и возвращает результат. Результат будет напечатан примитивной функцией PRINT, которая создает внешнее S-выражение из данных Лиспа.

Данные Лиспа, список с использованием различных типов данных: (под) списки, символы, строки и целые числа.

((:name "john" :age 20) (:name "mary" :age 18) (:name "alice" :age 22))

Код на Лиспе. В примере используются списки, символы и числа.

(* (sin 1.1) (cos 2.03))      ; in infix: sin(1.1)*cos(2.03)

Создайте указанное выше выражение с помощью примитивной функции Lisp LISTи установите переменную EXPRESSIONв результат

(setf expression  (list '* (list 'sin 1.1) (list 'cos 2.03)) )  
-> (* (SIN 1.1) (COS 2.03))    ; Lisp returns and prints the result

(third expression)    ; the third element of the expression
-> (COS 2.03)

Измените COSтермин наSIN

(setf (first (third expression)) 'SIN)
; The expression is now (* (SIN 1.1) (SIN 2.03)).

Оцените выражение

(eval expression)
-> 0.7988834

Вывести выражение в строку

(print-to-string expression)
->  "(* (SIN 1.1) (SIN 2.03))"

Прочитать выражение из строки

(read-from-string "(* (SIN 1.1) (SIN 2.03))")
->  (* (SIN 1.1) (SIN 2.03))     ; returns a list of lists, numbers and symbols

В Прологе

1 ?- X is 2*5.
X = 10.

2 ?- L = (X is 2*5), write_canonical(L).
is(_, *(2, 5))
L = (X is 2*5).

3 ?- L = (ten(X):-(X is 2*5)), write_canonical(L).
:-(ten(A), is(A, *(2, 5)))
L = (ten(X):-X is 2*5).

4 ?- L = (ten(X):-(X is 2*5)), assert(L).
L = (ten(X):-X is 2*5).

5 ?- ten(X).
X = 10.

6 ?-

В строке 4 мы создаем новое предложение. Оператор :-разделяет заголовок и тело предложения. С помощью assert/1*мы добавляем его к существующим предложениям (добавляем его в «базу данных»), чтобы мы могли вызвать его позже. На других языках мы бы назвали это «созданием функции во время выполнения». Мы также можем удалить предложения из базы данных с помощью abolish/1, или retract/1.

* Число после имени предложения - это количество аргументов, которые оно может принимать. Это еще называют арностью .

Мы также можем запросить базу данных, чтобы получить тело предложения:

7 ?- clause(ten(X),Y).
Y = (X is 2*5).

8 ?- clause(ten(X),Y), Y = (X is Z).
Y = (X is 2*5),
Z = 2*5.

9 ?- clause(ten(X),Y), call(Y).
X = 10,
Y = (10 is 2*5).

callаналогична evalфункции Лиспа .

В Реболе

Концепция обработки кода как данных, а также манипуляции с ними и их оценка могут быть очень четко продемонстрированы в Rebol . (Rebol, в отличие от Lisp, не требует скобок для разделения выражений).

Ниже приведен пример кода в Rebol (обратите внимание, что >>это приглашение интерпретатора; для удобства чтения были добавлены пробелы между некоторыми элементами):

>> repeat i 3 [ print [ i "hello" ] ]

1 hello
2 hello
3 hello

( repeatна самом деле это встроенная функция в Rebol, а не языковая конструкция или ключевое слово).

Заключая код в квадратные скобки, интерпретатор не оценивает его, а просто рассматривает его как блок, содержащий слова:

[ repeat i 3 [ print [ i "hello" ] ] ]

Этот блок имеет типовой блок! и, кроме того, может быть назначен как значение слова с использованием того, что кажется синтаксисом для присваивания, но фактически понимается интерпретатором как специальный тип ( set-word!) и принимает форму слова, за которым следует двоеточие:

>> block1: [ repeat i 3 [ print [ i "hello" ] ] ] ;; Assign the value of the block to the word `block1`
== [repeat i 3 [print [i "hello"]]]
>> type? block1 ;; Evaluate the type of the word `block1`
== block!

Блок по-прежнему можно интерпретировать с помощью doфункции, предоставленной в Rebol (аналогично evalLisp ).

Можно опросить элементы блока и изменить их значения, тем самым изменив поведение кода, если он должен быть оценен:

>> block1/3 ;; The third element of the block
== 3
>> block1/3: 5 ;; Set the value of the 3rd element to 5
== 5
>> probe block1 ;; Show the changed block
== [repeat i 5 [print [i "hello"]]]
>> do block1 ;; Evaluate the block
1 hello
2 hello
3 hello
4 hello
5 hello

Смотрите также

использованная литература

внешние ссылки