Может ли язык содержать мощные макросы Lisp без скобок? - PullRequest
34 голосов
/ 27 апреля 2010

Может ли язык содержать мощные макросы Lisp без скобок?

Ответы [ 17 ]

41 голосов
/ 27 апреля 2010

Конечно, вопрос в том, удобен ли этот макрос и насколько он силен.

Давайте сначала посмотрим, как Лисп немного отличается.

Синтаксис Lisp основан на данных, а не на тексте

Lisp имеет двухэтапный синтаксис.

A) сначала идет синтаксис данных для s-выражений

Примеры:

(mary called tim to tell him the price of the book)

(sin ( x ) + cos ( x ))

s-выражения - это атомы, списки атомов или списки.

B) во-вторых, есть синтаксис языка Lisp поверх s-выражений. Не каждое s-выражение является допустимой программой на Лиспе.

(3 + 4)

не является допустимой программой на Лиспе, потому что Лисп использует префиксную нотацию.

(+ 3 4)

является допустимой программой на Лиспе. Первый элемент - это функция - здесь функция +.

S-выражения являются данными

Теперь интересным является то, что s-выражения могут быть прочитаны, а затем Лисп использует их обычные структуры данных (числа, символы, списки, строки).

Большинство других языков программирования не имеют примитивного представления для внутреннего источника - кроме строк.

Обратите внимание, что s-выражения здесь не представляют AST (абстрактное синтаксическое дерево). Это больше похоже на иерархическое дерево токенов, выходящее из фазы лексера. Лексер определяет лексические элементы.

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

Простые манипуляции с кодом с помощью функций списка

Давайте посмотрим на неверный код Lisp:

(3 + 4)

Программа

(defun convert (code)
   (list (second code) (first code) (third code)))

