Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Примеры:

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

(sin ( x ) + cos ( x ))

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

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

(3 + 4)

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

(+ 3 4)

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

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

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

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

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

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

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

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

(3 + 4)

Программа

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

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

преобразовал инфиксное выражение в действительное префиксное выражение 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е нет отдельной стадии синтаксического анализа, но в любое время выражения могут быть прочитаны, преобразованы и оценены - потому что эти функции доступны пользователю.

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

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

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

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

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

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

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

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

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

Ответ 2

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

У 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).

Ответ 3

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

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

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

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

Ответ 4

Скобки не имеют отношения к макросам. Это просто Lisp способ делать вещи.

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

Ответ 5

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

Ответ 6

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

A ((B C) D)

и

A
    B
    C
  D

Ответ 7

Переписывание кода в 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 - это, между прочим, идеальный конструктор для фрагментов кода, подходящих для замены.

Ответ 8

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

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

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

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

Ответ 9

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

Сам

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

Ответ 10

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

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

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

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

Ответ 11

Нет, это не обязательно. Все, что дает вам некоторый доступ к дереву разбора, будет достаточно, чтобы вы могли манипулировать телом макроса так же, как это сделано в Common Lisp. Однако, поскольку манипуляция АСТ в lisp идентична манипулированию списками (что-то, что граничит с простым в семействе lisp), возможно, это не так естественно, не имея "дерева синтаксического анализа" и "написанного" форма "будет по существу одинаковой.

Ответ 12

Я думаю, что это не упоминалось.

С++ шаблоны завершают Turing и выполняют обработку во время компиляции.

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

Итак, представьте, что у вас есть 3 вектора из 1000 элементов, и вы должны выполнить:

(A + B + C)[0] 

Вы можете захватить это дерево в шаблоне выражения и произвольно манипулировать им во время компиляции.

С этим деревом во время компиляции вы можете преобразовать выражение. Например, если это выражение означает A[0] + B[0] + C[0] для вашего домена, вы можете избегайте нормальной обработки С++, которая будет:

  • Добавьте A и B, добавив 1000 элементов.
  • Создайте временное значение для результата и добавьте с 1000 элементами C.
  • Индекс результата, чтобы получить первый элемент.

И замените на другое преобразованное дерево шаблонов выражений, которое делает:

  • Захват A [0]
  • Capture B [0]
  • Capture C [0]
  • Добавьте все 3 результата вместе в результат, чтобы вернуться с + = во избежание временные.

Это не лучше, чем lisp, я думаю, но он все еще очень мощный.

Ответ 14

Boo имеет хороший синтаксис макроса с цитированием, который использует [| |] как разделители и имеет определенные подстановки, которые на самом деле синтаксически проверяются конвейером компилятора с использованием переменных $. В то время как простой и относительно безболезненный в использовании, гораздо сложнее реализовать на стороне компилятора, чем s-выражения. Решение Boo может иметь несколько ограничений, которые не повлияли на мой собственный код. Существует также альтернативный синтаксис, который читается больше как обычный OO-код, но он попадает в категорию "не для слабонервных", например, для работы с деревьями разбора Ruby или Python.

Ответ 15

Javascript строки шаблонов предлагают еще один подход к подобным вещам. Например, Марк С. Миллер quasiParserGenerator реализует синтаксис грамматики для парсеров.

Ответ 16

Идите вперед и введите язык программирования Elixir.

Elixir - это функциональный язык программирования, который по ощущениям похож на Lisp по отношению к макросам, но он работает на Ruby и работает поверх Erlang VM.

Для тех, кто не любит скобки, но хочет, чтобы на их языке были мощные макросы, Elixir - отличный выбор.

Ответ 17

Вы можете написать макрос в R (он больше похож на синтаксис Algol), который имеет понятие отложенного выражения, как в макросах LISP. Вы можете вызвать substitute() или quote() чтобы не вычислять задержанное выражение, а получить фактическое выражение и просмотреть его исходный код, как в LISP. Даже структура исходного кода выражения такая же, как в LISP. Операторы являются первым пунктом в списке. Например: input$foo который получает свойство foo из списка input как выражение записывается как ['$', 'input', 'foo'] как в LISP.

Вы можете проверить метапрограммирование книги в R, которое также показывает, как создавать макросы в R (не то, что вы обычно делаете, но это возможно). Это основано на статье 2001 года Programmes Niche: Макросы в R, которые объясняют, как писать макросы LIPS в R.