Гомоконичность
В компьютерном программировании гомоиконичность ( от греческих слов 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 — список, использующий различные типы данных: (под)списки, символы, строки и целые числа.
(( :имя "Джон" :возраст 20 ) ( :имя "Мэри" :возраст 18 ) ( :имя "Алиса" :возраст 22 ))
Лисп-код. В примере используются списки, символы и числа.
( * ( грех 1,1 ) ( потому что 2,03 )) ; в инфиксе: грех(1.1)*cos(2.03)
Создайте приведенное выше выражение с помощью примитивной функции Lisp. LIST
и установите переменную EXPRESSION
к результату
( setf выражение ( list '* ( list'sin ' 1.1 ) ( list cos 2.03 ) )
-> ( * ( SIN 1.1 ) ( COS 2.03 )) ; Лисп возвращает и печатает результат
( третье выражение ) ; третий элемент выражения
-> ( COS 2.03 )
Изменить COS
срок до SIN
( setf ( первое ( третье выражение )) 'SIN )
; Теперь выражение имеет вид (* (SIN 1.1) (SIN 2.03)).
Оцените выражение
( оценки выражение )
-> 0,7988834
Распечатайте выражение в строку
( печати в строку выражение )
-> "(* (SIN 1.1) (SIN 2.03))"
Прочитать выражение из строки
( чтение из строки "(* (SIN 1.1) (SIN 2.03))" )
-> ( * ( SIN 1.1 ) ( SIN 2.03 )) ; возвращает список списков, чисел и символов
В Прологе [ править ]
1 ?- X равно 2*5.
X = 10,2
? - L = ( X равно 2*5 ) , write_canonical ( L ) .
есть ( _, * ( 2 , 5 ))
L = ( X есть 2*5 ) .
3 ?- L = ( десять ( X ) :- ( X равно 2*5 )) , write_canonical ( L ) .
:- ( десять ( A ) , есть ( A, * ( 2 , 5 )))
L = ( десять ( X ) :-X равно 2*5 ) .
4 ?- L = ( десять ( X ) :- ( X равно 2*5 )) , утверждать ( L ) .
L = ( десять ( X ) :-X равно 2*5 ) .
5 ?- десять ( Х ) .
Х = 10,6
? -
В строке 4 мы создаем новое предложение. Оператор :-
разделяет голову и тело предложения. С assert/1
* мы добавляем его в существующие предложения (добавляем в «базу данных»), чтобы можно было вызвать его позже. На других языках мы бы назвали это «созданием функции во время выполнения». Мы также можем удалить предложения из базы данных с помощью abolish/1
, или retract/1
.
* Число после имени предложения — это количество аргументов, которые оно может принимать. Его еще называют аритностью .
Мы также можем запросить базу данных, чтобы получить тело предложения:
7 ? -пункт ( десять ( X ) , Y ) .
Y знак равно ( Икс равно 2*5 ) .
8 ?- пункт ( десять ( X ) , Y ) , Y = ( X is Z ) .
Y = ( X равно 2*5 ) ,
Z = 2*5.
9 ? -пункт ( десять ( X ) , Y ) , вызов ( Y ) .
Икс знак равно 10 ,
Y = ( 10 это 2*5 ) .
call
аналогичен Лиспу eval
функция.
В Реболе [ править ]
Концепция обработки кода как данных, а также манипулирование ими и их оценка могут быть очень четко продемонстрированы в Rebol . (Rebol, в отличие от Lisp, не требует скобок для разделения выражений).
Ниже приведен пример кода в Rebol (обратите внимание, что >>
представляет подсказку переводчика; для удобства чтения добавлены пробелы между некоторыми элементами):
>> repeat i 3 [ print [ i "hello" ] ]
1 привет
2 привет
3 привет
( repeat
на самом деле это встроенная функция в Rebol, а не языковая конструкция или ключевое слово).
Заключая код в квадратные скобки, интерпретатор не оценивает его, а просто воспринимает как блок, содержащий слова:
[ повторить i 3 [ напечатать [ i "привет" ] ] ]
Этот блок имеет тип block! и, кроме того, может быть присвоено как значение слова с использованием того, что кажется синтаксисом присваивания, но на самом деле понимается интерпретатором как специальный тип ( set-word!
) и принимает форму слова, за которым следует двоеточие:
>>block1: [ repeat i 3 [ print [ i "hello" ] ] ]
;; Присвойте значение блока слову `block1` == [повторить i 3 [печатать [i "привет"]]] >>type? block1
;; Оцените тип слова `block1` == блокировать!
Блок по-прежнему можно интерпретировать с помощью do
функция, предоставляемая в Rebol (аналогично eval
в Лиспе ).
Можно опрашивать элементы блока и изменять их значения, тем самым изменяя поведение кода при его оценке:
>>block1/3
;; Третий элемент блока == 3 >>block1/3: 5
;; Установите значение третьего элемента равным 5. == 5 >>probe block1
;; Показать измененный блок == [повторить i 5 [печатать [i "привет"]]] >>do block1
;; Оцените блок 1 привет 2 привет 3 привет 4 привет 5 привет
См. также [ править ]
- Когнитивные аспекты нотаций , принципы построения синтаксиса языков программирования.
- Конкатенативный язык программирования
- Языко-ориентированное программирование
- Символическое программирование
- Самомодифицирующийся код
- Метапрограммирование — техника программирования, для которой очень полезна гомоиконичность.
- Реификация (информатика)
Примечания [ править ]
- ^ Предыдущие версии этой страницы Википедии 2006–2023 годов объединяли несвязанное примечание 5 в приведенном выше документе TRAC, в результате чего возникло неверное утверждение о том, что термин «гомоиконический» возник в статье Дугласа Макилроя по обработке макросов. В этой статье не упоминается никакой терминологии, даже отдаленно похожей; его влияние на работу ПРОФ носит иной характер. Это утверждение с тех пор было повторено некоторыми источниками. [4]
Ссылки [ править ]
- ^ Перейти обратно: а б Черавола, Антонелло; Жублен, Франк (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). Реактивный двигатель (доктор философии). Университет Юты.
- ^ Перейти обратно: а б с д Это ж г час я Гомоиконические языки
- ^ Перейти обратно: а б Гомоиконические языки (в архиве) в блоге true Blue в Oracle
- ^ «Шипучий эликсир» . 8thlight.com .
Эликсир, на первый взгляд, не является гомоиконом. Однако синтаксис на поверхности — это всего лишь фасад гомоиконической структуры, лежащей под ней.
- ^ «Зачем мы создали Юлю» . julialang.org .
Нам нужен гомоиконический язык с настоящими макросами, такими как Lisp, но с очевидными, знакомыми математическими обозначениями, такими как Matlab.
- ^ «метапрограммирование» . docs.julialang.org .
Как и Лисп, Джулия представляет свой собственный код как структуру данных самого языка.
- ^ Шапиро, Эхуд Ю.; Стерлинг, Леон (1994). Искусство Пролога: передовые методы программирования . МТИ Пресс. ISBN 0-262-19338-8 .
- ^ Рамзи, С.; Пытлик-Зиллиг, Б. (2012). «Методы генерации кода для совместимости XML-коллекций» . Материалы конференции по цифровым гуманитарным наукам dh2012 .
- ^ «Заметки для знатоков языков программирования» . Язык Вольфрам . Вольфрам. 2017.