16.2 文字列からのデータの読み込み
問 16.1
getc が (char, substring) reader 型を持つことを確かめよ.
解答例 Substring.getcの型は substring -> (char * substring) option である。 一方 (’a, ’b) readerは’b -> (’a * ’b) option であるから、 substring -> (char * substring) optionは、 (char, substring) readerである。
問 16.2
上記の関数を使用して,空白で区切られた数字データの文字列を受け取り,
呼び出されるごとに,そのデータから数字を1つずつ読み出す関数を返す関数
readInt : string -> unit -> int option
を定義せよ.
たとえば
val f = readInt "123 345 abc";
val f = fn : unit -> int option
- f ();
val it = SOME 123 : int option
- f();
val it = SOME 345 : int option
- f();
val it = NONE : int option
のような振る舞いをする.
解答例 教科書にあるdecScanとintScanの定義も含めた コード例を以下に示す。
fun decScan x = Int.scan StringCvt.DEC x;
val intScan = decScan Substring.getc
fun readInt string =
let
val stream = ref (Substring.full string)
in
fn () =>
case intScan (!stream) of
SOME (i, substring) =>
SOME i before stream := substring
| NONE => NONE
end
問 16.3
real 型および bool 型に対してもそれぞれ以下の型を持つ関数が定義 されている.
Real.scan : (char,’a) reader -> (real,’a) reader Bool.scan : (char,’a) reader -> (bool,’a) reader
これらを用いて,ReadInt と同様の読み込み関数 readReal および readBool を定義せよ.
解答例 コード例を以下にしめす。 これら関数はすべて同型であるため、以下の例では、 高階の関数を定義し、それを各型のscan関数に適用している。
fun makeRead scan string =
let
val reader = scan Substring.getc
val stream = ref (Substring.full string)
in
fn () =>
case reader (!stream) of
SOME (i, substring) =>
SOME i before stream := substring
| NONE => NONE
end
val readReal = makeRead Real.scan
val readBool = makeRead Bool.scan
以下はテスト実行例である。
# val f = readReal "1.2 3E10 abc"; val f = fn : unit -> real option # f (); val it = SOME 1.2 : real option # f (); val it = SOME 30000000000.0 : real option # f (); val it = NONE : real option # val f = readBool "true false abc"; val f = fn : unit -> bool option # f (); val it = SOME true : bool option # f (); val it = SOME false : bool option # f (); val it = NONE : bool option
問 16.4
上記の各種の scan 関数は,対応するリーダに適用すればsubstring型以外のストリームデータに対してもそのまま使用することができる. これら種々のストリームに使用することができる以下の型を持つ汎用の 読み込み関数
genericReadInt : (char,’a) reader -> ’a -> unit -> int option
を書け.
解答例
fun genericReadInt baseReader baseData =
let
val stream = ref baseData
val reader = Int.scan StringCvt.DEC baseReader
in
fn () =>
case reader (!stream) of
SOME (i, data) =>
SOME i before stream := data
| NONE => NONE
end
例えば、以下のように使用できる。
# fun readIntFromString string = genericReadInt Substring.getc (Substring.full string); val readIntFromString = fn : string -> unit -> int option # val f = readIntFromString "123 456 abc"; val f = fn : unit -> int option # f (); val it = SOME 123 : int option # f (); val it = SOME 456 : int option # f (); val it = NONE : int option
問 16.5
ファイルの入力ストリームを受け取り, 入力ストリームから数字データを1つずつ読み出す関数を返す関数
readIntFromStream : TextIO.instream -> unit -> int option
をgenericReadInt を使って定義せよ. genericReadInt を使い,空白で区切られた数字列を内容とする ファイル名を受け取り,ファイル内の各数字列を int 型データに変換し, そのリストを返す関数
readIntFromFile : string -> int list
を書け.
解答例 TextIO.StreamIO.input1が、 (char, TextIO.StreamIO.instream) reader を持つので、この関数を使って、以下のように定義できる。
fun readIntFromStream ins =
let
val stream = TextIO.getInstream ins
in
genericReadInt TextIO.StreamIO.input1 stream
end
readIntFromFileの定義例を以下に示す。
fun readIntFromFile file =
let
val ins = TextIO.openIn file
val reader = readIntFromStream ins
fun loop L =
case reader () of
SOME i => loop (L @ [i])
| NONE => L
in
loop nil before TextIO.closeIn ins
end
問 16.6
parseHttp を参考にして, ローカルファイルシステムのファイルパスを表すURLの解析処理関数 parseFile,およびスキーマ指定のない相対アドレス解析処理関数 parseRelative を書き ParseUrl モジュールを完成せよ.
解答例
structure ParseURL =
struct
local
structure SS = Substring
in
exception urlFormat
datatype url
= HTTP of {host : string list, path : string list option,
anchor : string option}
| FILE of {path : string list, anchor : string option}
| RELATIVE of {path : string list, anchor : string option}
fun parseHttp s =
let val s = if SS.isPrefix "://" s then
SS.triml 3 s
else raise urlFormat
fun neq c x = not (x = c)
fun eq c x = c = x
val (host,body) = SS.splitl (neq #"/") s
val domain = map SS.string (SS.tokens (eq #".") host)
val (path,anchor) =
if SS.isEmpty body then (NONE,NONE)
else
let val (p,a) = SS.splitl (neq #"#") body
in (SOME (map SS.string (SS.tokens (eq #"/") p)),
if SS.isEmpty a then NONE
else SOME (SS.string (SS.triml 1 a)))
end
in {host=domain, path=path, anchor=anchor}
end
fun parseRelative s =
let val (path,anchor) =
let val (p,a) = SS.splitl (fn #"#" => false | _ => true) s
in (map SS.string (SS.fields (fn c => c = #"/") p),
if SS.isEmpty a then NONE else SOME (SS.string (SS.triml 1 a)))
end
in {path=path, anchor=anchor}
end
fun parseFile s =
let val s = if SS.isPrefix ":/" s then SS.triml 2 s else raise urlFormat
val (path,anchor) =
let val (p,a) = SS.splitl (fn #"#" => false | _ => true) s
in (map SS.string (SS.tokens (fn c => c = #"/") p),
if SS.isEmpty a then NONE else SOME (SS.string (SS.triml 1 a)))
end
in {path=path,anchor=anchor}
end
fun parseUrl s =
let val s = SS.full s
val (scheme,body) = SS.splitl (fn c => not (c = #":")) s
in
if SS.isEmpty body then
RELATIVE (parseRelative scheme)
else case SS.string scheme of
"http" => HTTP (parseHttp body)
| "file" => FILE (parseFile body)
| _ => raise urlFormat
end
end
end