ストリームではまった

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:過去のエントリで解いた練習問題もこれの絡みだったね