「48時間でSchemeを書こう/IOプリミティブの作成」の版間の差分

削除された内容 追加された内容
M編集の要約なし
 
1 行
 私たちのSchemeは外部の世界と未だに対話することが出来ません。もし何かしらのI/Oの機能があればとてもよいでしょう。同様に、私たちがインタプリターを起動する度に、本当に関数の中で長ったらしい記述をするより、コードが書いてあるファイルを読み込めて、それを評価できたらよいでしょう。<!-- TODO: syntaxhighlight -->
 
 必要になる、まず最初のことは、LispVal のための新しいコンストラクタです。PrimitiveFuncsはIOモナドを含まない特別な型を持っています。ですので、他のIOを用いることができません。私たちは、IOを用いることができる専用のコンストラクタが必要です。
<syntaxhighlight lang="haskell">
| IOFunc ([LispVal] -&gt;> IOThrowsError LispVal)
</syntaxhighlight>
 
 この中で、私たちは、同様に [http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.6.1 port] という、Schemeのデータタイプの為のコンストラクタを定義します。私たちのIO関数の多くは、これらの一つを取って、読み書きされます。
<syntaxhighlight lang="haskell">
| Port Handle
</syntaxhighlight>
 
 [http://www.haskell.org/onlinereport/io.html#sect21 Handle] は、基本的な portの Haskell notionで、openFIleおよび類似のIOアクションによって返され、貴方はそれへ読み書きを行うことが出来ます。
 
 完全を期すために、私たちは新しいデータ型のために、showValメソッドを定義する必要があります。
<syntaxhighlight lang="haskell">
showVal (Port _) = "&lt;<IO port&gt;>"
showVal (IOFunc _) = "&lt;<IO primitive&gt;>"
</syntaxhighlight>
 
 
 これは、きっと、REPL機能を適切にし、portを返す関数を使うときに、クラッシュしないようにしてくれるでしょう。
 
 私たちは同様に applyを修正し、IOFunc が扱えるようにします。
<syntaxhighlight lang="haskell">
 
apply (IOFunc func) args = func args
</syntaxhighlight>
 
 [http://www.schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-9.html#%_sec_6.6.4 load]をサポートするため、私たちのパーサーをマイナーチェンジする必要があります。普通、Schemeファイルは複数の定義を含んでおり、パーサーにも、幾つかの評価をサポートしたり、あるいは空白によって分割することを追加しないといけません。そして、同様に扱い時のエラーも必要になります。私たちは、実際のパーサーがパラメーターを取れるような基礎的なreadExprを作ることによって、殆どの存在する基盤を再利用することが出来ます。
<syntaxhighlight lang="haskell" highlight="1-2,6-7">
<span class="changed_code">readOrThrow :: Parser a -&gt;</span> String -&gt;> ThrowsError a
<span class="changed_code">readOrThrow parser</span> input = case parse <span class="changed_code">parser</span> "lisp" input of
Left err -&gt; throwError $ Parser err
Right val -&gt; return val
 
readExprListreadExpr = readOrThrow (endBy parseExpr spaces)
<span class="changed_code">
readExprreadExprList = readOrThrow (endBy parseExpr spaces)
</syntaxhighlight>
readExprList = readOrThrow (endBy parseExpr spaces)
</span>
 
 再び、readExpr及びreadExprListの両方を、新しく命名されたreadOrThrowが特別化されたものとして考えます。私たちは、自分たちのREPLを単純な評価として読み込みます。私たちは、readExprListを、loadの中から、プログラムの中から読み込むために使うでしょう。
 次に、ただ存在するprimitiveリストみたいに構成された、IO primitvesの新しいリストが必要になるでしょう。
 
<syntaxhighlight lang="haskell">
ioPrimitives :: [(String, [LispVal] -&gt;> IOThrowsError LispVal)]
ioPrimitives = [("apply", applyProc),
("open-input-file", makePort ReadMode),
48 ⟶ 49行目:
("read-contents", readContents),
("read-all", readAll)]
</syntaxhighlight>
 
 ここでは、違いは型のシグネチャの違いです。残念なことに、私たちは、存在するprimitiveのリストを使うことが出来ません。というのも、リストは型の違いによる要素に含めることができないからです。私たちは同様に、primitiveBindingsの定義を新しいprimitivesに新しく追加するよう編集する必要があります。
<syntaxhighlight lang="haskell">
primitiveBindings :: IO Env
primitiveBindings = nullEnv &gt;&gt;>>= (flip bindVars $ <span class="changed_code">map (makeFunc IOFunc) ioPrimitives
++ </span>map (makeFunc PrimitiveFunc) primitives)
where makeFunc <span class="changed_code">constructor</span> (var, func) = (var, <span class="changed_code">constructor</span> func)
</syntaxhighlight>
 
 私たちは、コンストラクターの引数を取るためにmakeFuncを作り、そして今や過去のまっさらなprimitivesにioPrimitiveのリストを追加してmakeFuncを呼び出します。
 
 今、私たちは実際の関数を定義し始めています。applyProcはapplyのまわりを薄く包み込み、解きほぐされた引数のリストから、applyが期待しているものへと反応します。
<syntaxhighlight lang="haskell">
applyProc :: [LispVal] -&gt;> IOThrowsError LispVal
applyProc [func, List args] = apply func args
applyProc (func : args) = apply func args
</syntaxhighlight>
 
 makeProtはHaskellの関数であるopenFileを包み込み、右の型にコンバートし、そしてPortコンストラクタの中で返り値をラップします。これはIOMode、open-input-fileの為の、ReadMode及びWriteModeに、部分的に適応します。
<syntaxhighlight lang="haskell">
makePort :: IOMode -&gt;> [LispVal] -&gt;> IOThrowsError LispVal
makePort mode [String filename] = liftM Port $ liftIO $ openFile filename mode
</syntaxhighlight>
 
 ClosePortは同様に、Haskellの同等の手続きと、同じhCloseをラップします。
<syntaxhighlight lang="haskell">
closePort :: [LispVal] -&gt;> IOThrowsError LispVal
closePort [Port port] = liftIO $ hClose port &gt;&gt;>> (return $ Bool True)
closePort _ = return $ Bool False
</syntaxhighlight>
 Schemeのために、LispValに適切に切り替えるために、(ビルドインされたreadと、名前がコンフリクトしないように避けられた)readProcは、hGetLineをラップし、そしてparseExprの結果に送ります。
 
<syntaxhighlight lang="haskell">
 Schemeのために、LispValに適切に切り替えるために、(ビルドインされたreadと、名前がコンフリクトしないように避けられた)readProcは、hGetLineをラップし、そしてparseExprの結果に送ります。
readProc :: [LispVal] -&gt; IOThrowsError LispVal
readProc :: [LispVal] -&gt;= IOThrowsErrorreadProc LispVal[Port stdin]
readProc [Port port] = (liftIO $ hGetLine port) &gt;&gt;= liftThrows . readExpr
readProc [] = readProc [Port stdin]
</syntaxhighlight>
readProc [Port port] = (liftIO $ hGetLine port) &gt;&gt;= liftThrows . readExpr
 
 "hGetLine port"が如何に <span class="inline_code">{{code|IO String</span>}}の型となるのか、また如何にreadExprが{{code|<span class="inline_code"nowiki>String -&gt;> ThrowsError LispVal</spannowiki>}}の型になるのかに気をつけてください。そして、両方とも、(liftIOやliftThrowsや、おのおのと共に)、IOThrowsErrorモナドに変換する必要があります。彼らができることは、モナディックバインドオペレーターを使って彼らを橋渡しすることです。
 
 writeProcはLispValをストリングにして、特別なポートにコンバートして書き直すということです。
121 ⟶ 128行目:
 
 そして、新しいrunOne関数によって、mainを変化させましょう。私たちはもはやコマンドライン引数の間違った値を扱うために、三つ目の節が必要となりましたので、私たちはif文を簡単に、ここに追加しましょう。
<syntaxhighlight lang="haskell">
main :: IO ()
main = do args &lt;>- getArgs
if null args then runRepl else runOne $ args
</syntaxhighlight>
 
[[カテゴリ:48時間でSchemeを書こう]]