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

Как Lisp цикл read-eval-print отличается от Python?

Я столкнулся с следующим выражением Ричардом Столлманом:

'Когда вы запускаете систему Lisp, она вводит цикл чтения-eval-print. Большинство других языков ничего не сравнимо с чтением, ничто не сравнимо с eval и ничего не сравнимо с печатью. Какие зияющие недостатки!

Теперь я немного программировал в Lisp, но я написал большое количество кода в Python и недавно немного в Erlang. Мое впечатление было то, что эти языки также предлагают цикл чтения-eval-print, но Столлман не согласен (по крайней мере, на Python):

'Я просмотрел документацию Python после того, как люди сказали мне, что он в основном похож на Lisp. Я пришел к выводу, что это не так. Когда вы начинаете Lisp, он выполняет "чтение", "eval" и "print", все из которых отсутствуют в Python. '

Есть ли принципиальная техническая разница между Lisp и циклами чтения-eval-печати Python? Можете ли вы привести примеры того, что Lisp REPL делает легко и что трудно сделать в Python?

4b9b3361

Ответ 1

В поддержку позиции Stallman Python не делает то же самое, что и обычные Lisp системы в следующих областях:

  • Функция read в Lisp считывает S-выражение, которое представляет собой произвольную структуру данных, которая может обрабатываться как данные или оцениваться как код. Самое близкое в Python читает одну строку, которую вам придется анализировать самостоятельно, если вы хотите, чтобы она имела в виду что-то.

  • Функция eval в Lisp может выполнять любой код Lisp. Функция eval в Python оценивает только выражения и нуждается в инструкции exec для запуска операторов. Но оба они работают с исходным кодом Python, представленным в виде текста, и вам нужно перепрыгнуть через кучу обручей, чтобы "eval" Python AST.

  • Функция print в Lisp записывает S-выражение в той же форме, что и read. print в Python выводит что-то, определенное данными, которые вы пытаетесь распечатать, что, безусловно, не всегда обратимо.

Задание Stallman немного неприлично, потому что, очевидно, Python имеет функции с именем точно eval и print, но они делают что-то другое (и хуже) к тому, что он ожидает.

На мой взгляд, у Python есть некоторые аспекты, похожие на Lisp, и я могу понять, почему люди могли бы рекомендовать Stallman заглянуть в Python. Однако, как Пол Грэм утверждает в What Made Lisp Разное, любой язык программирования, который включает в себя все возможности Lisp, также должен быть Lisp.

Ответ 2

Точка Stallman заключается в том, что не реализация явного "читателя" делает Python REPL кажущимся искалеченным по сравнению с Lisps, потому что он удаляет важный шаг из процесса REPL. Reader - это компонент, который преобразует текстовый входной поток в память - подумайте о чем-то вроде XML-синтаксического анализатора, встроенного в язык и используемом как для исходного кода, так и для данных. Это полезно не только для написания макросов (что теоретически возможно в Python с модулем ast), но и для отладки и интроспекции.

Скажите, что вас интересует, как реализована специальная форма incf. Вы можете проверить это следующим образом:

