Lisp(ruby、scala)以外にも、REPL(Read、Eval、Print、Loop)を使用していると言う言語がありますが、REPLの意味がLispと同じかどうかは不明です。LispREPLは非LispREPLとどう違うのですか?
7 に答える
REPLのアイデアはLispコミュニティから来ています。コマンドラインインターフェイスなど、他の形式のテキストインタラクティブインターフェイスがあります。一部のテキストインターフェイスでは、ある種のプログラミング言語のサブセットを実行することもできます。
REPLは、READ EVAL PRINT LOOP:(loop(print(eval(read))))の略です。
上記の4つの関数はそれぞれプリミティブLisp関数です。
Lispでは、REPLはコマンドラインインタープリター(CLI)ではありません。READ
コマンドを読み取らず、REPLはコマンドを実行しません。READ
入力データをS式形式で読み取り、内部データに変換します。したがって、このREAD
関数は、Lispコードだけでなく、あらゆる種類のS式を読み取ることができます。
READはs式を読み取ります。これは、ソースコードのエンコードもサポートするデータ形式です。READはLispデータを返します。
EVALはLispソースコードをLispデータの形式で受け取り、それを評価します。副作用が発生する可能性があり、EVALは1つ以上の値を返します。インタプリタまたはコンパイラを使用してEVALを実装する方法は定義されていません。実装はさまざまな戦略を使用します。
PRINTは、Lispデータを取得し、それをS式として出力ストリームに出力します。
LOOPはこれをループします。実際には、REPLはより複雑で、エラー処理とサブループ、いわゆるブレークループが含まれています。エラーが発生した場合は、エラーのコンテキストで、デバッグコマンドが追加された別のREPLを取得します。1回の反復で生成された値は、次の評価の入力として再利用することもできます。
Lispはデータとしてのコードと機能要素の両方を使用しているため、他のプログラミング言語とはわずかな違いがあります。
類似した言語、それらは同様のインタラクティブなインターフェースも提供します。たとえば、Smalltalkはインタラクティブな実行も可能にしますが、LispのようにI/Oにデータ形式を使用しません。Ruby / Python/...インタラクティブインターフェイスについても同じです。
質問:
では、表現を読み、評価し、値を印刷するという当初の考えはどれほど重要なのでしょうか。それは他の言語が行うこととの関連で重要ですか:テキストの読み取り、解析、実行、オプションで何かを印刷し、オプションで戻り値を印刷しますか?多くの場合、戻り値は実際には使用されません。
したがって、2つの可能な答えがあります:
Lisp REPLは、S式のデータI / Oの概念に基づいており、これらを評価するため、他のほとんどのテキストインタラクティブインターフェイスとは異なります。
REPLは、プログラミング言語の実装またはそれらのサブセットへのテキストの対話型インターフェースを説明する一般的な用語です。
LispのREPL
実際の実装では、Lisp REPLは複雑な実装を持ち、入力オブジェクトと出力オブジェクトのクリック可能なプレゼンテーション(Symbolics、CLIM、SLIME)まで多くのサービスを提供します。高度なREPL実装は、たとえばSLIME(Common Lisp用の人気のあるEmacsベースのIDE)、McCLIM、LispWorks、およびAllegroCLで利用できます。
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コードを計算することもできます。
2つの引数の2乗を加算する関数を見てみましょう。
CL-USER 4 > '(defun foo (a b) (+ (* a a) (* b b)))
(DEFUN FOO (A B) (+ (* A A) (* B B)))
4番目の要素は単なる算術式です。*
最後の値を参照します:
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に設定し++
ます。これは、データとして読み取られた2番目の前の入力を指します。SET
シンボル値を設定します。
CL-USER 10 > (set ++ 3)
3
次に、上記のリストの印刷方法が異なります。**
前の2番目の結果(テキストではなくデータ)を参照します。
CL-USER 11 > **
(1 2 3 ...)
2つのアプローチを比較するのは興味深いと思います。LispシステムのベアボーンREPLループは次のようになります。
(loop (print (eval (read))))
これがREPLループの2つの実際のForth実装です。ここでは何も残していません。これがこれらのループの完全なコードです。
: 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に戻る
実装の詳細に立ち入ることなく、LispREPLをたとえばRubyREPLと区別することはできません。関数としては同じです。
Scalaの「REPL」は「RCRPL」であると言えるでしょう。読み取り、コンパイル、実行、印刷です。ただし、コンパイラはメモリ内で「ホット」に保たれているため、進行中の対話ではかなり高速です。起動には数秒しかかかりません。
REPLがLISPの場合とまったく同じように動作する必要があると考える人はたくさんいます。そうでない場合、REPLは真のREPLではありません。むしろ、CLI(コマンドラインインタープリター)のように、それを別のものと見なします。正直なところ、私はそれが次の基本的な流れに従うと思う傾向があります:
- ユーザーからの入力を読み取る
- その入力を評価する
- 出力を印刷します
- 読み取りにループバック
それからそれはREPLです。前述のように、上記の機能を備えた言語はたくさんあります。
このような議論の例については、このredditスレッドを参照してください。
multi-repl
Node.JSを介してさまざまなREPLを公開するという素晴らしいプロジェクトがあります。
https://github.com/evilhackerdude/multi-repl
サポートされている言語のリストを見ると、LispだけがREPLの概念を持っているわけではないことは明らかです。
- clj(clojure)
- ghci(ghc)
- ipython
- irb(ruby)
- js(スパイダーモンキー)
- ノード
- Python
- sbcl
- v8
実際、Rubyで簡単なものを実装するのはかなり簡単です。
repl = -> prompt { print prompt; puts(" => %s" % eval(gets.chomp!)) }
loop { repl[">> "] }
LispREPLは非LispREPLとどう違うのですか?
CommonLispのREPLをPythonのIPythonと比較してみましょう。
主な2つのポイントは次のとおりです。
- Lispは画像ベースの言語です。変更後にプロセス/REPL/アプリ全体を再起動する必要はありません。コード関数を関数ごとにコンパイルします(コンパイラの警告などを使用)。
- 状態を失うことはありません。さらに、クラス定義を更新すると、REPL内のオブジェクトも、制御可能なルールに従って更新されます。そうすれば、実行中のシステムでコードをホットリロードできます。
Pythonでは、通常、IPythonを起動するか、ipdbにドロップされます。新しい関数を試すまで、いくつかのデータを定義します。ソースを編集し、再試行したいので、IPythonを終了し、プロセス全体を再開します。Lisp(主にCommon Lisp)では、まったくではなく、すべてがよりインタラクティブです。