「48時間でSchemeを書こう/REPLの作成」の版間の差分

削除された内容 追加された内容
en:Write Yourself a Scheme in 48 Hours/Building a REPL (09:08, 19 July 2007 UTC) をインポート
 
訳出
1 行
今まで、私たちはコマンドラインから一つだけ式を取って評価し、結果を表示して終了することで満足してきました。これは計算機にしては上出来ですが、人々の思うところの「プログラミング」っぽくはないですね。新しい関数や変数を定義し、後でそれを参照したりしたいものです。でもその前に、終了せずに複数の式を実行できるシステムを作らなければなりません。
So far, we've been content to evaluate single expressions from the command line, printing the result and exiting afterwards. This is fine for a calculator, but isn't what most people think of as "programming". We'd like to be able to define new functions and variables, and refer to them later. But before we can do this, we need to build a system that can execute multiple statements without exiting the program.
 
プログラム全体を一度に実行してしまうのではなく、私たちは''read-eval-print loop''を作ることにします。これは対話的にコンソールからの式をひとつづつ読み込んで実行し、式それぞれの後に結果を表示します。後で入力された式はそれより前で定義された変数を参照でき(次の章の後には)、それによって関数のライブラリを作りあげることができるようになります。
Instead of executing a whole program at once, we're going to build a ''read-eval-print loop''. This reads in expressions from the console one at a time and executes them interactively, printing the result after each expression. Later expressions can reference variables set by earlier ones (or will be able to, after the next section), letting you build up libraries of functions.
 
First, we need to import some additional 始めに、いくらか追加の[http://www.haskell.org/onlinereport/io.html IO functions]. Add the following to the top of the program:をインポートする必要があります。次の行をプログラムの冒頭に加えてください。
 
<syntaxhighlight lang="haskell">
import IO hiding (try)
</syntaxhighlight>
 
Parsecの<code>try</code>関数を使っているので、<code>try</code>関数(IOモジュールでエラー処理に使われる)は隠す必要があります。
We have to hide the try function (used in the IO module for exception handling) because we use Parsec's try function.
 
次に、私たちの行うIOタスクを簡単にするためいくつかの補助関数を用意します。文字列を表示してストリームをすぐにフラッシュする関数が要ります。出力がバッファに溜め込まれたままになって、ユーザはついにプロンプトや結果を見ることがないようなことがあると困りますから。
Next, we define a couple of helper functions to simplify some of our IO tasks. We'll want a function that prints out a string and immediately flushes the stream; otherwise, output might sit in output buffers and the user will never see prompts or results.
 
<syntaxhighlight lang="haskell">
flushStr :: String -&gt;> IO ()
flushStr str = putStr str &gt;&gt;>> hFlush stdout
</syntaxhighlight>
 
そして、プロンプトを表示して入力から一行読み込む関数を作ります。
Then, we create a function that prints out a prompt and reads in a line of input:
 
<syntaxhighlight lang="haskell">
readPrompt :: String -&gt;> IO String
readPrompt prompt = flushStr prompt &gt;&gt;>> getLine
</syntaxhighlight>
 
<code>main</code>から文字列をパースして評価しエラーを補足する部分を抜き出して一つの関数にします。
Pull the code to parse and evaluate a string and trap the errors out of main into its own function:
 
<syntaxhighlight lang="haskell">
evalString :: String -&gt;> IO String
evalString expr = return $ extractValue $ trapError (liftM show $ readExpr expr &gt;&gt;>>= eval)
</syntaxhighlight>
 
そして文字列を評価して結果を表示する関数を書きます。
And write a function that evaluates a string and prints the result:
 
<syntaxhighlight lang="haskell">
evalAndPrint :: String -&gt;> IO ()
evalAndPrint expr = evalString expr &gt;&gt;>>= putStrLn
</syntaxhighlight>
 
それではこれら全てを一緒にしましょう。入力を読み、関数を実行し、結果を表示するという過程を無限ループするのが目標です。組み込み関数[http://www.haskell.org/onlinereport/standard-prelude.html#$vinteract interact]は''ほぼ''私たちの目的通りのことをしますが、ループしてくれません。<code>sequence . repeat . interact</code>という組み合わせを使えば無限ループはできますが、今度はそれから抜け出すことができなくなります。そこで自前でループを定義しましょう。
Now it's time to tie it all together. We want to read input, perform a function, and print the output, all in an infinite loop. The built-in function [http://www.haskell.org/onlinereport/standard-prelude.html#$vinteract interact] ''almost'' does what we want, but doesn't loop. If we used the combination <span class="inline_code">sequence . repeat . interact</span>, we'd get an infinite loop, but we wouldn't be able to break out of it. So we need to roll our own loop:
 
<syntaxhighlight lang="haskell">
until_ :: Monad m =&gt;> (a -&gt;> Bool) -&gt;> m a -&gt;> (a -&gt;> m ()) -&gt;> m ()
until_ pred prompt action = do
result &lt;<- prompt
if pred result
then return ()
else action result &gt;&gt;>> until_ pred prompt action
</syntaxhighlight>
 
最後にアンダースコアの付いた名前は、Haskellにおける一般的な命名規約に沿ったもので、繰り返すが値を返さないモナド関数を表します。<code>until_</code>は終了を判断する述語、その前に実行するアクション、そして入力に対して行うアクションを返す関数を引数に取ります。後者2つはIOのみならず''どんな''モナドに対しても働くように一般化されています。そのため、それらの型は型変数<code>m</code>を使って表され、<code>Monad m =></code>という型制約を付けてあります。
The underscore after the name is a typical naming convention in Haskell for monadic functions that repeat but do not return a value. until_ takes a predicate that signals when to stop, an action to perform before the test, and a function-returning-an-action to do to the input. Each of the latter two is generalized over ''any'' monad, not just IO. That's why we write their types using the type variable "m", and include the type constraint "Monad m =&gt;".
 
再帰的な関数のみならず、再帰的なアクションも書くことができるのに注目してください。
Note also that we can write recursive actions just as we write recursive functions.
 
今や必要なものが全て調ったので、REPLを簡単に書くことができます。
Now that we have all the machinery in place, we can write our REPL easily:
 
<syntaxhighlight lang="haskell">
runRepl :: IO ()
runRepl = until_ (== "quit") (readPrompt "Lisp&gt;&gt;&gt;>>> ") evalAndPrint
</syntaxhighlight>
 
そして<code>main</code>関数を編集して、一つだけ式を実行するか、REPLに入ってquitと打つまで式の評価を続けるようにします。
And change our main function so it either executes a single expression, or enters the REPL and continues evaluating expressions until we type "quit":
 
<syntaxhighlight lang="haskell">
main :: IO ()
main = do args &lt;<- getArgs
case length args of
0 -&gt;> runRepl
1 -&gt;> evalAndPrint $ args !! 0
otherwise -&gt;> putStrLn "Program takes only 0 or 1 argument"
</syntaxhighlight>
 
コンパイル・実行して試してみましょう。
Compile and run the program, and try it out:
 
<syntaxhighlight lang="text">
debian:/home/jdtang/haskell_tutorial/code#% ghc -package parsec -fglasgow-exts -o lisp [../code/listing7.hs listing7.hs]
% ./lisp
debian:/home/jdtang/haskell_tutorial/code# ./lisp
Lisp&gt;&gt;&gt;>>> (+ 2 3)
5
Lisp&gt;&gt;&gt;>>> (cons this '())
Unrecognized special form: this
Lisp&gt;&gt;&gt;>>> (cons 2 3)
(2 . 3)
Lisp&gt;&gt;&gt;>>> (cons 'this '())
(this)
Lisp&gt;&gt;&gt;>>> quit
%
debian:/home/jdtang/haskell_tutorial/code#
</syntaxhighlight>