Существуют языки, отличные от Lisp (ruby, scala), которые говорят, что используют REPL (Read, Eval, Print, Loop), но неясно, является ли то, что подразумевается под REPL, такое же, как в Lisp. Как Lisp REPL отличается от Lisp REPL?
Является ли Lisp единственным языком с REPL?
Ответ 1
Идея REPL исходит от сообщества Lisp. Существуют и другие формы текстовых интерактивных интерфейсов, например интерфейс командной строки. Некоторые текстовые интерфейсы также позволяют выполнять подмножество некоторого языка программирования.
REPL означает READ EVAL PRINT LOOP: (цикл (печать (оценка (чтение)))).
Каждая из четырех вышеперечисленных функций является примитивной функцией Lisp.
В Lispе REPL не является интерпретатором командной строки (CLI). READ
не читает команды, а REPL не выполняет команды. READ
читает ввод и преобразует его в данные. Таким образом, функция READ
может читать все виды s-выражений - не только код на Lispе.
READ читает s-выражение. Это формат данных, который также поддерживает кодирование исходного кода. READ возвращает данные Lisp.
EVAL берет исходный код Lisp в виде данных Lisp и оценивает его. Могут возникнуть побочные эффекты, и EVAL возвращает одно или несколько значений. Как реализуется EVAL с помощью интерпретатора или компилятора, не определено. Реализации используют разные стратегии.
PRINT берет данные Lisp и печатает их как s-выражения.
LOOP просто зацикливается на этом. В реальной жизни REPL является более сложным и включает обработку ошибок и подциклы, так называемые разрывные циклы. В случае ошибки можно получить еще один REPL с добавленными командами отладки в контексте ошибки. Значение, полученное за одну итерацию, также можно повторно использовать в качестве входных данных для следующей оценки.
Поскольку в Lisp используются как код-как-данные, так и функциональные элементы, есть небольшие отличия от других языков программирования.
Подобные языки предоставляют также похожие интерактивные интерфейсы. Например, Smalltalk также допускает интерактивное выполнение, но он не использует формат данных для ввода-вывода, как это делает Lisp. То же самое для любого интерактивного интерфейса Ruby/Python/...
Вопрос:
Итак, насколько важна первоначальная идея чтения ВЫРАЖЕНИЙ, их оценки и ПЕЧАТИ их значений? Это важно по отношению к тому, что делает другой язык: чтение текста, его синтаксический анализ, выполнение, печать чего-либо и, при необходимости, печать возвращаемого значения? Часто возвращаемое значение на самом деле не используется.
Таким образом, есть два возможных ответа:
-
Lisp REPL отличается от большинства других текстовых интерактивных интерфейсов, потому что он основан на идее ввода-вывода данных s-выражений и их оценки.
-
REPL - это общий термин, описывающий текстовые интерактивные интерфейсы для реализаций языка программирования или их подмножеств.
В реальных реализациях Lisp REPL имеют сложную реализацию и предоставляют множество сервисов, вплоть до интерактивных презентаций (Symbolics, CLIM, SLIME) объектов ввода и вывода. Расширенные реализации REPL доступны, например, в SLIME (популярной интегрированной среде разработки на Emacs для Common Lisp), LispWorks и Allegro CL.
Пример взаимодействия Lisp REPL:
перечень товаров и цен:
CL-USER 1 > (setf *products* '((shoe (100 euro))
(shirt (20 euro))
(cap (10 euro))))
((SHOE (100 EURO)) (SHIRT (20 EURO)) (CAP (10 EURO)))
Заказ, список товара и количество:
CL-USER 2 > '((3 shoe) (4 cap))
((3 SHOE) (4 CAP))
Цена для заказа, *
является переменной, содержащей последнее значение REPL. Он не содержит это значение в виде строки, но содержит реальные фактические данные.
CL-USER 3 > (loop for (n product) in *
sum (* n (first (second (find product *products*
:key 'first)))))
340
Но вы также можете вычислить код на Lispе:
Давайте возьмем функцию, которая добавляет квадраты двух своих аргументов:
CL-USER 4 > '(defun foo (a b) (+ (* a a) (* b b)))
(DEFUN FOO (A B) (+ (* A A) (* B B)))
Четвертый элемент - это просто арифметическое выражение. *
относится к последнему значению:
CL-USER 5 > (fourth *)
(+ (* A A) (* B B))
Теперь мы добавим код вокруг него, чтобы связать переменные a
и b
с некоторыми числами. Мы используем функцию Lisp LIST
для создания нового списка.
CL-USER 6 > (list 'let '((a 12) (b 10)) *)
(LET ((A 12) (B 10)) (+ (* A A) (* B B)))
Затем мы оцениваем вышеприведенное выражение. Опять же, *
относится к последнему значению.
CL-USER 7 > (eval *)
244
Есть несколько переменных, которые обновляются с каждым взаимодействием REPL
. Примерами являются *
, **
и ***
для предыдущих значений. Также есть +
для предыдущего ввода. Эти переменные имеют в качестве значений не строки, а объекты данных. +
будет содержать последний результат операции чтения REPL. Пример:
Каково значение переменной *print-length*
?
CL-USER 8 > *print-length*
NIL
Давайте посмотрим, как список читается и печатается:
CL-USER 9 > '(1 2 3 4 5)
(1 2 3 4 5)
Теперь давайте установим вышеуказанный символ *print-length*
в 3. ++
относится ко второму предыдущему прочитанному вводу, как к данным. SET
устанавливает значение символов.
CL-USER 10 > (set ++ 3)
3
Тогда вышеприведенный список печатается по-другому. **
относится ко второму предыдущему результату - данные, а не текст.
CL-USER 11 > **
(1 2 3 ...)
Ответ 2
Увидев, что концепция REPL - это просто читать, Eval, Print и Loop, она не слишком удивляет, что существуют REPL для многих языков:
Haskell (на окнах)
Smalltalk - Я узнал об этом на REPL!
EDIT Я забыл о Java!
Ответ 3
Я думаю, что интересно сравнить два подхода. Голый цикл REPL в системе Lisp будет выглядеть следующим образом:
(loop (print (eval (read))))
Вот две фактические реализации Forth цикла REPL. Я ничего не оставляю здесь - это полный код для этих циклов.
: DO-QUIT ( -- ) ( R: i*x -- )
EMPTYR
0 >IN CELL+ ! \ set SOURCE-ID to 0
POSTPONE [
BEGIN \ The loop starts here
REFILL \ READ from standard input
WHILE
INTERPRET \ EVALUATE what was read
STATE @ 0= IF ." OK" THEN \ PRINT
CR
REPEAT
;
: quit
sp0 @ 'tib !
blk off
[compile] [
begin
rp0 @ rp!
status
query \ READ
run \ EVALUATE
state @ not
if ." ok" then \ PRINT
again \ LOOP
;
Lisp и Forth делают совершенно разные вещи, особенно в части EVAL, но также и в части PRINT. Тем не менее, они разделяют тот факт, что программа на обоих языках запускается путем подачи исходного кода в соответствующие циклы, и в обоих случаях код - это просто данные (хотя в случае Forth это больше похоже на то, что данные также являются кодом).
Я подозреваю, что кто-то, кто говорит только Lisp, имеет REPL, состоит в том, что цикл READ считывает DATA, который анализируется с помощью EVAL, и создается программа, потому что CODE также является DATA. Это различие во многом интересно в отношении разницы между Lisp и другими языками, но что касается REPL, это вообще не имеет значения.
Рассмотрим это извне:
- READ - возвращает данные из stdin
- EVAL - обрабатывает указанный ввод как выражение на языке
- PRINT - печать результата EVAL
- LOOP - вернитесь к READ
Не вдаваясь в подробности реализации, нельзя отличить Lisp REPL от, например, Ruby REPL. В качестве функций они одинаковы.
Ответ 4
Я думаю, вы могли бы сказать, что Scala "REPL" - это "RCRPL": "Чтение, компиляция, запуск, печать". Но поскольку компилятор хранится "горячим" в памяти, он довольно быстрый для текущих взаимодействий - для запуска всего несколько секунд.
Ответ 5
Есть несколько людей, которые считают, что REPL должен вести себя так же, как в LISP, или это не настоящий REPL. Скорее, они считают это чем-то другим, например CLI (интерпретатор командной строки). Честно говоря, я склонен думать, что если он будет следовать основному потоку:
- читать данные от пользователя
- оцените этот вход
- распечатать вывод
- вернуться к чтению
то это REPL. Как уже отмечалось, существует много языков, обладающих вышеуказанными возможностями.
См. этот reddit thread для примера такого обсуждения.
Ответ 6
Там есть хороший проект под названием multi-repl
, который предоставляет различные REPL с помощью Node.JS:
https://github.com/evilhackerdude/multi-repl
Если вы посмотрите на список поддерживаемых языков, то совершенно ясно, что не только Lisp имеет концепцию REPL.
- clj (clojure)
- ghci (ghc)
- IPython
- irb (ruby)
- js (spidermonkey)
- node
- питон
- SbCl
- v8
Фактически реализация тривиального в Ruby довольно проста:
repl = -> prompt { print prompt; puts(" => %s" % eval(gets.chomp!)) }
loop { repl[">> "] }
Ответ 7
Чем Lisp REPL отличается от не-Lisp REPL?
Давайте сравним Common Lisp REPL с Python IPython.
Основные два момента:
- Lisp - это язык изображений. Нет необходимости перезапускать процесс/REPL/все приложение после изменения. Мы компилируем нашу функцию кода функцией (с предупреждениями компилятора и т.д.).
- мы не теряем состояние. Более того, когда мы обновляем определения классов, наши объекты в REPL также обновляются в соответствии с правилами, которые мы контролируем. Таким образом, мы можем выполнить горячую перезагрузку кода в работающей системе.
В Python, как правило, вы запускаете IPython или попадаете в ipdb. Вы определяете некоторые данные, пока не опробуете свою новую функцию. Вы редактируете исходный код и хотите повторить попытку, поэтому вы выходите из IPython и снова запускаете весь процесс. В Lispе (в основном Common Lisp) совсем нет, все более интерактивно.