Stream と cat コマンド [OCaml]
「ふつうの Haskell」を買ったので OCaml の実用レッスンをするのにちょうどよさそうな問題をこの中から探して OCaml に書き換えて実装してみようと思う。
まずは cat コマンド。といっても標準入力から読んで標準出力へ出すだけの簡易版。Ocaml だとこんな感じだろうか。
let _ =
let stdinstr = Stream.of_channel stdin in
Stream.iter print_char stdinstr
;;
Stream.of_channel は入力チャネルからストリームを作って文字の列のストリームを作る。
stdin から作ったその入力ストリームの1文字1文字に対して print_char を適用するので標準入力から標準出力へ1文字ずつ流すということになるわけだ。
実際のプログラミングでは行単位で処理を行うことが多いだろう。行単位のストリームを作るビルダーは用意されていないので Stream.from に自作関数を与えて作る。
let stdinstr =
Stream.from (
fun i ->
try
Some (input_line stdin)
with End_of_file ->None
)
in
Stream.iter print_endline stdinstr;;
これだと標準入力ストリームの1行ずつ文字に対して print_endline を適用することになる。
行ベースの処理が可能になったので cat コマンドを拡張して、行番号を付すようにしてみよう。
やり方のひとつには以下のように Stream が作られる時点で行番号をつけてしまうのがある。
let numbered_stdinstr =
Stream.from (
fun i ->
try
Some (Printf.sprintf "%05d: %s" (i + 1) (input_line stdin))
with End_of_file -> None
)
in
Stream.iter print_endline numbered_stdinstr;;
Stream.from に渡す関数はストリームの現在のカウントを引数にして呼び出されるのでこの目的にかなっている。
だがストリーム自体にすでに行番号がついているということが何か気持ち悪いかもしれない。
同じことが「ふつうの Haskell」では以下のようなコードで表現されている。
main = do cs <- getContents
putStr numbering cs
numbering :: String -> String
numbering cs = unlines $ map format $ zipLineNumber $ lines
zipLIneNumber :: [String] -> [(Int, String)]
zipLineNumber xs = zip [1...] xs
後略したが format 関数は整数と文字列のタプルを引数にとって書式化された文字列(行番号付)を返す関数だ。
ここでのポイントは zip を使って「自然数のリストと文字列のリスト」―いずれも無限リスト―を「自然数と文字列のタプルのリスト」に変換して、そのタプルを format 関数に渡しているところだ。
無限リストが普通にリストで書けるのはすべてが遅延評価される Haskell の特徴なのでOCaml でこれを真似するとしたらやっぱりストリームを使うことになるだろう。
let stdinstr =
Stream.from (
fun i ->
try
Some (input_line stdin)
with End_of_file -> None
)
in
let numstr =
Stream.from (fun i -> Some (i + 1))
in
let combine astr bstr =
Stream.from (
fun i ->
try
Some ((Stream.next astr), (Stream.next bstr))
with Stream.Failure -> None
)
in
let print_numbering (num, line) =
Printf.printf "%05d: %s\n" num line
in
Stream.iter print_numbering (combine numstr stdinstr);;
あんまりうまくないかな?
コメント 0