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

Является ли Lisp единственным языком с REPL?

Существуют языки, отличные от Lisp (ruby, scala), которые говорят, что используют REPL (Read, Eval, Print, Loop), но неясно, является ли то, что подразумевается под REPL, такое же, как в Lisp. Как Lisp REPL отличается от Lisp REPL?

4b9b3361

Ответ 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/...

Вопрос:

Итак, насколько важна первоначальная идея чтения ВЫРАЖЕНИЙ, их оценки и ПЕЧАТИ их значений? Это важно по отношению к тому, что делает другой язык: чтение текста, его синтаксический анализ, выполнение, печать чего-либо и, при необходимости, печать возвращаемого значения? Часто возвращаемое значение на самом деле не используется.

Таким образом, есть два возможных ответа:

  1. Lisp REPL отличается от большинства других текстовых интерактивных интерфейсов, потому что он основан на идее ввода-вывода данных s-выражений и их оценки.

  2. 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 для многих языков:

C/С++

С#/LINQ

Erlang

Haskell (на окнах)

Java

Javascript

Perl

Python

Ruby

Scala

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) совсем нет, все более интерактивно.