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