「48時間でSchemeを書こう/変数と代入」の版間の差分

削除された内容 追加された内容
en:Write Yourself a Scheme in 48 Hours/Adding Variables and Assignment (19:33, 26 February 2009 UTC) をインポート
 
編集の要約なし
1 行
 最終的に、私たちはいいものが手に入ります。それは変数です。変数は評価の結果を保存してくれて、それをあとから参照出来るようにしてくれます。Schemeの中では、変数は新しい値でリセットすることができますし、またプログラムの実行によって、その値は変化します。これは、Haskellにとっては複雑に見えます。というのも、Haskellの実行モデルは値を返す変数の上に構築されており、それらを変えることが出来ないからです。
Finally, we get to the good stuff: variables. A variable lets us save the result of an expression and refer to it later. In Scheme, a variable can also be reset to new values, so that its value changes as the program executes. This presents a complication for Haskell, because the execution model is built upon functions that return values, but never change them.
 
 にも関わらず、Haskellでは同じ状態をシミュレートするいくつかの方法があり、全てモナドに関係します。一番シンプルなのは、おそらくステートモナドです。このモナドは、任意の状態をモナドの中に隠しておいて、舞台裏で周囲に渡します。貴方は、パラメータとして、状態のタイプをこのモナドに記入し、普通はdoブロックの中から、get と putの機能を使って、アクセスすることができます(もし関数がIntegerを返してきて、でもStringの二組のリストに変更するなら、それは<span class="inline_code"> State [(String,String)] Integer</span>という型を持ちます)。貴方は、戻り値と最終状態を含んだペアを返す<span class="inline_code">runState myStateAction initialList</span>を通じて、初期状態を記述します。
Nevertheless, there are several ways to simulate state in Haskell, all involving monads. The simplest is probably the [http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-State.html State monad], which lets you hide arbitrary state within the monad and pass it around behind the scenes. You specify the state type as a parameter to the monad (e.g. if a function returns an integer but modifies a list of string pairs, it would have type <span class="inline_code">State [(String, String)] Integer</span>), and access it via the get and put functions, usually within a do-block. You'd specify the initial state via the <span class="inline_code">runState myStateAction initialList</span>, which returns a pair containing the return value and the final state.
 
 残念なことに、このStateモナドは私たちのためには上手く動いてはくれません。というのも、私たちが格納しておきたいデータの型というのは、かなり複雑だからです。もっともシンプルな環境では、変数名から値にマッピングするのを格納することによって、[(String, LispVal)]でやり通すことができますが、しかし、私たちが関数の呼び出しを扱い始めると、これらのマッピングは、任意の深さの、入れ子になった環境のスタックになるでしょう。そして、私たちがクロージャーを追加したとき、この環境は環境はきっと任意の関数の値に保存され、そしてプログラム全体から返されるかもしれません。事実、私たちが許せなくなるまで、値を保存し、そしてrunStateモナドが完全に潰されるでしょう。
Unfortunately, the state monad doesn't work well for us, because the type of data we need to store is fairly complex. For a simple top-level environment, we could get away with <span class="inline_code">[(String, LispVal)]</span>, storing mappings from variable names to values. However, when we start dealing with function calls, these mappings become a stack of nested environments, arbitrarily deep. And when we add closures, environments might get saved in an arbitrary Function value, and passed around throughout the program. In fact, they might be saved in a variable and passed out of the runState monad entirely, something we're not allowed to do.
 
 代わりに、私たちは''State スレッド''と呼ばれるものを使って、私たちの為に、この集合状態をHaskellに管理させましょう。これは変数を得たり、設定する為の機能を使って、他のプログラム言語と同じように、可変変数を扱うことができるようになります。Stateスレッドには二つの種類があります。the [http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad-ST.html ST monad] は残りのプログラムへ状態を回避させることなしに、単体で実行されるような、ステートフルな経産を作り出すSTモナドです。また、[http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-IORef.html IORef モジュール]は、IO monadの中で、私たちにステートフルな変数を使わせてくれます。私たちの状態を、どんな方法でもいいので、仲介する必要があります(REPLの中で、行間を持続させ、最終的に、言語自身にIO functionを持つとしても)、私たちはIO Refsを扱います。
