読者です 読者をやめる 読者になる 読者になる

F# でIteratee,Enumerator,Enumeratee関数を作成する(の途中経過)

F#

先日、FSharpxのIterateeモジュールで遊んでみた。

FSharpxのIterateeで遊ぶ - pocketberserkerの爆走

ただ、FSharpx.Iterateeで提供されている関数はHaskellのそれに比べて随分と少ないように思える。

なので自作してみようと思ったのだが・・・ちょっと挫折気味なので息抜きに途中経過を書いておく。

ちなみに諸々の関数は下記Haskellライブラリを参考にして記述している。

enumerator: simple, efficient incremental IO for Haskell

動くものも動いていないものも混ぜて記載するので、気をつけてください。
まぁ、明確に動作していると判断できるているのって今のところconsume関数くらいですけど・・・。

前回の記事で実装した関数も含めてのせておきます。ベタ張りをお許しください。

データ構造に依存しない関数

// val enumList : 'a list -> Enumerator<'a,'b>
let enumList xs =
  let rec loop xs = function
  | Continue k when xs |> (List.isEmpty >> not) ->
    let s1, s2 = List.splitAt 1 xs
    let s1 = Seq.head s1
    loop s2 (k (Chunk s1))
  | step -> step
  loop xs

// 本当は'a list list -> Enumerator<'a,'b>にしたいのだがデータ構造の違いによりできていない
// val enumLists : 'a list -> Enumerator<'a,'b>
let rec enumLists l s =
  match l,s with
  | x::xs, Continue k -> enumLists xs (k (Chunk x))
  | _, step -> step

let runLists_ lists iter = run_ (enumLists lists iter)

let runLists lists iter = run (enumLists lists iter)

// val joinI : Iteratee<'a,Iteratee<'b,'c>> -> Iteratee<'a,'c>
let joinI outher =
  let rec check = function
  |  Continue k ->
    (fun s ->
      match s with
      | Continue _ -> failwith "joinI: divergent iteratee"
      | _ -> check s
    ) (k EOF)
  | Done (x, _) -> returnI x
  | Error e -> Error e
  outher >>= check

// 動作しない。現状ではstepは必ずContinueが束縛されてしまうので例外が発生してしまう模様。
// val joinE : Enumerator<'a,Iteratee<'b,'c>> -> Enumeratee<'a,'b,'c> -> Enumerator<'b,'c>
let joinE (enum:Enumerator<'a,Iteratee<'b,'c>>) (enee:Enumeratee<'a,'b,'c>) : Enumerator<'b,'c> =
  fun s -> iteratee {
      let! step = (enumEOF << enum << enee) s
      let result = 
        match step with
        | Error e -> Error e
        | Done (x, _) -> returnI x
        | Continue _ -> failwith "joinE: divergent iteratee"
      return! result
  }

// val checkDoneEx : Stream<'c> -> ((Stream<'a> -> Iteratee<'a,'b>) -> Iteratee<'c,Iteratee<'a,'b>>) -> Enumeratee<'c,'a,'b>
et checkDoneEx extra f = function
| Continue k -> f k
| step -> Done(step, extra)

// val checkDone : ((Stream<'a> -> Iteratee<'a,'b>) -> Iteratee<'c,Iteratee<'a,'b>>) -> Enumeratee<'c,'a,'b>
let checkDone f = checkDoneEx Empty f

ByteStringの実装の違いにより微妙なことになっているような気がしなくもない。

List用関数