(convert '(3 + 4)) -> (+ 3 4)

преобразовал выражение infix в действительное выражение префикса Lisp. Мы можем оценить это тогда.

(eval (convert '(3 + 4))) -> 7

EVAL оценивает преобразованный исходный код. eval принимает в качестве входных данных s-выражение, здесь список (+ 3 4).

Как рассчитать с кодом?

В языках программирования теперь есть как минимум три варианта, позволяющих сделать исходные вычисления:

  1. основывать преобразования исходного кода на строковых преобразованиях

  2. использовать такую ​​же примитивную структуру данных, как Lisp. Более сложным вариантом этого является синтаксис на основе XML. Затем можно преобразовать выражения XML. Существуют и другие возможные внешние форматы в сочетании с интернализованными данными.

  3. использует формат описания реального синтаксиса и представляет исходный код, интернализованный в виде синтаксического дерева, с использованием структур данных, которые представляют синтаксические категории. -> использовать AST.

Для всех этих подходов вы найдете языки программирования. Lisp более или менее находится в лагере 2. Следствие: теоретически это не совсем удовлетворительно и делает невозможным статический анализ исходного кода (если преобразования кода основаны на произвольных функциях Lisp). Сообщество Lisp борется с этим на протяжении десятилетий (см., Например, множество подходов, которые пробовало сообщество Scheme). К счастью, он сравнительно прост в использовании по сравнению с некоторыми альтернативами и довольно мощный. Вариант 1 менее элегантен. Вариант 3 приводит к большой сложности в простых И сложных преобразованиях. Обычно это также означает, что выражение уже было проанализировано относительно определенной грамматики языка.

Другая проблема - КАК преобразовать код. Один подход будет основан на правилах преобразования (как в некоторых вариантах макроса Scheme). Другим подходом может быть специальный язык преобразования (например, язык шаблонов, который может выполнять произвольные вычисления). Подход Lisp заключается в использовании самого Lisp. Это позволяет писать произвольные преобразования, используя полный язык Lisp. В Лиспе нет отдельной стадии синтаксического анализа, но в любое время выражения могут быть прочитаны, преобразованы и оценены, потому что эти функции доступны пользователю.

Lisp является своего рода локальным максимумом простоты для преобразования кода.

Другой синтаксис внешнего интерфейса

Также обратите внимание, что функция read считывает s-выражения во внутренние данные. В Лиспе можно использовать другой reader для другого внешнего синтаксиса или повторно использовать встроенный считыватель Lisp и перепрограммировать его с помощью механизма чтения макроса - этот механизм позволяет расширять или изменять s-выражение синтаксис. Есть примеры для обоих подходов, чтобы обеспечить другой внешний синтаксис в Лиспе.

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

Почему синтаксис на основе s-выражений популярен среди программистов на Лиспе?

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

1) данные - это код - это данные Идея упрощает написание всех видов преобразований кода на основе интернализованных данных. Существует также относительно прямой путь от чтения кода, манипулирования кодом до печати кода. Можно использовать обычные средства разработки.

2) текстовый редактор может быть запрограммирован прямым способом для манипулирования s-выражениями. Это делает базовые преобразования кода и данных в редакторе относительно простыми.

Первоначально считалось, что Лисп имеет другой, более традиционный синтаксис. Позже было несколько попыток переключиться на другие варианты синтаксиса, но по некоторым причинам он либо потерпел неудачу, либо породил разные языки.

20 голосов
/ 27 апреля 2010

Абсолютно. Это просто на пару порядков сложнее, если вам приходится иметь дело со сложной грамматикой. Как заметил Питер Норвиг :

Python имеет доступ к абстрактное синтаксическое дерево программ, но это не для слабонервных. На положительная сторона, модули легко понять, и с пяти минут и пять строк кода я смог получить это:

>>> parse("2 + 2")

['eval_input', ['testlist', ['test', ['and_test', ['not_test', ['comparison', ['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term', ['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term', ['factor', ['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]

Это было скорее разочарованием для меня. Анализ Lisp эквивалентного выражения равен (+ 2 2). Кажется, что только настоящий эксперт захочет манипулировать деревьями разбора Python, тогда как деревья разбора Lisp просты в использовании. По-прежнему возможно создать нечто похожее на макросы в Python путем объединения строк, но это не интегрировано с остальным языком, и поэтому на практике это не делается.

Поскольку я не супергений (или даже Питер Норвиг), я буду придерживаться (+ 2 2).

13 голосов
/ 27 апреля 2010

Вот более короткая версия ответа Райнера:

Чтобы иметь макросы в стиле lisp, вам нужен способ представления исходного кода в структурах данных. В большинстве языков единственной «структурой данных исходного кода» является строка, которая не имеет почти структуры, достаточной для того, чтобы вы могли выполнять настоящие макросы. Некоторые языки предлагают реальную структуру данных, но она слишком сложна, как, например, Python, поэтому написание реальных макросов глупо сложно и не стоит того.

Списки и скобки Лиспа попали в самую лучшую точку посередине. Достаточно структуры, чтобы с ней было легко работать, но не слишком, чтобы вы утонули в сложности. В качестве бонуса, когда вы вкладываете в списки, вы получаете дерево, которое оказывается именно той структурой, которую естественным образом принимают языки программирования (почти все языки программирования сначала разбираются в «абстрактное синтаксическое дерево», или AST, перед тем, как фактически интерпретируются / компилируются ).

По сути, программирование на Лиспе - это написание AST напрямую, а не на каком-то другом языке, который затем превращается в AST на компьютере. Вы могли бы отказаться от паренов, но вам просто нужен другой способ сгруппировать вещи в список / дерево. Вы, вероятно, не многого выиграете от этого.

9 голосов
/ 27 апреля 2010

Скобки не имеют отношения к макросам. Это просто способ Лиспа.

Например, у Пролога есть очень мощный механизм макросов, который называется «расширение термина». По сути, всякий раз, когда Пролог читает термин T, он пытается использовать специальное правило term_expansion(T, R). Если это успешно, содержание R интерпретируется вместо T.

5 голосов
/ 27 апреля 2010

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

A ((B C) D)

и

A
    B
    C
  D
5 голосов
/ 27 апреля 2010

Не говоря уже о языке Дилана , который имеет довольно мощную синтаксическую систему макросов, которая обеспечивает (помимо прочего) ссылочную прозрачность, будучи языком инфикса (в стиле Алгола).

3 голосов
/ 05 ноября 2010

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

По этой причине он предлагает инфиксный синтаксис, такой как {1 + 2 + 3} и {1 + {2 * 3}} (обратите внимание на пробелы между символами), которые переводятся в (+ 1 2) и (+ 1) (* 2 3)) соответственно. Он добавляет, что если кто-то пишет {1 + 2 * 3}, он должен стать (nfx 1 + 2 * 3), который может быть захвачен, если вы действительно хотите предоставить приоритет, но в качестве по умолчанию будет ошибка.

Он также предполагает, что отступы должны быть значительными, предлагает, чтобы функции могли называться fn (ABC), а также (fn ABC), хотел бы, чтобы данные [A] переводились в (данные A bracketaccess), и что все система должна быть совместима с s-выражениями.

В целом, это интересный набор предложений, с которыми я бы хотел поэкспериментировать. (Но не говорите никому на comp.lang.lisp: они сожгут вас на костре за ваше любопытство: -).

3 голосов
/ 27 апреля 2010

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

proc gproc {name arguments body} {
    set realbody "global foo bar boo;$body"
    uplevel 1 [list proc $name $arguments $realbody]
}

При этом все процедуры, объявленные с gproc xyz вместо proc xyz, будут иметь доступ к глобальным переменным foo, bar и boo. Весь ключ в том, что uplevel принимает команду и оценивает ее в контексте вызывающего , а list (среди прочего) является идеальным конструктором для безопасных для замены фрагментов кода.

2 голосов
/ 15 марта 2013

Да, вы можете использовать макросы Lisp без скобок.

Взгляните на «сладкие выражения», которые предоставляют набор дополнительных сокращений для традиционных s-выражений. Они добавляют отступы, способ создания инфиксов и традиционные вызовы функций, такие как f (x), но с обратной совместимостью (вы можете свободно смешивать хорошо отформатированные s-выражения и sweet-выражения), универсальные и гомо-логические .

Sweet-выражения были разработаны на http://readable.sourceforge.net, и есть пример реализации.

Для Схемы есть SRFI для сладких выражений, SRFI-110: http://srfi.schemers.org/srfi-110/

2 голосов
/ 27 апреля 2010

Преобразования парсинга Эрланга по мощности похожи на макросы Lisp, хотя их гораздо сложнее писать и использовать (они применяются ко всему исходному файлу, а не вызываются по требованию).

У самого Лиспа было небольшое противоречие с синтаксисом без скобок в форме M-выражений . Это не понравилось сообществу, хотя варианты идеи нашли свое отражение в современном Лиспе, так что вы получите мощные макросы Лиспа без скобок ... в Лиспе!

...