Instead, we use a feature called ''state threads'', letting Haskell manage the aggregate state for us. This lets us treat mutable variables as we would in any other programming language, using functions to get or set variables. There are two flavors of state threads: the [http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad-ST.html ST monad] creates a stateful computation that can be executed as a unit, without the state escaping to the rest of the program. The [http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-IORef.html IORef module] lets you use stateful variables within the IO monad. Since our state has to be interleaved with IO anyway (it persists between lines in the REPL, and we will eventually have IO functions within the language itself), we'll be using IORefs.
 
We can start out by importing  私たちは[http://www.haskell.org/ghc/docs/6.4/html/libraries/base/Data.IORef.html Data.IORef] and defining a type for our environments:をimportすることから始めましょう。そして、私たちの環境のために、型を決定します。
 
import Data.IORef
type Env = IORef [(String, IORef LispVal)]
 
 これで、変化が多いLispValを文字列として対応した、IORefが持つリストとして、Envを定義できます。リストかそれ自身両方のための、そして個々の値のために、IO Refsが必要です。というのも、プログラムが環境を変化させるためには、二つの方法があるからです。まず一つに、個々の変数の値を変化させ、明らかな変化を、この環境で共有するためのいくつかの機能を、<span class="inline_lisp">set!</span>使えます(Schemeはスコープを入れ子にすることを許可しています。なので、外のスコープにある変数も、全ての内部スコープから見えています)。また、全てのサブシークエントの式から見えるべきである、新しい変数を追加するための<span class="inline_lisp">define</span>も使うことができます。
This declares an Env as an IORef holding a list that maps Strings to mutable LispVals. We need IORefs for both the list itself and for individual values because there are ''two'' ways that the program can mutate the environment. It might use <span class="inline_lisp">set!</span> to change the value of an individual variable, a change visible to any function that shares that environment (Scheme allows nested scopes, so a variable in an outer scope is visible to all inner scopes). Or it might use <span class="inline_lisp">define</span> to add a new variable, which should be visible on all subsequent statements.
 IORefs は ただ、IO monadの内部の中でのみ使うことができます。私たちは、空の環境を作るアクションのための手伝いを欲しがるでしょう。ただ、空のリストである [] は使うことができません。というのは、IORefsにアクセスするときは、必ず順番が決まっていなければならないからで、だから私たちの空っぽな環境の型は、ただの空のEnvの代わりに、IO Envになります。
 
Since IORefs can only be used within the IO monad, we'll want a helper action to create an empty environment. We can't just use the empty list [] because all accesses to IORefs must be sequenced, and so the type of our null environment is IO Env instead of just plain Env:
 
nullEnv :: IO Env
nullEnv = newIORef []
 
 ここから、ことはちょっとだけ複雑になります。というのも同時に''二つ''のモナドを亜つかないといけないからです。覚えておいてください。私たちはまた束縛されていない変数のようなものを扱うためのErrorモナドが同様に必要になります。例外を投げることができる、IOの機能性が必要になるパーツは仲介され、そして私たちはただ全ての例外をキャッチしたり、またはIOモナドからただ値だけを返してもらうということができなくなります。
From here, things get a bit more complicated, because we'll be simultaneously dealing with ''two'' monads. Remember, we also need an Error monad to handle things like unbound variables. The parts that need IO functionality and the parts that may throw exceptions are interleaved, so we can't just catch all the exceptions and return only normal values to the IO monad.
 
多様なモナドが持つ機能を結合させるような、モナド変化として知られるようなメカニズムを、Haskellは供給しています。私たちは、きっとその一つを使うことでしょう。 - [http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aErrorT ErrorT] - これは、IOモナドのトップで、エラーハンドリングの機能性があるレイヤーを与える。私たちの最初のステップは、私たちの結合したモナドのために、別の名前の型を作ってあげます。
Haskell provides a mechanism known as ''monad transformers'' that lets you combine the functionality of multiple monads. We'll be using one of these - [http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aErrorT ErrorT] - which lets us layer error-handling functionality on top of the IO monad. Our first step is create a type synonym for our combined monad:
 
type IOThrowsError = ErrorT LispError IO
 
 ThrowsErrorやIOThrowsErrorのようなものは、実際にタイプのコンストラクタなのです。というのも、私たちは最後の引数や関数の返り値のタイプを辞めたからです。しかし、ErrorTは、まっさらな、古い、おのおのの引数から、一つ以上の引数を取ります。私たちは、自分たちのエラーを扱う機能のレイヤーにある、モナドの型を明示しなければならないのです。なので、LispErrorを投げるIOの行動を含めることができるモナドを作ります。
Like ThrowsError, IOThrowsError is really a type constructor: we've left off the last argument, the return type of the function. However, ErrorT takes one more argument than plain old Either: we have to specify the type of monad that we're layering our error-handling functionality over. We've created a monad that may contain IO actions that throw a LispError.
 
 私たちは、IOThrowsErrorとThrowsErrorのミックスがあります。しかし違う型によるアクションは同じdoブロックの中に含めることが出来ないのです。それが、たとえ本質的には同じ機能を提供していた、としても。Haskellは、低い型(IO)の値を、結合したモナドへ送るためのメカニズムが、既に提供されています。 [http://www.haskell.org/all_about_monads/html/transformers.html#lifting lifting]です。残念なことに、まだ変形していない、上位型の値を豪勢モナドの中へと運ぶ似たようなサポートはないので、私たちは自分たちでそういうものを書く必要があります。
We have a mix of ThrowsError and IOThrowsError functions, but actions of different types cannot be contained within the same do-block, even if they provide essentially the same functionality. Haskell already provides a mechanism - [http://www.haskell.org/all_about_monads/html/transformers.html#lifting lifting] to bring values of the lower type (IO) into the combined monad. Unfortunately, there's no similar support to bring a value of the untransformed upper type into the combined monad, so we need to write it ourselves:
 
liftThrows :: ThrowsError a -&gt; IOThrowsError a
liftThrows (Left err) = throwError err
liftThrows (Right val) = return val
 
This destructures the Either type and either re-throws the error type or returns the ordinary value. Methods in typeclasses resolve based on the type of the expression, so  これは、それぞれの型と、エラー型か、元の値を返すかのどちらかを再び投げ返すという記述です。タイプクラスの中のメソッドは、例外の型を基礎に解決します。だから、[http://www.haskell.org/ghc/docs/latest/html/libraries/mtl/Control-Monad-Error.html#v%3AthrowError throwError] and [http://www.haskell.org/onlinereport/standard-prelude.html#$tMonad return] (members of [http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aMonadError MonadError] and [http://www.haskell.org/onlinereport/standard-prelude.html#$tMonad Monad], respectivelyのメンバーです) take on their は、IOThrowsErrorの定義を受けることが出来ます。 definitions. Incidentally, the type signature provided here is not fully general: if we'd left it off, the compiler would have inferred ついでに、ここで提供されている型のシグネチャは、完全に一般的ではありません。もし、これを私たちが使うなら、コンパイラーはきっと<span class="inline_code">liftThrows :: (MonadError m a) =&gt; Either e a -&gt; m a</span>.と推測するでしょう。
 
 私たちは同様に、全般的に、IOのアクションを返してくれる、トップレベルのIOThrwosErrorのアクションを実行する手助けをしてくれる機能が必要です。IOモナドを回避することは出来ません。というのも、機能がIOに対して振る舞うのは、外の世界へ影響を持つからで、遅延評価された純粋関数では、望まれないことだろうからです。しかし、貴方はErrorを受け取ったり、実行したりすることが可能なのです。
We'll also want a helper function to run the whole top-level IOThrowsError action, returning an IO action. We can't escape from the IO monad, because a function that performs IO has an effect on the outside world, and you don't want that in a lazily-evaluated pure function. But you can run the error computation and catch the errors.
 
runIOThrows :: IOThrowsError String -&gt; IO String
runIOThrows action = runErrorT (trapError action) &gt;&gt;= return . extractValue
 
 これは、私たちが以前に定義した<spna class="inline_code">trapError</span>関数をいくつかのエラーとして取ったり、文字列表記に変換して使えるようにしたもので、従ってrunErrorTから全体の計算へと走らせることができます。結果はextractValueに渡され、そしてIOモナドの中で値として返されます。
This uses our previously-defined <span class="inline_code">trapError</span> function to take any error values and convert them to their string representations, then runs the whole computation via runErrorT. The result is passed into extractValue and returned as a value in the IO monad.
 
 今や私たちは本当に環境を扱う必要が出てきました。[http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_sec_5.2 define]を適切に扱うためになくてはならない、変数が環境によって既に束縛されているかどうかを調べる関数を実装しはじめましょう。
Now we're ready to return to environment handling. We'll start with a function to determine if a given variable is already bound in the environment, necessary for proper handling of [http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_sec_5.2 define]<nowiki>: </nowiki>
 
isBound :: Env -&gt; String -&gt; IO Bool
isBound envRef var = readIORef envRef &gt;&gt;= return . maybe False (const True) . lookup var
 
 まず最初に、readIORefを使って、このIORefから実際の環境の値を取り出します。そして、lookupを使って、私たちが関心を持った、特定の値を探し出し、渡してくれます。lookupはMaybeの値を返し、もしこの値がNothingならばFalseを返し、他の値ならTureを返します(私たちは[http://www.haskell.org/onlinereport/standard-prelude.html#$vconst const]関数を使う必要が出てきます。 というのも、[http://www.haskell.org/onlinereport/standard-prelude.html#$vmaybe maybe]は結果出すか、あるいはただ値ではないかが求められているからです)。最終的に、私たちはIOモナドに取り上げられた値の戻りを使うでしょう。というのも、私たちはただ、値がtrueかfalseかに興味があるのであって、lookupが返した実際のIORefを扱う必要は無いからです。
This first extracts the actual environment value from its IORef via readIORef. Then we pass it to lookup to search for the particular variable we're interested in. lookup returns a Maybe value, so we return False if that value was Nothing and True otherwise (we need to use the [http://www.haskell.org/onlinereport/standard-prelude.html#$vconst const] function because [http://www.haskell.org/onlinereport/standard-prelude.html#$vmaybe maybe] expects a function to perform on the result and not just a value). Finally, we use return to lift that value into the IO monad. Since we're just interested in a true/false value, we don't need to deal with the actual IORef that lookup returns.
 
 次に、私たちは、変数の現在の値を取り出す関数を決定する必要が出てきます。
Next, we'll want to define a function to retrieve the current value of a variable:
 
getVar :: Env -&gt; String -&gt; IOThrowsError LispVal
getVar envRef var = do env &lt;- liftIO $ readIORef envRef
65 ⟶ 58行目:
(lookup var env)
 
Like the previous function, this begins by retrieving the actual environment from the  これは、前出した関数のように、IORef. However, から実際の環境を取り出し始めます。しかし、getVar uses the IOThrowsError monad, because it also needs to do some error handling. As a result, we need to use the はIOthrowsErrorを使います。というのも、これも同様にいくつかのエラーを扱う必要があるからです。結局、私たちは、readIORefを取り出すための関数である、[http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Trans.html#v%3aliftIO liftIO] function to lift the readIORef action into the combined monad. Similarly, when we return the value, we use 関数を使う必要が出てきました。同じように、私たちが値を返すときも、liftIO . を使います。IOThrowsErrorアクションを作り出すためのreadIORef to generate an IOThrowsError action that reads the returned は、IORef. We don't need to use の返り値を返します。しかし、私たちはエラーを投げてもらうために、liftIO to throw an error, however, because を使う必要はなくなりました。というのも、throwError is a defined for the [http://www.haskell.org/ghc/docs/6.4/html/libraries/mtl/Control.Monad.Error.html#t%3aMonadError MonadError typeclass], of which に定義されているからで、ErrorT is an instance.はこのインスタンスです。
 
Now we create a function to set values:
 
 関数に値をセットする関数を作りましょう。
setVar :: Env -&gt; String -&gt; LispVal -&gt; IOThrowsError LispVal
77 ⟶ 69行目:
return value
 
Again, we first read the environment out of its  再び、まずこのIORef and run a の外にある環境を読み込んで、この上にlookup on it. This time, however, we want to change the variable instead of just reading it. The を走らせます。しかし、このとき、私たちはただ読み込む代わりに、値を変えて欲しいと思ってます。[http://www.haskell.org/ghc/docs/6.4/html/libraries/base/Data.IORef.html#v%3awriteIORef writeIORef] action provides a means for this, but takes its arguments in the wrong order アクションは、これらの意味を供給してくれますが、間違った命令(ref -&gt;> valuebalue instead of value -&gt;> ref). So we use the built-in function のいくつかの引数を受けとります。だから、writeIORef周辺の引数を切り替えるためのビルトイン関数[http://www.haskell.org/onlinereport/standard-prelude.html#$vflip flip] to switch the arguments of writeIORef around, and then pass it the value. Finally, we return the value we just set, for convenience.値をそこから受けとります。最後に、便利に、ただセットしたい値を返します。
 
We'll want a function to handle the special behavior of [http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_sec_5.2 define], which sets a variable if already bound or creates a new one if not. Since we've already defined a function to set values, we can use it in the former case:
 
 セットする変数が、既に束縛されているものなのか、あるいは新しく作られたものなのか、そうではないのか、という[http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_sec_5.2 define]の特別な振る舞いを扱う関数も欲しいでしょう。だから、私たちは既に、値をセットする関数を定義したのであり、私たちは前のケースによって、この関数を作ることができます。
defineVar :: Env -&gt; String -&gt; LispVal -&gt; IOThrowsError LispVal
93 ⟶ 84行目:
return value
 
 興味深いのは、後者のケースで、というのも値が束縛されていないからです。これで、(do-notation経路で)新しい値を持つための、新しいIORefを作り、環境における現在の値を読み込み、以前のリストに対して、変数と一致したペア(key,変数)を戻すための新しいリストを書くような、そんなIOアクションを作りました。そして、liftIOを使って、IOThrowsErrorモナドへと向かうことが出来ます。
It's the latter case that's interesting, where the variable is unbound. We create an IO action (via do-notation) that creates a new IORef to hold the new variable, reads the current value of the environment, then writes a new list back to that variable consisting of the new (key, variable) pair added to the front of the list. Then we lift that whole do-block into the IOThrowsError monad with liftIO.
 
There's one more useful environment function: being able to bind a whole bunch of variables at once, as happens when a function is invoked. We might as well build that functionality now, though we won't be using it until the next section:
 
 もっと使える環境関数というのがあります。一度、全体の変数の枝葉を結び合わせることができ、関数が呼び出されたかのように実行される関数です。私たちは、きっとその機能を上手く作ることができます。ですから、次のセクションに移るまでに、それは使わないでおきましょう。
bindVars :: Env -&gt; [(String, LispVal)] -&gt; IO Env
104 ⟶ 94行目:
return (var, ref)
 
 これは、恐らく他の関数よりも複雑なものです。ですから、これは(do-notationの代わりに)モナディックパイプラインと、作業の手伝いをする機能のペアを使います。これは関数を手助けするはじまりとしてベストです。addBindingは変数名と値を取って、(name,value)のペアを返します。extendEnvは(String,IORef LispVal)のペアのリストを作るために束縛した、それぞれのメンバー([http://www.haskell.org/onlinereport/standard-prelude.html#$vmapM mapM])から、addBindingを呼び出し、そして現在の環境の最後に、これを追加します(++ env)。最終的に、この関数全体は、IORefの外から、存在している環境を読み込み始め、そしてextendEnvに結果を投げ、さらに存在している環境と共に新しいIORefを返す働きを行うパイプラインの中で、関数に作用します。
This is perhaps more complicated than the other functions, since it uses a monadic pipeline (rather than do-notation) and a pair of helper functions to do the work. It's best to start with the helper functions. addBinding takes a variable name and value, creates an IORef to hold the new variable , and then returns the (name, value) pair. extendEnv calls addBinding on each member of bindings ([http://www.haskell.org/onlinereport/standard-prelude.html#$vmapM mapM]) to create a list of (String, IORef LispVal) pairs, and then appends the current environment to the end of that <span class="inline_code">(++ env)</span>. Finally, the whole function wires these functions up in a pipeline, starting by reading the existing environment out of its IORef, then passing the result to extendEnv, then returning a new IORef with the extended environment.
 
Now that we have all our environment functions, we need to start using them in the evaluator. Since  今、私たちは、環境関数全てを持ったので、パラメーターと同じエバリュエーターを作り始める必要が出てきました。Haskell has no global variables, we'll have to thread the environment through the evaluator as a parameter. While we're at it, we might as well add the はグローバル変数というのを持ちませんから、私たちは、パラメーターと同じようなエバリューターを扱う必要が出てきました。そこで、私たちはきっと[http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-7.html#%_sec_4.1.6 set!] and [http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8.html#%_sec_5.2 define] special forms.の特別なかたちを上手く追加することができるでしょう。
 
127 ⟶ 117行目:
eval <span class="changed_code">env</span> badForm = throwError $ BadSpecialForm "Unrecognized special form" badForm
 
 全体のインタラクティヴなセッションを通じて、単独の環境はスレッドを得ることができるので、私たちは環境を取るための、幾つかのIO関数を改造してやる必要が出てきます。
Since a single environment gets threaded through a whole interactive session, we need to change a few of our IO functions to take an environment.
 
evalAndPrint :: Env -&gt; String -&gt; IO ()
136 ⟶ 125行目:
evalString <span class="changed_code">env</span> expr = <span class="changed_code">runIOThrows $ </span>liftM show $ (<span class="changed_code">liftThrows $ </span>readExpr expr) &gt;&gt;= eval <span class="changed_code">env</span>
 
 私たちはevalStringの中にあるrunIOThrowsが必要になります。というのも、ThrowsErrorからIOThrowsErrorに変化させることができるからです。同様に、私たちはreadExprをIOThrowsErrorモナドに運ぶためのliftThrowsが必要になります。
We need the runIOThrows in evalString because the type of the monad has changed from ThrowsError to IOThrowsError. Similarly, we need a liftThrows to bring readExpr into the IOThrowsError monad.
 
Next, we initialize the environment with a null variable before starting the program:
 
 次に、私たちは、プログラムが始まる前に、null値によって環境を初期化しましょう。
<span class="changed_code">runOne :: String -&gt; IO ()
147 ⟶ 135行目:
runRepl = <span class="changed_code">nullEnv &gt;&gt;= </span>until_ (== "quit") (readPrompt "Lisp&gt;&gt;&gt; ") <span class="changed_code">. </span>evalAndPrint
 
 私たちは、単体表現のケースを扱うための手助けをしてくれる新しい関数、runOneを作りました。というのは、今やただevalAndPrintをただ走らせるよりも複雑になっているからです。runReplの書き換えは、ちょっとだけとらえがたいものです。evalAndPrintの前に、オペレーターを構成する関数を如何に追加したか、思い出してください。今や、evalAndPrintは追加されたEnvパラメーターであるnullEnvを取るようになりました。この関数の構成は、アクションと同等だった、まっさらな古いevalAndPrintを取るかわりにnullEnvと、モナディックパイプラインが流れ出るならなんでも、まず最初に適応して、この場合のnullEnvの結果を伝えてくれます。従って、入力されたそれぞれのラインを適応するための実際の関数は(evalAndPrint env)で、正しくこれが、私たちが求めていたものです。
We've created an additional helper function runOne to handle the single-expression case, since it's now somewhat more involved than just running evalAndPrint. The changes to runRepl are a bit more subtle: notice how we added a function composition operator before evalAndPrint. That's because evalAndPrint now takes an additional Env parameter, fed from nullEnv. The function composition tells until_ that instead of taking plain old evalAndPrint as an action, it ought to apply it first to whatever's coming down the monadic pipeline, in this case the result of nullEnv. Thus, the actual function that gets applied to each line of input is (evalAndPrint env), just as we want it.
 
Finally, we need to change our main function to call runOne instead of evaluating evalAndPrint directly:
 
 最後に、私たちはevalAndPrintを直接評価する代わりに、runOneを呼び出すように、main関数を変えてやる必要があります。
main :: IO ()
159 ⟶ 146行目:
otherwise -&gt; putStrLn "Program takes only 0 or 1 argument"
 
 そして、コンパイルしたあとに、プログラムをテストしてみましょう。
And we can compile and test our program:
 
debian:/home/jdtang/haskell_tutorial/code# ghc -package parsec -o lisp [../code/listing8.hs listing8.hs]<nowiki>