// 参照元のテストと同様のテストに通るのでたぶん大丈夫
// val consume : Iteratee<'a list, 'a list>
let consume<'a> =
  let rec step before = function
  | Empty | Chunk ([]: 'a list) -> Continue <| step before
  | Chunk str -> Continue <| step (before @ str)
  | EOF -> Done(before, EOF)
  Continue (step List.empty)

// 一見動いているようには見えるが・・・?
// 8/17修正: *> 関数から >>. 関数に変更
// val isolate : int -> Enumeratee<'a list,'b>
let rec isolate n = function
| step when n <= 0 -> returnI step
| Continue k ->
  let rec loop = function
  | Empty -> Continue loop
  | Chunk [] -> Continue loop
  | Chunk str ->
    if CList.length str < n then k (Chunk str) |> isolate (n - (CList.length str))
    else let s1, s2 = List.splitAt n str in Done(k (Chunk s1), Chunk s2)
  | EOF -> Done(k EOF, EOF)
  Continue loop
| step -> (drop n) >>. (returnI step)

// きちんと消費できない大問題が・・・
// 追記:8/6 上記問題を修正。step関数でのEmptyの動作を間違えていた
// val concatMapM :: ('a -> 'b) -> Enumeratee<'a list, 'b, 'c>
let concatMapM f =
  let rec step k = function
  | EOF -> Done(Continue k, EOF)
  | Chunk xs -> loop k xs
  | Empty -> loop k []
  and loop k = function
  | [] -> Continue (step k)
  | x::xs -> iteratee {
    let! fx = returnI (f x)
    return! checkDoneEx (Chunk xs) (fun x -> loop x xs) (k (Chunk fx)) }
  checkDone (continueI << step)

// val map :: ('a -> 'b) -> Enumeratee<'a list, 'b list, 'c>
let map f = concatMapM (fun x -> [f x])

Binary用関数

ByteString用だけど、基本Listのやつと変わらない。

HaskellのByteStringとFSharpxのByteString実装が異なるので参照元と微妙に異なる部分はある。

// これも動くはず
// val consume : Iteratee<ByteString, ByteString>
let consume =
  let rec step before = function
  | Empty -> Continue <| step before
  | Chunk str when ByteString.isEmpty str -> Continue <| step before
  | Chunk str -> Continue <| step (ByteString.append before str)
  | EOF -> Done(before, EOF)
  Continue (step ByteString.empty)

// 動いているように見せかけてそうでもない・・・?
// 8/17修正: *> 関数から >>. 関数に変更
// val isolate : int -> FSharpx.Enumeratee<FSharpx.ByteString,FSharpx.ByteString,'a>
let rec isolate n = function
| step when n <= 0 -> returnI step
| Continue k ->
  let rec loop = function
  | Empty -> Continue loop
  | Chunk str when ByteString.isEmpty str -> Continue loop
  | Chunk str ->
    let len = ByteString.length str
    if len < n then isolate (n - len) (k (Chunk str))
    else let s1, s2 = ByteString.splitAt n str in Done(k (Chunk s1), (Chunk s2))
  | EOF -> Done(k EOF, EOF)
  Continue loop
| step -> (drop n) >>. (returnI step)

// やはりきちんと消費されない・・・
// 追記:8/6 上記問題を修正。step関数でのEmptyの動作を間違えていた
// val concatMapM :: (byte -> ByteString) -> Enumeratee<ByteString, ByteString, 'a>
let concatMapM (f : byte -> ByteString) =
  let rec step k = function
  | EOF -> Done(Continue k, EOF)
  | Chunk xs -> loop k xs
  | Empty -> loop k ByteString.empty
  and loop k = function
  | xs when ByteString.isEmpty xs -> Continue (step k)
  | xs -> iteratee {
    let x = ByteString.head xs
    let xs = ByteString.tail xs
    let! fx = returnI (f x)
    return! checkDoneEx (Chunk xs) (fun x -> loop x xs) (k (Chunk fx)) }
  checkDone (continueI << step)

// val map :: (byte -> byte) -> Enumeratee<ByteString, ByteString, 'a>
let map f = concatMapM (fun x -> ByteString.singleton (f x))

さてはて、どうしたものやらですね。

とりあえずテストが書きづらいので原因特定が難しいのがネック・・・へるぷみー。