[4]> (macroexpand '(incf a))
(SETQ A (+ A 1)) ;

Но incf может делать гораздо больше, чем увеличивать значения символов. Что именно это делает, когда его попросили увеличить запись хэш-таблицы? Давайте посмотрим:

[2]> (macroexpand '(incf (gethash htable key)))
(LET* ((#:G3069 HTABLE) (#:G3070 KEY) (#:G3071 (+ (GETHASH #:G3069 #:G3070) 1)))
 (SYSTEM::PUTHASH #:G3069 #:G3070 #:G3071)) ;

Здесь мы узнаем, что incf вызывает системную функцию puthash, которая представляет собой деталь реализации этой общей системы Lisp. Обратите внимание, как "принтер" использует функции, известные "читателю", например, ввод анонимных символов с синтаксисом #: и обращение к тем же символам в пределах расширенного выражения. Эмулирование такого рода инспекций в Python было бы гораздо более подробным и менее доступным.

В дополнение к очевидным применениям в REPL опытные Lispers используют print и read в коде как простой и легкодоступный инструмент сериализации, сравнимый с XML или json. Хотя Python имеет функцию str, эквивалентную Lisp print, ей не хватает эквивалента read, ближайший эквивалент которого eval. eval, конечно, объединяет два разных понятия, разбора и оценки, что приводит к проблемам вроде этого и решения, подобные этому, и является повторяющейся темой на форумах Python. Это не было бы проблемой в Lisp именно потому, что читатель и оценщик чисто разделены.

Наконец, расширенные функции средства чтения позволяют программисту расширить язык таким образом, который даже макросы не могли обеспечить иначе. Прекрасным примером такого рода трудностей является пакет infix Марка Кантровица, реализующий полнофункциональный синтаксис инфикса как макрос читателя.

Ответ 3

В системе, основанной на Lisp, обычно разрабатывается программа, когда она выполняется из REPL (читается цикл eval print). Таким образом, он объединяет множество инструментов: завершение, редактор, интерпретатор командной строки, отладчик,... По умолчанию это нужно. Введите выражение с ошибкой - вы находитесь на другом уровне REPL с включенными некоторыми командами отладки. Вам действительно нужно что-то сделать, чтобы избавиться от этого поведения.

Вы можете иметь два разных значения концепции REPL:

  • Read Eval Print Loop, как в Lisp (или нескольких других подобных языках). Он читает программы и данные, оценивает и распечатывает данные результата. Python не работает таким образом. Lisp REPL позволяет работать непосредственно в метапрограммном режиме, записывая код, который генерирует (код), проверяет разложения, преобразует фактический код и т.д. Lisp имеет read/eval/print в качестве верхнего цикла. Python имеет что-то вроде readstring/evaluation/printstring как верхний цикл.

  • Интерфейс командной строки. Интерактивная оболочка. См. Например, IPython. Сравните это с общим Lisp SLIME.

По умолчанию оболочка Python в режиме по умолчанию не так эффективна для интерактивного использования:

Python 2.7.2 (default, Jun 20 2012, 16:23:33) 
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a+2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

Появляется сообщение об ошибке и оно.

Сравните это с CLISP REPL:

rjmba:~ joswig$ clisp
  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Welcome to GNU CLISP 2.49 (2010-07-07) <http://clisp.cons.org/>

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010

Type :h and hit Enter for context help.

[1]> (+ a 2)

*** - SYSTEM::READ-EVAL-PRINT: variable A has no value
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of A.
STORE-VALUE    :R2      Input a new value for A.
ABORT          :R3      Abort main loop
Break 1 [2]> 

CLISP использует систему условий Lisp для взлома в отладчик REPL. Он представляет некоторые перезагрузки. В контексте ошибки новый REPL предоставляет расширенные команды.

Используйте перезапуск :R1:

Break 1 [2]> :r1
Use instead of A> 2
4
[3]> 

Таким образом, вы получаете интерактивный ремонт программ и выполнение прогонов...

Ответ 4

Интерактивный режим Python отличается от режима "читать код из файла" Python несколькими, небольшими, критическими способами, вероятно, присущими текстовому представлению языка. Python также не является homoiconic, что-то, что заставляет меня назвать его "интерактивным", а не "read-eval-print loop". В стороне, я бы сказал, что это больше разница в классе, чем разница в натуральной форме.

Теперь что-то tahtactly приближается к "разнице в натуральном выражении", в файле кода Python вы можете легко вставлять пустые строки:

def foo(n):
  m = n + 1

  return m

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

Кроме того, есть довольно удобные переменные удобства в Common Lisp (CL), которые недоступны (по крайней мере, насколько я знаю) в Python. И CL, и Python имеют "значение последнего выражения" (* в CL, _ в Python), но CL также имеет ** (значение выражения перед последним) и *** (значение параметра before что) и +, ++ и +++ (сами выражения). CL также не различает выражения и утверждения (по сути, все - это выражение), и все это помогает построить гораздо более богатый опыт REPL.

Как я уже сказал в начале, это больше разница в классе, чем разница в натуральной форме. Но если бы разрыв был только более сильным между ними, это, вероятно, было бы и разницей в роде.