Гомоконичность
В компьютерном программировании гомоиконичность ( от греческих слов homo — «тот же» и icon — «представление») является свойством некоторых языков программирования . Язык является гомоиконичным , если написанной на нем программой можно манипулировать как данными, используя этот язык. [1] Таким образом, внутреннее представление программы можно получить, просто прочитав саму программу. Это свойство часто резюмируют, говоря, что язык рассматривает код как данные .
В гомоиконном языке первичным представлением программ является также структура данных в примитивном типе самого языка. [1] Это делает метапрограммирование проще, чем в языке без этого свойства: отражение в языке (исследование сущностей программы во время выполнения ) зависит от единой однородной структуры, и ему не приходится обрабатывать несколько разных структур, которые могли бы появиться в сложном синтаксисе. Гомоиконические языки обычно включают полную поддержку синтаксических макросов , что позволяет программисту кратко выражать преобразования программ.
Часто цитируемым примером является Lisp , который был создан для облегчения манипулирования списками и где структура задается S-выражениями , которые принимают форму вложенных списков и которыми можно манипулировать с помощью другого кода Lisp. [2] Другими примерами являются языки программирования Clojure (современный диалект Lisp), Rebol (также его преемник Red ), Refal , Prolog и, возможно, Julia см. в разделе «Методы реализации» ( подробнее ).
История [ править ]
Термин впервые появился в связи с языком программирования TRAC , разработанным Кэлвином Мурсом : [3]
Одна из основных целей разработки заключалась в том, чтобы входной сценарий TRAC (то, что вводится пользователем) был идентичен тексту, который управляет внутренними действиями процессора TRAC. Другими словами, процедуры TRAC должны храниться в памяти в виде строки символов точно в том виде, в котором пользователь набирает их с клавиатуры. Если процедуры ПРОФ сами по себе развивают новые процедуры, эти новые процедуры также должны быть указаны в том же сценарии. Процессор TRAC в своих действиях интерпретирует этот скрипт как свою программу. Другими словами, программа-переводчик TRAC (процессор) эффективно преобразует компьютер в новый компьютер с новым программным языком — языком TRAC. В любой момент должна быть возможность отображать программу или процедурную информацию в той же форме, в которой процессор TRAC будет воздействовать на нее во время ее выполнения. Желательно, чтобы внутреннее представление кода символа было идентично или очень похоже на представление внешнего кода. В настоящей реализации TRAC внутреннее представление символов основано на ASCII . Поскольку процедуры и текст TRAC имеют одинаковое представление внутри и снаружи процессора, применим термин гомоиконный, от слова homo, означающего то же самое, и значка, означающего представление.
Последнее предложение выше снабжено сноской 4, которая указывает на происхождение этого термина: [а]
Следуя предложению Маккалоу WS, основанному на терминологии Пирса, CS.
Исследователями, замешанными в этой цитате, могут быть нейрофизиолог и кибернетик Уоррен Стерджис Маккалок (обратите внимание на разницу в фамилии из заметки) и философ, логик и математик Чарльз Сандерс Пирс . [5] Пирс действительно использовал термин «икона» в своей «Семиотической теории». По Пирсу, в общении существует три вида знаков: значок, указатель и символ. Значок — это самое простое представление: значок физически похож на то, что он обозначает.
Алан Кей использовал и, возможно, популяризировал термин «гомоиконический», используя его в своей докторской диссертации 1969 года: [6]
Заметной группой исключений из всех предыдущих систем являются Interactive LISP [...] и TRAC. Оба функционально ориентированы (один список, другая строка), оба общаются с пользователем на одном языке и оба являются «гомоиконическими» в том смысле, что их внутреннее и внешнее представление по существу одинаковы. Они оба имеют возможность динамически создавать новые функции, которые затем могут быть доработаны по желанию пользователей. Их единственный большой недостаток состоит в том, что программы, написанные в них, похожи на письмо царя Бурнибуриаха шумерам, написанное вавилонской клинописью! [...]
Использование и преимущества [ править ]
Одним из преимуществ гомоиконичности является то, что расширение языка новыми концепциями обычно становится проще, поскольку данные, представляющие код, могут передаваться между мета- и базовым уровнем программы. Абстрактное синтаксическое дерево функции может быть составлено и обработано как структура данных на метауровне, а затем оценено . Гораздо проще понять, как манипулировать кодом, поскольку его легче понять как простые данные (поскольку формат самого языка соответствует формату данных).
Типичным примером гомоиконичности является метациклический оценщик .
Методы реализации [ править ]
Все системы с архитектурой фон Неймана , к которым сегодня относится подавляющее большинство компьютеров общего назначения, можно неявно охарактеризовать как гомоиконные из-за того, как в памяти выполняется необработанный машинный код, а тип данных в памяти — байты. Однако эту функцию также можно абстрагировать до уровня языка программирования.
Такие языки, как Лисп и его диалекты, [7] например Схема , [8] Clojure и Racket используют S-выражения для достижения гомоиконичности и считаются «самыми чистыми» формами гомоиконичности, поскольку эти языки используют одно и то же представление как для данных, так и для кода.
Другие языки предоставляют структуры данных для простого и эффективного управления кодом. Яркие примеры этой более слабой формы гомоиконичности включают Julia , Nim и Elixir .
Языки, которые часто считаются гомоиконическими, включают:
В Лиспе [ править ]
Lisp использует S-выражения в качестве внешнего представления данных и кода. S-выражения можно прочитать с помощью примитивной функции Lisp. READ
. READ
возвращает данные Lisp: списки, символы , числа, строки. Примитивная функция Лиспа EVAL
использует код Lisp, представленный в виде данных Lisp, вычисляет побочные эффекты и возвращает результат. Результат будет напечатан примитивной функцией PRINT
, который создает внешнее S-выражение из данных Lisp.
Данные Lisp — список, использующий различные типы данных: (под)списки, символы, строки и целые числа.
((: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" ] ] ]
Этот блок имеет тип block! и, кроме того, может быть присвоено как значение слова, используя то, что кажется синтаксисом присваивания, но на самом деле понимается интерпретатором как специальный тип ( 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 (аналогично eval
в Лиспе ).
Можно опрашивать элементы блока и изменять их значения, тем самым изменяя поведение кода при его оценке:
>>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
См. также [ править ]
- Когнитивные аспекты нотаций , принципы построения синтаксиса языков программирования.
- Конкатенативный язык программирования
- Языко-ориентированное программирование
- Символическое программирование
- Самомодифицирующийся код
- Метапрограммирование — техника программирования, для которой очень полезна гомоиконичность.
- Реификация (информатика)
Примечания [ править ]
- ^ Предыдущие версии этой страницы Википедии за 2006–2023 годы объединяли несвязанное примечание 5 в приведенном выше документе TRAC, в результате чего возникло неверное утверждение о том, что термин «гомоиконический» возник в статье Дугласа Макилроя по обработке макросов. В этой статье не упоминается никакой терминологии, даже отдаленно похожей; его влияние на работу ПРОФ носит иной характер. Это утверждение с тех пор было повторено некоторыми источниками. [4]
Ссылки [ править ]
- ^ Jump up to: а б Черавола, Антонелло; Жублен, Франк (2021). «От JSON к JSEN через виртуальные языки» . Открытый журнал веб-технологий . 8 (1): 1–15.
В гомоиконичном языке первичным представлением программ является также структура данных в примитивном типе самого языка.
- ^ Уилер, Дэвид А. «Читаемые S-выражения Lisp» .
- ^ Мурс, Китай ; Дойч, LP (1965). «TRAC, язык обработки текста». Труды ACM '65 Материалы 20-й национальной конференции 1965 года . стр. 229–246. дои : 10.1145/800197.806048 .
- ^ Маннарт, Хервиг; МакГроарти, Крис; Галлант, Скотт; Де Кок, Коэн; Галлогично, Джим; Раваль, Ануп; Снавли, Кейт (2022). «На пути к масштабируемому совместному метапрограммированию: пример интеграции двух сред метапрограммирования» (PDF) . Международный журнал достижений в области программного обеспечения . 15 : 128–140. ISSN 1942-2628 .
- ^ «Не говори «гомоикон» » . Выражения перемен . 1 марта 2018 г.
- ^ Кей, Алан (1969). Реактивный двигатель (доктор философии). Университет Юты.
- ^ Jump up to: а б с д и ж г час я Гомоиконические языки
- ^ Jump up to: а б Гомоиконические языки (в архиве) в true Blue в Oracle блоге
- ^ «Шипучий эликсир» . 8thlight.com .
Эликсир, на первый взгляд, не является гомоиконом. Однако синтаксис на поверхности — это всего лишь фасад гомоиконической структуры, лежащей под ней.
- ^ «Зачем мы создали Юлю» . julialang.org .
Нам нужен гомоиконический язык с настоящими макросами, такими как Lisp, но с очевидными, знакомыми математическими обозначениями, такими как Matlab.
- ^ «метапрограммирование» . docs.julialang.org .
Как и Лисп, Джулия представляет свой собственный код как структуру данных самого языка.
- ^ Шапиро, Эхуд Ю.; Стерлинг, Леон (1994). Искусство Пролога: передовые методы программирования . МТИ Пресс. ISBN 0-262-19338-8 .
- ^ Рамзи, С.; Пытлик-Зиллиг, Б. (2012). «Методы генерации кода для совместимости XML-коллекций» . dh2012 Материалы конференции по цифровым гуманитарным наукам .
- ^ «Заметки для знатоков языков программирования» . Язык Вольфрам . Вольфрам. 2017.