ストリームではまった
SML(というかML)界ではストリームと言えば 'a -> ('a, 'b) option こんな型が定番なようです。
これは標準ライブラリでも ('a,'b) StringCvt.reader として定義されています。*1
私が公開しているMsgPack-SMLでも、バイトシーケンスを渡すとmsgpackデータが読み出せるreader型を提供しています。
ところで遅延リストのデータを幾つか読み飛ばす関数がありますよね?というか標準関数には遅延リスト無いので当然作るわけです。
で、遅延リストとリーダーって似てるよねーと思って調子こいて書いたのが下の関数。
fun skipn 0 rdr = rdr | skipn (n:int) (rdr:('a,'b) reader) : ('a,'b) reader = fn s => case rdr s of SOME (_,s') => skipn (n-1) rdr s' | NONE => NONE (* ついでに後で使う関数も定義しておく *) fun unfoldReader (f:('a, 'b) reader) (k:'b) = case f k of SOME (x,k') => x::(unfoldReader f k') | NONE => []
で、以下使用例。 implode o unfoldReader は結果を展開して文字列にしてるだけです。
- infixr 1 $; fun f $ a = f a; open Substring; (* skipn 0 = 何も読み飛ばさない *) - implode $ (unfoldReader (skipn 0 getc)) $ full "abcdef"; val it = "abcdef" : string (* skipn 1 = 先頭1文字だけ読み飛ばすはず!? *) - implode $ (unfoldReader (skipn 1 getc)) $ full "abcdef"; val it = "bdf" : string
2つ目の例では skipn 1 "abcdef" を指定したのに "bdf" が得られてるじゃないですか!!
よく考えると unfoldReader には毎回 1文字スキップするリーダー が渡されることになるので、結果として得られるのは 1要素(文字)おきになった入力(文字列) じゃないですかやだー
書いてて恥ずかしくなって来たので(*ノ▽ノ)終わり!
けつろん!
StringCvt.dropl(のようなシグネチャの関数) 使っとけ。
val dropl : (char -> bool) -> (char, 'a) reader -> 'a -> 'a
*1:過去のエントリで解いた練習問題もこれの絡みだったね