Skip to content

Instantly share code, notes, and snippets.

@lyricallogical
Created February 15, 2012 10:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lyricallogical/1834901 to your computer and use it in GitHub Desktop.
Save lyricallogical/1834901 to your computer and use it in GitHub Desktop.
rpscala69
!SLIDE
sclaz7 の scalaz.Iteratee の紹介
================================
!SLIDE
発端
----
> @halcat0x15a: @lyrical_logical 7のiterateeが別物になってて読めないです・・・・ 11:38 PM - 14 Feb 12
[tweet](https://twitter.com/#!/halcat0x15a/status/169430205029552129)
!SLIDE
そういうわけなので
------------------
読みました
!SLIDE
Iteratee とは
-------------
* Haskell の Lazy IO に替わる新しい入力処理として提案された手法
* 二年弱前に Oleg 先生(エライ)が提案してから、様々なライブラリが登場
* 正に Iteratee 戦国時代…!
* 現在進行形で改良が続けられている
!SLIDE
既存の入力処理の問題点
----------------------
* Haskell の Lazy IO がオワコン
* 基本的に副作用や例外は遅延評価と相性が悪い
* 入出力は副作用や例外を伴う
* ヤバイ!
* Scala には関係ない
* composable ではない
* 入力の供給と処理が強く結びついている
* etc...
!SLIDE
入力処理の現実
--------------
* 入力とは、一度に供給されるものではない
* man read(2)
* ファイル入出力、ネットワークストリーム、etc...
* 効率のためには、少しずつ供給される入力を、少しずつ処理しなければならない
* バッファリング、暗号、圧縮、エンコーディング等の前処理もある
* 大変だ!
!SLIDE
Design Goal
-----------
* 速度、メモリ使用量に優れ
* 安全であり
* 記述力が高い入力処理を実現する
!SLIDE
そんなことできるの?
-------------------
どうもできるらしい
!SLIDE
Scala 界隈における Iteratee の現状
----------------------------------
* Iteratee を導入しているライブラリがいくつか
* scalaz
* scala-query
* Akka
* Play 2.0
* scalaz7(new!)
* 他にもあったら教えてください
!SLIDE
Scala と Iteratee の相性
------------------------
* そもそも Haskell のためのもの。Scala との相性は…?
* 性能測定などしてないので分かりません
* 素朴な移植では恐らく駄目
* Scala なりの設計が多分あるはず
* scalaz7 はどうかな?
* これ以上は考えないことにしよう
!SLIDE
というわけで
------------
本題に入ります
!SLIDE
アジェンダみたいな何か
----------------------
* 何かイメージ
* 入力とは
* 入力処理とは
* scalaz.Iteratee
* scalaz.Iteratee.Input
* scalaz.Iteratee.StepT
* scalaz.Iteratee.IterateeT
* scalaz.Iteratee.EnumeratorT
!SLIDE
入力
----
* そもそも入力とは
* 何らかの値(の列)
!SLIDE
イメージせよ!
-------------
![Image1](http://desmond.yfrog.com/Himg875/scaled.php?tn=0&server=875&filename=4ecxz.jpg&xsize=640&ysize=640 "Image1")
Hello
!SLIDE
入力の処理
----------
* 入力を取り、何らかの結果を返す
!SLIDE
イメージせよ!
-------------
![Image2](http://desmond.yfrog.com/Himg877/scaled.php?tn=0&server=877&filename=93qcf.jpg&xsize=640&ysize=640 "Image2")
食べるぜー
!SLIDE
入力の処理 問題その 1
---------------------
* 入力を取り、何らかの結果を返す
* 入力は少しずつ供給される
* 処理に必要な分の入力が供給されなかったら?
!SLIDE
イメージせよ!
-------------
![Image3](http://desmond.yfrog.com/Himg640/scaled.php?tn=0&server=640&filename=oy5oi.jpg&xsize=640&ysize=640 "Image3")
もっと食べたいよぉ
!SLIDE
入力の処理 問題その 1 解決
--------------------------
* 入力を取り、何らかの結果を返す
* 入力は少しずつ供給される
* 足りなかったら、もっと入力を要求しよう!
!SLIDE
イメージせよ!
-------------
![Image4](http://desmond.yfrog.com/Himg532/scaled.php?tn=0&server=532&filename=bg2hk.jpg&xsize=640&ysize=640 "Image4")
おかわりよこせ
!SLIDE
入力 問題その 1
---------------------
* そもそも入力とは
* 何らかの値(の列)
* 一時的になくなってしまうことも
* しばらく待てばまた値を供給できる
!SLIDE
イメージせよ!
-------------
![Image5](http://desmond.yfrog.com/Himg618/scaled.php?tn=0&server=618&filename=d5bmv.jpg&xsize=640&ysize=640 "Image5")
エンプティ
!SLIDE
入力 問題その 1 解決
--------------------------
* そもそも入力とは
* 何らかの値(の列)
* 一時的になくなってしまうことも
* 空を表現可能にしよう
!SLIDE
イメージせよ!
-------------
![Image6](http://desmond.yfrog.com/Himg857/scaled.php?tn=0&server=857&filename=rp1nj.jpg&xsize=640&ysize=640 "Image6")
また食べられるぞ
!SLIDE
入力 問題その 2
---------------------
* そもそも入力とは
* 何らかの値(の列)
* 一時的になくなってしまうことも
* 空を表現可能にしよう
* いつかはなくなってしまうことも
* もう値を供給できない
!SLIDE
イメージせよ!
-------------
![Image7](http://desmond.yfrog.com/Himg614/scaled.php?tn=0&server=614&filename=bpkter.jpg&xsize=640&ysize=640 "Image7")
売り切れです
!SLIDE
入力 問題その 2 解決
--------------------------
* そもそも入力とは
* 何らかの値(の列)
* 一時的になくなってしまうことも
* 空を表現可能にしよう
* いつかはなくなってしまうことも
* 終端を表現可能にしよう
!SLIDE
イメージせよ!
-------------
![Image8](http://desmond.yfrog.com/Himg877/scaled.php?tn=0&server=877&filename=znyvc.jpg&xsize=640&ysize=640 "Image8")
Hello!
!SLIDE
入力の処理 問題その 2
---------------------
* 入力を取り、何らかの結果を返す
* 入力は少しずつ供給される
* 足りなかったら、もっと入力を要求しよう!
* 時には予期せぬ入力も
!SLIDE
イメージせよ!
-------------
![Image9](http://desmond.yfrog.com/Himg737/scaled.php?tn=0&server=737&filename=13frt.jpg&xsize=640&ysize=640 "Image9")
Bad Apple
!SLIDE
入力の処理 問題その 2 解決
--------------------------
* 入力を取り、何らかの結果を返す
* 入力は少しずつ供給される
* 足りなかったら、もっと入力を要求しよう!
* 時には予期せぬ入力も
* エラーを返せるようにしよう
!SLIDE
イメージせよ!
-------------
![Image10](http://desmond.yfrog.com/Himg740/scaled.php?tn=0&server=740&filename=bn3lku.jpg&xsize=640&ysize=640 "Image10")
変なもの食べさせないで!
!SLIDE
ここまでまとめ
--------------
* 入力とは
* 何らかの値(の列)か
* 空か
* 終端である
* 入力処理とは、入力を取り
* 入力が十分なら何らかの結果を返し
* 足りなければ更に要求し
* 予期せぬ入力にはエラーを返すもの
!SLIDE
説明し忘れてたことがあった
--------------------------
* ぐだぐだですいません…
!SLIDE
入力の供給
----------
* 入力処理を行う人は、自分では入力を取ってこれない
* 誰かが供給してあげる必要がある
* 「おなかいっぱい」か「こんなの食べられない」というまで食べさせ続ける
!SLIDE
scalaz.Iteratee
-------------
* scalaz7 の Iteratee 実装 
* 主な登場人物
* scalaz.Iteratee.Input
* scalaz.Iteratee.StepT
* scalaz.Iteratee.IterateeT
* scalaz.Iteratee.EnumeratorT
!SLIDE
scalaz.Iteratee
-------------
* scalaz7 の Iteratee 実装 
* 主な登場人物
* scalaz.Iteratee.Input
* 入力に相当
* scalaz.Iteratee.StepT
* 入力処理を行う人に相当
* scalaz.Iteratee.IterateeT
* 「あるコンテキストで」入力処理を行う人に相当
* scalaz.Iteratee.EnumeratorT
* 入力を供給する人に相当
!SLIDE
コードを読み始める前に
----------------------
* scala で「A もしくは B もしくは C」のような型を定義するには?
!SLIDE
普通はこう
----------
```scala
sealed trait Nanika
case class A(a : TA) extends Nanika
case class B(b : TB) extends Nanika
case class C(c : TC) extends Nanika
```
sealed + case class/object で ADT
!SLIDE
コードを読み始める前に
----------------------
* scala で「A もしくは B もしくは C」のような型を定義するには?
* その型の値を処理するには?
!SLIDE
普通はこう
----------
```scala
nanika match {
case A(a) => ...
case B(a) => ...
case C(c) => ...
}
```
パターンマッチ
!SLIDE
ところがどっこい
----------------
* scala で「A もしくは B もしくは C」のような型を定義するには?
* その型の値を処理するには?
* これらを scalaz では?
!SLIDE
こうなる
--------
```scala
sealed trait Nanika {
def fold[Z](caseA : TA => Z, caseB : TB => Z, caseC : TC => Z)
}
```
ん???
!SLIDE
どうしてこんなことに
--------------------
* 以下は全て推測ですが…
* match は isInstanceOf とか使うので遅い
* 分岐のためのメソッドを用意しておけばよい
* case class/object と遅延評価は相性が悪い
* パラメタに lazy val も by name parameter も使えない
!SLIDE
兎に角
------
* scalaz ではそんな感じで書かれてる
* 読みなれてないと辛い 
* 脳内で ADT + パターンマッチに変換しましょう
!SLIDE
というわけで
------------
* 読み始めます
!SLIDE
scalaz.Iteratee.Input
---------------------
```scala
sealed trait Input[E] {
def fold[Z](empty: => Z, el: (=> E) => Z, eof: => Z): Z
}
```
* E 型の値(の列)の入力を表すトレイト
* 早速例の fold です
* 空と、何らかの入力と、終端に対するパターンマッチの実装だと思いましょう
!SLIDE
Input の値達 
----------------------------
```scala
object Empty {
def apply[E]: Input[E] = new Input[E] {
def fold[Z](empty: => Z, el: (=> E) => Z, eof: => Z) = empty
}
}
object Element {
def apply[E](e: => E): Input[E] = new Input[E] {
def fold[Z](empty: => Z, el: (=> E) => Z, eof: => Z) = el(e)
}
}
object Eof {
def apply[E]: Input[E] = new Input[E] {
def fold[Z](empty: => Z, el: (=> E) => Z, eof: => Z) = eof
}
}
```
みたまま
!SLIDE
Input のメソッド例
--------------------
```scala
def isEof: Boolean = fold(false, // 空の場合
_ => false, // 値がある場合
true) // 終端の場合
```
!SLIDE
scalaz.Iteratee.StepT
---------------------
```scala
sealed trait StepT[X, E, F[_], A] {
def fold[Z](
cont: (Input[E] => IterateeT[X, E, F, A]) => Z
, done: (=> A, => Input[E]) => Z
, err: (=> X) => Z
): Z
}
```
* X はエラー型
* E は Input に渡す、入力の値型
* F はコンテキスト
* コンテキストとは何かだって?難しいことは後です!
* A は処理の結果の型
!SLIDE
StepT の値達
------------
* 重要なのは IterateeT のほう
* Input と似たようなものなので飛ばします
!SLIDE
scalaz.Iteratee.IterateeT
-------------------------
```scala
sealed trait IterateeT[X, E, F[_], A] {
def value: F[StepT[X, E, F, A]]
def foldT[Z](
cont: (Input[E] => IterateeT[X, E, F, A]) => F[Z]
, done: (=> A, => Input[E]) => F[Z]
, err: (=> X) => F[Z]
)(implicit F: Bind[F]): F[Z] =
F.bind(value)((s: StepT[X, E, F, A]) => s(cont, done, err))
}
```
* 入力を食べたりおなかいっぱいになったり駄々をこねたりする人 
* 型パラメタは StepT と同じ
* あるコンテキスト F 上で StepT を扱うのが IterateeT
* 各パラメタの意味は次頁
* value て名前は酷くないか…step でいいじゃん
!SLIDE
IterateeT のメソッド例
------------------------
* ここでコードを紹介できるくらい実装が簡単なものがなかった!
* run, apply メソッド
* EOF を供給し、処理を終了させ、結果を取り出す
* あとエラーハンドリングもする。
* runOrZero メソッド
* F[A] が Monoid である場合にのみ利用できる。エラーの時は単位元を返す run
* map, flatMap メソッド
* おなじみのメソッド。でも実装は少しだけややこしい。
* 基本的に処理が終了するまで引数に渡された関数の適用を遅延する、といえばよいか
* contramap メソッド
* 後で読む(これはスライドだぞ!何を言っているんだ!)
* etc...
!SLIDE
IterateeT の値達 コンストラクタ編
---------------------------------
```scala
def cont[X, E, F[_] : Pointed, A](c: Input[E] => IterateeT[X, E, F, A]): IterateeT[X, E, F, A] =
StepT.scont(c).pointI
def done[X, E, F[_] : Pointed, A](d: => A, r: => Input[E]): IterateeT[X, E, F, A] =
StepT.sdone(d, r).pointI
def err[X, E, F[_] : Pointed, A](e: => X): IterateeT[X, E, F, A] =
StepT.serr(e).pointI
```
* pointI は StepT を IterateeT に変換するメソッド
* cont
* まだまだ食べるよの人
* 処理が残ってる状態
* done
* もうおなかいっぱいだよの人
* 処理が終了している状態
* 食べ切れなかった残りの入力を持っていることに注意
* err
* こんなの食べられないよの人
* エラーが発生した状態
!SLIDE
IterateeT の値達 プリミティブ編
-------------------------------
```scala
def head[X, E, F[_] : Pointed]: IterateeT[X, E, F, Option[E]] = {
def step(s: Input[E]): IterateeT[X, E, F, Option[E]] =
s(empty = cont(step)
, el = e => done(Some(e), emptyInput[E])
, eof = done(None, eofInput[E])
)
cont(step)
}
```
* 先頭一文字を返す Iteratee
* 関数 step を定義して cont(step) を返すのは頻出パターン
* 入力が空だった場合に、残りの処理として自分自身を返すため
!SLIDE
IterateeT の値達 合成編
-----------------------
```scala
def head2[X, F[_] : Monad] : Iteratee[X, Char, F, Option[String]] =
for {
optc0 <- head[X, Char, F]
optc1 <- head[X, Char, F]
} yield for {
c0 <- opt0
c1 <- opt1
} yield "" + c0 + c1
```
* 先頭二文字を文字列として返す Iteratee
* コンパイルしてないので型エラーでたらごめんなさい…
* composable な入力処理が実現できている
* 型パラメタが鬱陶しいけど…
* F 高階型パラメタの Monad の制約が flatMap のために必要
!SLIDE
いい加減コンテキストってなんなんですか
--------------------------------------
* ぶっちゃけモナドです。以下のようなものがあります
* IO モナド -> I/O を伴うコンテキスト
* Option モナド -> 失敗を伴うコンテキスト
* List モナド -> 非決定性を伴うコンテキスト
* Id モナド -> 特に何もない、普通のコンテキスト
!SLIDE
つまりどういうこと?
--------------------
* IterateeT[X, E, IO, A] は入出力を伴う StepT
* 入力を処理しつつ出力をしたいような場合はこっち
* ロギングやトレースのことを考えると常にこっちになってしまう気はする
* IterateeT[X, E, Id, A] は単なる StepT
* type Iteratee[X, E, A] = Iteratee[X, E, Id, A] という type alias が用意されています
!SLIDE
なんでこんなことするの?
------------------------
* その入力処理が、I/O を伴うかどうかが型で区別できる
* I/O は副作用を引き起こすので、区別できると嬉しい、こともある
* ぶっちゃけ Haskell のマネ…!
* SIDE-EFFECT IS POWER!!!!
!SLIDE
scalaz.Iteratee.EnumeratorT
---------------------------
```scala
trait EnumeratorT[X, E, F[_]] {
def apply[A]: StepT[X, E, F, A] => IterateeT[X, E, F, A]
}
```
* StepT に入力を食べさせてあげる人
* 食べさせたあとはコンテキストの中へ
!SLIDE
EnumeratorT のメソッド例
------------------------
* ここでコードを紹介できるくらい実装が簡単なものがなかった!
* map, flatMap メソッド
* おなじみのメソッド。でも実装は少しだけややこしい。
* EnumeratorT は IterateeT => IterateeT のようなものだから簡単な気がする
* と思ったらそんなことはなかったぜ
* 複数の EnumeratorT を連結するメソッド
* があるはずなのに見つけられない!どこかにあるはず
!SLIDE
EnumeratorT の値達 プリミティブ編 その 1
----------------------------------------
```scala
def empty[X, E, F[_]: Pointed]: EnumeratorT[X, E, F] =
new EnumeratorT[X, E, F] {
def apply[A] = _.pointI
}
```
* 空の入力を供給する Enumerator
* と思ったら実装これ emptyInput 供給してないじゃん!詐欺だよ!
!SLIDE
EnumeratorT の値達 プリミティブ編 その 2
----------------------------------------
```scala
def enumEofT[X, E, F[_] : Pointed]: EnumeratorT[X, E, F] =
new EnumeratorT[X, E, F] {
def apply[A] = _.mapCont(_(eofInput))
}
```
* EOF を供給する Enumerator
* これを供給してから IterateeT#run を呼ぶ
!SLIDE
EnumeratorT の値達 プリミティブ編 その 3
----------------------------------------
```scala
def enumOne[X, E, F[_]: Pointed](e: E): EnumeratorT[X, E, F] =
new EnumeratorT[X, E, F] {
def apply[A] = _.mapCont(_(elInput(e)))
}
```
* 入力を一つだけ供給する Enumerator
!SLIDE
未完
----
* 残念
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment