Arrow (Kleisli) で List モナド - Haskell, Frege, Scalaz
「Scalaz でリストモナド - Kleisli による関数合成 」等で試してきた List モナドを使ったチェスのナイト移動の処理を Arrow (Kleisli) を使って実装し直してみました。
Arrow は計算のための汎用的なインターフェースで、モナドを扱うための Arrow として Kleisli があります。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140810/
Haskell の場合
Haskell の Arrow は >>>
や <<<
で合成できるようになっています。
Kleisli はモナドを扱うための Arrow なので、下記では List モナドを返す関数 moveKnight を Kleisli へ包んで合成しています。
Kleisli から包んだ関数を取り出すには runKleisli
を使います。
3手版
まずは 3手版です。
以前の List モナド版との違いは in3
と canReachIn3
関数を Arrow で実装し直した点です。
Kleisli を使えば、モナド値が無くてもモナドを返す関数 (通常の値を取ってモナドを返す関数) を簡単に合成できるので in3
はポイントフリースタイルで定義しました。 (このため canReachIn3 関数の引数の順序が 以前のもの と異なっています)
また、通常の関数は Arrow のインスタンスなので、canReachIn3 関数の部分は単純に canReachIn3 end = runKleisli in3 >>> elem end
とする事も可能です。
move_knight.hs
import Control.Arrow type KnightPos = (Int, Int) moveKnight :: KnightPos -> [KnightPos] moveKnight (c, r) = filter onBoard [ (c + 2, r - 1), (c + 2, r + 1), (c - 2, r - 1), (c - 2, r + 1), (c + 1, r - 2), (c + 1, r + 2), (c - 1, r - 2), (c - 1, r + 2) ] where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8] -- 3手先の移動位置を列挙 in3 :: Kleisli [] KnightPos KnightPos in3 = Kleisli moveKnight >>> Kleisli moveKnight >>> Kleisli moveKnight -- 指定位置に3手で到達できるか否かを判定 canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool canReachIn3 end = arr (runKleisli in3) >>> arr (elem end) -- 以下でも可 -- canReachIn3 :: KnightPos -> KnightPos -> Bool -- canReachIn3 end = runKleisli in3 >>> elem end main = do print $ runKleisli in3 $ (6, 2) print $ canReachIn3 (6, 1) $ (6, 2) print $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight.hs [(8,1),(8,3),・・・ ・・・ ,(3,4),(3,8)] True False
N手版
3手版と同様に inMany
と canReachInMany
関数を Arrow で実装し直してみました。
move_knight_many.hs
・・・ -- N手先の移動位置を列挙 inMany :: Int -> Kleisli [] KnightPos KnightPos inMany x = foldr (>>>) returnA (replicate x (Kleisli moveKnight)) -- 指定位置にN手で到達できるか否かを判定 canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool canReachInMany x end = arr (runKleisli (inMany x)) >>> arr (elem end) -- 以下でも可 -- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool -- canReachInMany x end = runKleisli (inMany x) >>> elem end main = do print $ runKleisli (inMany 3) $ (6, 2) print $ canReachInMany 3 (6, 1) $ (6, 2) print $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight_many.hs [(8,1),(8,3),・・・ ・・・ ,(3,4),(3,8)] True False
Frege の場合
Frege は Haskell とほとんど同じ実装になりますが、下記の点が異なります。
>>>
の代わりに.
で Arrow を合成runKleisli
の代わりにrun
を使用
なお、.
は >>>
と合成の向きが異なります。
3手版
3手版です。
move_knight.fr
package sample.MoveKnight where import frege.control.Arrow import frege.control.arrow.Kleisli type KnightPos = (Int, Int) moveKnight :: KnightPos -> [KnightPos] moveKnight (c, r) = filter onBoard [ (c + 2, r - 1), (c + 2, r + 1), (c - 2, r - 1), (c - 2, r + 1), (c + 1, r - 2), (c + 1, r + 2), (c - 1, r - 2), (c - 1, r + 2) ] where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8] -- 3手先の移動位置を列挙 in3 :: Kleisli [] KnightPos KnightPos in3 = Kleisli moveKnight . Kleisli moveKnight . Kleisli moveKnight -- 指定位置に3手で到達できるか否かを判定 canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool canReachIn3 end = arr (elem end) . arr in3.run -- 以下でも可 -- canReachIn3 :: KnightPos -> KnightPos -> Bool -- canReachIn3 end = elem end . in3.run main args = do println $ in3.run $ (6, 2) println $ canReachIn3 (6, 1) $ (6, 2) println $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight.fr ・・・ > java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnight [(8, 1), (8, 3), ・・・ ・・・ (3, 4), (3, 8)] true false
N手版
N手版です。
move_knight_many.fr
package sample.MoveKnightMany where ・・・ -- N手先の移動位置を列挙 inMany :: Int -> Kleisli [] KnightPos KnightPos inMany x = foldr (.) id (replicate x (Kleisli moveKnight)) -- 指定位置にN手で到達できるか否かを判定 canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool canReachInMany x end = arr (elem end) . arr (inMany x).run -- 以下でも可 -- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool -- canReachInMany x end = elem end . (inMany x).run main args = do println $ (inMany 3).run $ (6, 2) println $ canReachInMany 3 (6, 1) $ (6, 2) println $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight_many.fr ・・・ > java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnightMany [(8, 1), (8, 3), ・・・ ・・・ (3, 4), (3, 8)] true false
Scalaz の場合
最後に Scalaz を使った Scala による実装です。
Haskell と同様に >>>
で Arrow を合成できるようになっています。
3手版
3手版です。
MoveKnight.scala
package sample import scalaz._ import Scalaz._ object MoveKnight extends App { type KnightPos = Tuple2[Int, Int] val inRange = (p: Int) => 1 to 8 contains p val moveKnight = (p: KnightPos) => List( (p._1 + 2, p._2 - 1), (p._1 + 2, p._2 + 1), (p._1 - 2, p._2 - 1), (p._1 - 2, p._2 + 1), (p._1 + 1, p._2 - 2), (p._1 + 1, p._2 + 2), (p._1 - 1, p._2 - 2), (p._1 - 1, p._2 + 2) ).filter { case (x, y) => inRange(x) && inRange(y) } // 3手先の移動位置を列挙 val in3 = Kleisli(moveKnight) >>> Kleisli(moveKnight) >>> Kleisli(moveKnight) // 以下でも可 // val in3 = Kleisli(moveKnight) >==> moveKnight >==> moveKnight // 指定位置に3手で到達できるか否かを判定 val canReachIn3 = (end: KnightPos) => in3.run >>> { xs => xs.contains(end) } in3 (6, 2) |> println (6, 2) |> canReachIn3 (6, 1) |> println (6, 2) |> canReachIn3 (7, 3) |> println }
実行結果
> gradle run MoveKnight :compileJava UP-TO-DATE :compileScala UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :run List((8,1), (8,3), ・・・ ・・・ ・・・, (3,4), (3,8)) true false
N手版
N手版です。
MoveKnightMany.scala
package sample import scalaz._ import Scalaz._ object MoveKnightMany extends App { ・・・ // N手先の移動位置を列挙 val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >>> b } // 以下でも可 // val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >=> b } // 指定位置にN手で到達できるか否かを判定 val canReachInMany = (x: Int) => (end: KnightPos) => inMany(x).run >>> { xs => xs.contains(end) } (6, 2) |> inMany(3) |> println (6, 2) |> canReachInMany(3)(6, 1) |> println (6, 2) |> canReachInMany(3)(7, 3) |> println }
実行結果
> gradle run -Pmany MoveKnightMany :compileJava UP-TO-DATE :compileScala UP-TO-DATE :processResources UP-TO-DATE :classes UP-TO-DATE :run List((8,1), (8,3), ・・・ ・・・ ・・・, (3,4), (3,8)) true false
なお、ビルドと実行には下記のような Gradle ビルド定義ファイルを使用しました。
build.gradle
apply plugin: 'application' apply plugin: 'scala' repositories { mavenCentral() } dependencies { compile 'org.scala-lang:scala-library:2.11.2' compile 'org.scalaz:scalaz-core_2.11:7.1.0' } if (!hasProperty('many')) { println 'MoveKnight' mainClassName = 'sample.MoveKnight' } else { println 'MoveKnightMany' mainClassName = 'sample.MoveKnightMany' }
Roy で List モナド
前回に続き、今回も Roy を試してみます。
Roy ではモナドを使った do 記法を使えるようなので、JavaScript で List モナド - Monadic 等で試したチェスのナイト移動の List モナド処理を同じように実装してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20140724/
モナドの定義
Roy では下記のように return と bind を実装してモナドを定義します。
let <モナド> { return: \x -> ・・・ bind: \x f -> ・・・ }
これで Monadic と同じように do <モナド> ・・・
のような記法が使えます。
それでは、List モナドを定義して do を簡単に使ってみました。
Underscore.js に Haskell の concat と同等の処理が無さそうだったので concat を自前で定義しています。
sample.roy
let _ = require 'underscore' let concat xs = _.reduce xs (\a b -> a.concat b) [] // リストモナド let listMonad = { return: \x -> [x] bind: \xs f -> concat (_.map xs (\y -> f y)) } // do 記法 let res = do listMonad x <- [1, 3, 5] y <- ['a', 'b'] return (x * 2, y) console.log res
do でタプル (要素1, 要素2, ・・・)
を return していますが、下記のようにタプルは配列として扱われるようです。
実行結果
> roy -r sample.roy [ [ 2, 'a' ], [ 2, 'b' ], [ 6, 'a' ], [ 6, 'b' ], [ 10, 'a' ], [ 10, 'b' ] ]
3手版
それでは本題に入ります。
まずは、List モナドを使って下記の処理を実装してみます。
- 3手先のナイトの移動位置を全て列挙する処理
- 3手後に指定の終了位置に到達するか否かを判定する処理
Haskell と同様に type
で型シノニムを定義できるので、ナイトの位置情報を表す KnightPos
という型シノニムを定義しました。
関数の引数では (<引数>: <型>)
のように型を指定する事も可能です。
また、複数の引数は Haskell と同様に半角スペースで区切る事になります。
move_knight.roy (3手移動版)
let _ = require 'underscore' let concat xs = _.reduce xs (\a b -> a.concat b) [] let listMonad = { return: \x -> [x] bind: \xs f -> concat (_.map xs (\y -> f y)) } // 型シノニムの定義 type KnightPos = {c: Number, r: Number} let inRange (n: Number) = _.contains (_.range 1 9) n // ナイトの次の移動先を列挙 let moveKnight (p: KnightPos) = _.filter [ {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1}, {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1}, {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2}, {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2} ] ( \t -> (inRange t.c) && (inRange t.r) ) // 3手先の移動位置を列挙(重複あり) let in3 (start: KnightPos) = do listMonad fst <- moveKnight start snd <- moveKnight fst moveKnight snd console.log (in3 {c: 6, r: 2}) // 指定位置に3手で到達できるか否かを判定 let canReachIn3 (start: KnightPos) (end: KnightPos) = _.any (in3 start) (\p -> p.c == end.c && p.r == end.r) console.log (canReachIn3 {c: 6, r: 2} {c: 6, r: 1}) console.log (canReachIn3 {c: 6, r: 2} {c: 7, r: 3})
実行結果
> roy -r move_knight.roy [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
N手版
先程は 3手で固定していましたが、任意の手数を指定できるようにしてみます。
こちらは do を使わず、直接 listMonad.bind
を使って n 回のナイト移動処理を連結するようにしました。
move_knight_many.roy (N手移動版)
・・・ // n手先の移動位置を列挙(重複あり) let inMany (n: Number) (start: KnightPos) = _.reduce (_.range n) (\a b -> listMonad.bind a moveKnight) [start] console.log (inMany 3 {c: 6, r: 2}) // 指定位置に n手で到達できるか否かを判定 let canReachInMany (n: Number) (start: KnightPos) (end: KnightPos) = _.any (inMany 3 start) (\p -> p.c == end.c && p.r == end.r) console.log (canReachInMany 3 {c: 6, r: 2} {c: 6, r: 1}) console.log (canReachInMany 3 {c: 6, r: 2} {c: 7, r: 3})
実行結果
> roy -r move_knight_many.roy [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
Functional Java で Iteratee - take の実装
Functional Java の Iteratee を使って、以前試した Play2 の Iteratee (http://fits.hatenablog.com/entry/20130212/1360681996, http://fits.hatenablog.com/entry/20130216/1361027691) や RxJava (http://fits.hatenablog.com/entry/20130310/1362876374) と同等の処理を実装してみました。
ソースは http://github.com/fits/try_samples/tree/master/blog/20130518/
Functional Java の Iteratee に関して
Functional Java の Iteratee には、Iteratee への入力となる Iteratee.Input と Iteratee の処理を実装する Iteratee.IterV が用意されています。
Iteratee.Input は下記の 3タイプがあり、それぞれが匿名クラスとして実装されているのでファクトリメソッドを通して作成するようになっています。
apply メソッドに Empty・EOF・El の 3タイプそれぞれの処理内容を実装した P1 オブジェクトを渡す事で該当するタイプの処理を適用した結果を取得します。 *1
Iteratee.Input のタイプ
タイプ | 内容 | ファクトリメソッド |
---|---|---|
Empty | 入力データが空 | Input.empty() |
EOF | 終端に到達 | Input.eof() |
El | 入力データ有り | Input.el() |
Iteratee.IterV の 2つのタイプ Cont・Done も Input と同様に匿名クラスで実装されており、ファクトリメソッドを通して作成します。
run メソッドで処理を実施し処理結果を取得します。
Iteratee.IterV のタイプ
タイプ | 内容 | ファクトリメソッド |
---|---|---|
Cont | Iteratee の処理が継続中の状態。新しい Input を受け取って新しい IterV(Cont か Done)を返します | IterV.cont() |
Done | Iteratee の処理が完了した状態。処理結果を格納しています | IterV.done() |
動作内容としては下記のような処理が入れ子になるような感じです。
- Enumerator が Cont に Input を渡して、Done が返ってくれば処理終了、Cont なら Enumerator が次の Input を渡して・・・
IterV と Input の処理の流れは下記のようなイメージになると思います。
El で処理が完了となるか EOF が来れば Done で終了し、El で継続中となるか Empty が来れば Cont で処理が続く事になります。
ちなみに、ファイルの内容を読み込んで IterV に Input を渡す Enumerator 的な処理は IO クラスにメソッドが用意されており(enumFileLines 等)、drop や head 等の処理は IterV に用意されています。(ただし、take は用意されていないようです)
take 処理
Play2 の Enumeratee.take() や RxJava の Observable.take() のような take 処理は Functional Java 3.1 では用意されていないようなので、今回は自前で実装してみる事にしました。
take の処理内容は概ね下記のようになります。
- take の引数に与えられた数量の El を処理するまで Cont を返す
- 途中で EOF となるか、take が完了した際に Done (take した入力データを処理結果として格納) を返す
Groovy で実装
それでは Groovy で take を実装してみたいと思います。
まずは Cont を返さないと何も始まらないので take は Cont を返すようにします。
Cont には Input -> IterV という処理(与えられた Input の内容に応じて Cont / Done を返す)を実装した F オブジェクトを設定する事になります。
今回は Input を受け取って Cont / Done を返す処理をクロージャで実装し、"残りの take 数" と "take 結果のリスト" (今回は java.util.List を使いました)を引数として与えるようにしています。
ここで Empty・El・EOF それぞれの処理は以下のように実装しています。
Inputのタイプ | takeの状態 | 戻り値となるIterVのタイプ | 備考 |
---|---|---|---|
Empty | - | Cont | 残りの take 数そのままで step を実施するように設定 |
EOF | - | Done | take 結果のリストを設定 |
El | 完了(残りの take 数が 0 以下) | Done | take 結果のリストと未処理の入力データを設定 |
El | 継続中 | Cont | 残りの take 数を -1、take 結果のリストに入力値を追加して step を実施するように設定 |
また、Input.apply の引数に渡す型は下記のようになっています。(下記の E は入力データの型です)
Inputのタイプ | applyへの引数の型 |
---|---|
Empty | P1< IterV< E, List |
EOF | P1< IterV< E, List |
El | P1< F< E, IterV |
なお、クロージャ内で再帰的にクロージャを使っている関係上、def step を先に宣言して、後から実施を設定するようにしています。 *2
最後に take の動作確認として、drop と take を組み合わせた Iteratee にファイルの内容を処理させてみました。
今回はファイルを行単位で処理させるため IO.enumFileLines() を使っています。IO.enumFileLines() は IO
take_sample.groovy
@Grab('org.functionaljava:functionaljava:3.1') import fj.F import fj.P1 import fj.data.IO import fj.data.Option import static fj.data.Iteratee.* import java.nio.charset.StandardCharsets def take(int n) { def step step = { int count, List acc, Input s -> // Empty 時の処理内容(Input が空の場合) def empty = { IterV.cont( { step(count, acc, it) } as F) } as P1 // EOF 時の処理内容(終端に達した場合) def eof = { IterV.done(acc, s) } as P1 // El 時の処理内容(Input に値が設定されている場合) def el = { return { value -> if (count <= 0) { // take 結果リスト acc と未処理の入力 s を設定して Done IterV.done(acc, s) } else { // take した値を take 結果リスト acc に追加して Cont IterV.cont({ step(count - 1, acc << value, it) } as F) } } as F } as P1 s.apply(empty, el, eof) } IterV.cont({ step(n, [], it) } as F) } // (1) 1つ目を捨てて 2つ目から 3つ取り出す Iteratee 処理の組み立て def iter = IterV.drop(1).bind({ take(3) } as F) // (2) ファイルを行単位で (1) の処理を実施する IO<IterV> の取得 def ioIter = IO.enumFileLines(new File(args[0]), Option.some(StandardCharsets.UTF_8), iter) // (3) 処理の実行と結果の出力 ioIter.run().run().each { println "#${it}" }
実行結果は下記のようになります。
処理内容としては 1行目を無視して 2行目から 3行を取り出し先頭に # をつけて出力しています。
実行結果
> groovy take_sample.groovy sample.txt #サンプル2 #サンプル3 #サンプル4
使用したファイルの内容は以下の通りです。
sample.txt
サンプル1 サンプル2 サンプル3 サンプル4 サンプル5 サンプル6 サンプル7 サンプル8
Java で実装
同様の処理を Java SE 7 で実装してみました。(ただし、take 結果を溜めるリストの用途に java.util.List を使わず fj.data.List を使っています)
クロージャが使えない分、Groovy と比べると型まわりが相当ややこしくなっているので実用的とは言い難いかもしれません。
TakeSample.java
import fj.F; import fj.F3; import fj.Function; import fj.P; import fj.P1; import fj.Unit; import fj.data.IO; import fj.data.Option; import fj.data.List; import static fj.data.Iteratee.*; import java.io.File; import java.nio.charset.StandardCharsets; class TakeSample { public static void main(String... args) throws Exception { // (1) 1つ目を捨てて 2つ目から 3つ取り出す Iteratee 処理の組み立て IterV<String, List<String>> iter = IterV.<String>drop(1).bind(new F<Unit, IterV<String, List<String>>>() { @Override public IterV<String, List<String>> f(final Unit xe) { return take(3); } }); // (2) ファイルを行単位で (1) の処理を実施する IO の取得 IO<IterV<String, List<String>>> ioIter = IO.enumFileLines(new File(args[0]), Option.some(StandardCharsets.UTF_8), iter); // (3) 処理の実行と結果の出力 ioIter.run().run().foreach(new F<String, Unit>() { @Override public Unit f(final String line) { System.out.println("#" + line); return Unit.unit(); } }); } public static final <E> IterV<E, List<E>> take(final int n) { final F3<Integer, List<E>, Input<E>, IterV<E, List<E>>> step = new F3<Integer, List<E>, Input<E>, IterV<E, List<E>>>() { final F3<Integer, List<E>, Input<E>, IterV<E, List<E>>> step = this; @Override public IterV<E, List<E>> f(final Integer count, final List<E> acc, final Input<E> s) { // Empty 時の処理内容(Input が空の場合) final P1<IterV<E, List<E>>> empty = new P1<IterV<E, List<E>>>() { @Override public IterV<E, List<E>> _1() { return IterV.cont(new F<Input<E>, IterV<E, List<E>>>() { @Override public IterV<E, List<E>> f(Input<E> e) { return step.f(count, acc, e); } }); } }; // EOF 時の処理内容(終端に達した場合) final P1<IterV<E, List<E>>> eof = new P1<IterV<E, List<E>>>() { @Override public IterV<E, List<E>> _1() { return IterV.done(acc, s); } }; // El 時の処理内容(Input に値が設定されている場合) final P1<F<E, IterV<E, List<E>>>> el = new P1<F<E, IterV<E, List<E>>>>() { @Override public F<E, IterV<E, List<E>>> _1() { return new F<E, IterV<E, List<E>>>() { @Override public IterV<E, List<E>> f(final E value) { if (count <= 0) { // take 結果リスト acc と未処理の入力 s を設定して Done return IterV.done(acc, s); } else { // take した値を take 結果リスト acc に追加して Cont return IterV.cont(new F<Input<E>, IterV<E, List<E>>>() { @Override public IterV<E, List<E>> f(Input<E> e) { return step.f(count - 1, acc.snoc(value), e); } }); } } }; } }; return s.apply(empty, el, eof); } }; return IterV.cont(new F<Input<E>, IterV<E, List<E>>>() { @Override public IterV<E, List<E>> f(Input<E> e) { return step.f(n, List.<E>nil(), e); } }); } }
Play2 の Iteratee を使った行単位のファイル処理2 - Enumerator.generateM, Enumerator.fromCallback1
前回 id:fits:20130212 は Enumeratee.grouped() を使って行単位のファイル処理を実装しましたが、今回は Enumerator.generateM() と Enumerator.fromCallback1() をそれぞれ使って同様の処理を実装してみました。
sbt のビルド定義ファイルなどは前回と同様のものを使っています。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130216/
Enumerator.generateM による行分割
Enumerator.generateM は、引数として渡した処理 *1 を繰り返し実行する Enumerator を作成します。
繰り返しは None を返す事で終了するため、下記 (a) ではファイルを 1行読んで Some(<1行分の文字列>) を返し、ファイルの終端に達した場合に None を返すようにしています。
こうする事でファイルの内容を 1行ずつ返す Enumerator を実現できます。
(b) の処理でファイルの内容を全て消費してしまいますが、(c) で BufferedReader をリセットする事で *2、(d) では (a) で作成した enumerator をそのまま使って 1行目から処理できるようになっています。
さらに (e) でも enumerator を利用していますが、enumerator はファイルの終端に達していないため (d) からの続きの行を処理する事になります。
ファイルのクローズ処理 (f) は Iteratee の処理とは別に一番最後で実施するようにしました。
EnumeratorLineSample.scala (Enumerator.generateM を使ったサンプル)
package fits.sample import play.api.libs.iteratee._ import scala.concurrent.{Await, Future} import scala.concurrent.duration.Duration import java.io.{BufferedReader, FileInputStream, InputStreamReader} import java.nio.charset.StandardCharsets._ object EnumeratorLineSample extends App { import scala.concurrent.ExecutionContext.Implicits.global val reader = new BufferedReader(new InputStreamReader(new FileInputStream(args(0)), UTF_8)) reader.mark(512) // (a) ファイルの内容を 1行ずつ返す Enumerator val enumerator = Enumerator.generateM( Future { reader.readLine match { case line: String => Some(line) case _ => None // ファイルの終端に達した場合 } }) // (b) 行毎に先頭へ # を付けて出力する処理の組み立て val future = enumerator |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future, Duration.Inf) println("----------") // (c) ファイルの内容を先頭から返すようにリセット reader.reset // (d) 1行目を捨てて 2行目から 2行取り出し各行の先頭へ # を付けて出力する処理の組み立て val future2 = enumerator &> Enumeratee.drop(1) &> Enumeratee.take(2) |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future2, Duration.Inf) println("----------") // (e) 1行出力。(d) からの続きとなるのでファイルの 4行目が出力される val future3 = enumerator &> Enumeratee.take(1) |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future3, Duration.Inf) // (f) クローズ処理 reader.close }
実行結果は以下の通りです。
実行結果
> sbt "run sample.txt" ・・・ [info] Running fits.sample.EnumeratorLineSample sample.txt #Play2のIterateeを使ったファイル処理の #サンプル # #1行毎に処理するサンプルを #実装してみました。 ---------- #サンプル # ---------- #1行毎に処理するサンプルを
Enumerator.fromCallback1 による行分割
次は、Enumerator.fromCallback1 *3 を使って同様の処理を実装してみます。
generateM に渡していた処理を fromCallback1 の第一引数の処理結果として返すようにすれば同じ処理内容となります。
ただし下記では、fromCallback1 の第二引数(onComplete)に BufferedReader を close する処理 (b) を渡し、Iteratee の処理が完了する度に入力ストリーム (今回はファイル) をクローズするようにしています。
(e) では (d) で使った enumerator2 を使っていますが、(d) でファイルはクローズされており Enumerator は何も生成しないため何も出力されません。
EnumeratorLineSample2.scala (Enumerator.fromCallback1 を使ったサンプル)
・・・ object EnumeratorLineSample2 extends App { import scala.concurrent.ExecutionContext.Implicits.global // (a) 1行ずつファイルの内容を返す Enumerator を作成 def fromStreamLine(input: BufferedReader) = { Enumerator.fromCallback1(_ => Future { input.readLine match { case line: String => Some(line) case _ => None // ファイルの終端に達した場合 } }, { println("*** close") // (b) ファイルのクローズ処理 input.close }) } val enumerator = fromStreamLine(new BufferedReader(new InputStreamReader(new FileInputStream(args(0)), UTF_8))) // (c) 行毎に先頭へ # を付けて出力する処理の組み立て val future = enumerator |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future, Duration.Inf) println("----------") val enumerator2 = fromStreamLine(new BufferedReader(new InputStreamReader(new FileInputStream(args(0)), UTF_8))) // (d) 1行目を捨てて 2行目から 2行取り出し各行の先頭へ # を付けて出力する処理の組み立て val future2 = enumerator2 &> Enumeratee.drop(1) &> Enumeratee.take(2) |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future2, Duration.Inf) println("----------") // (e) ファイルはクローズ済みのため何も出力されない val future3 = enumerator2 &> Enumeratee.take(1) |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future3, Duration.Inf) }
実行結果は以下の通りです。
Iteratee の処理を行う度に "*** close" が出力されている事を確認できます。
実行結果
・・・ [info] Running fits.sample.EnumeratorLineSample2 sample.txt *** close #Play2のIterateeを使ったファイル処理の #サンプル # #1行毎に処理するサンプルを #実装してみました。 ---------- *** close #サンプル # ----------
Play2 の Iteratee を使った行単位のファイル処理1 - Enumeratee.grouped() の利用
id:fits:20130116 で使った Play2 の Iteratee を単体利用して行単位のファイル処理を実装してみました。
- play-iteratees 2.1.0
Iteratee は Enumerator や Enumeratee と組み合わせて使用し、それぞれ以下のような役割を担います。
- Enumerator : データを生産
- Enumeratee : Enumerator から受け取ったデータを Iteratee へ渡す
- Iteratee : データを消費
ちなみに、ファイルの内容を "改行" で分割するような仕組みは今のところ Play2 の API に用意されていないようなので、
1行分のデータを取り出す Enumeratee や Enumerator を自前で用意する事になります。
そこで今回は Enumeratee.grouped() を使って行毎にデータをグルーピングする Enumeratee を作成してみました。
次回は Enumerator.generateM() を使って同様の処理を実装してみるつもりです。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130212/
はじめに
今回使用した sbt のビルド定義ファイルは以下の通りです。
build.sbt
scalaVersion := "2.10.0" resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" libraryDependencies += "play" %% "play-iteratees" % "2.1.0" mainClass in (Compile, run) := Some("fits.sample.EnumerateeLineSample")
sample.txt
Play2のIterateeを使ったファイル処理の サンプル 1行毎に処理するサンプルを 実装してみました。
Enumeratee.grouped による行分割
Enumeratee.grouped を使った行単位の処理は以下のようになりました。
- (a) Enumerator.fromFile() で chunkSize のサイズ分だけ読み込んだファイルの内容を Array[Byte] として生成する Enumerator 作成
- (e) Enumerator・Enumeratee・Iteratee を結合
- Enumeratee.mapConcat で Enumerator と Enumeratee.grouped を結び付ける Enumeratee 作成
- Enumeratee.grouped で行毎にグルーピングする Enumeratee 作成
- Iteratee.foreach で各行の先頭に # を付けて出力する Iteratee 作成
さらに、Enumeratee.grouped には以下のようにして組み立てた takeLine *1 を渡しています。
- (c) takeWhile で改行が来るまで入力を溜め、Iteratee.getChunks で溜めた分の入力 List[Byte] を line へ設定
- (d) 改行文字を take(1) で取得し Iteratee.ignore で捨てる
- new String() で line (改行までの部分)を文字列化
なお、Iteratee はモナドなので、takeLine は for 式を使って Iteratee を組み立てています。
EnumerateeLineSample.scala
package fits.sample import play.api.libs.iteratee._ import scala.concurrent.Await import scala.concurrent.duration.Duration import java.io.File import java.nio.charset.StandardCharsets._ object EnumerateeLineSample extends App { import scala.concurrent.ExecutionContext.Implicits.global // (a) ファイルの内容を取得する Enumerator val enumerator = Enumerator.fromFile(new File(args(0))) // (b) 1行分の String を生成する Iteratee val takeLine = for { line <- Enumeratee.takeWhile[Byte](_ != '\n'.toByte) &>> Iteratee.getChunks // (c) 改行文字までの入力を line へ設定 _ <- Enumeratee.take(1) &>> Iteratee.ignore[Byte] // (d) 改行文字を捨てる } yield new String(line.toArray, UTF_8) // (e) 行毎に先頭へ # を付けて出力する処理の組み立て val future = enumerator &> Enumeratee.mapConcat( _.toSeq ) &> Enumeratee.grouped(takeLine) |>>> Iteratee.foreach { s => println(s"#${s}") } // (f) future の処理完了待ち Await.ready(future, Duration.Inf) }
それでは sbt run で実行してみます。
"run <引数> ・・・" のように run と引数をダブルクォーテーションで囲めば、sbt run で実行時引数を指定する事が可能です。
実行結果
> sbt "run sample.txt" ・・・ [info] Running fits.sample.EnumerateeLineSample sample.txt #Play2のIterateeを使ったファイル処理の #サンプル # #1行毎に処理するサンプルを #実装してみました。 #
実はこの sbt run は処理が完了してもプロセスが終了しないので Ctrl + C する必要があります。
プロセスを終了するには play.api.libs.iteratee.internal.defaultExecutionContext 等で生成されている ExecutorService とかを shutdown する必要があると思うのですが、今回は上手く終了する方法を見つけられませんでした。
とりあえず、以下のようにスレッドを interrupt しまくれば強引にプロセスを終了させる事は可能です。
・・・ object EnumerateeLineSample extends App { ・・・ // 強引にプロセスを終了する import scala.collection.convert.WrapAsScala._ Thread.getAllStackTraces().keySet().foreach(_.interrupt) }
最後に、Enumeratee.grouped(takeLine) と Iteratee.foreach の間に Enumeratee.drop(1) と Enumeratee.take(2) を追加すれば、2行目と 3行目の先頭へ # を付けて出力するような処理に変わります。
EnumerateeLineSample2.scala
・・・ object EnumerateeLineSample2 extends App { ・・・ // 1行目を捨てて 2行目から 2行取り出し各行の先頭へ # を付けて出力する処理の組み立て val future = enumerator &> Enumeratee.mapConcat( _.toSeq ) &> Enumeratee.grouped(takeLine) &> Enumeratee.drop(1) &> Enumeratee.take(2) |>>> Iteratee.foreach { s => println(s"#${s}") } Await.ready(future, Duration.Inf) }
実行結果
・・・ [info] Running fits.sample.EnumerateeLineSample2 sample.txt #サンプル #
*1:型は Iteratee[Byte, String]
JavaScript で List モナド - Monadic
Monadic は JavaScript 用のモナドライブラリです。
今回はこの Monadic を使って、以前 (id:fits:20120912, id:fits:20120930) に Scalaz や Functional Java で実装したナイト移動の List モナド処理 *1 を JavaScirpt で実装してみました。
- Monadic 0.0.5
- Node.js v0.8.18
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130120/
Monadic のインストール
npm install monadic でインストールするか、以下のように package.json の dependencies に monadic の設定を追加して npm install しておきます。
package.json
{ "name": "monadic-list-sample", "version": "0.0.0", "dependencies": { "monadic": "*" } }
実行例
> npm install
3手版
3手先のナイトの移動位置を全て列挙する処理と3手後に指定の終了位置に到達するか否かを判定する処理を実装します。
Monadic でバインド処理 *2 を行うには mbind を使います。List モナドは require('monadic').list() で取得できます。
3手版 move_knight.js
var monadic = require('monadic'); var listM = monadic.list(); // ナイトの次の移動先を列挙 var moveKnight = function(p) { return [ {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1}, {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1}, {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2}, {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2} ].filter(function(t) { return (1 <= t.c && t.c <= 8 && 1 <= t.r && t.r <= 8); }); }; // 3手先の移動位置を列挙(重複あり) var in3 = function(start) { return listM.mbind( listM.mbind( // 下記の listM.mbind(・・・) は moveKnight(start) でも可 listM.mbind( [start], moveKnight ), moveKnight ), moveKnight ); }; console.log(in3({c: 6, r: 2})); // 指定位置に3手で到達できるか否かを判定 var canReachIn3 = function(start, end) { return in3(start).some(function(p) { return (p.c == end.c && p.r == end.r); }); }; console.log(canReachIn3({c: 6, r: 2}, {c: 6, r: 1})); console.log(canReachIn3({c: 6, r: 2}, {c: 7, r: 3}));
実行結果は以下の通り。
3手版の実行結果
> node move_knight.js [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
do 記法 3手版
上記サンプルの in3 の処理を Monadic の do 記法を使って実装し直したのが下記です。
Monadic の do 記法は以下のように実装します。(Haskell の do に近い記述が可能になっています)
do 記法 3手版 move_knight.mjs
var monadic = require('monadic'); var listM = monadic.list(); var moveKnight = function(p) { ・・・ }; var in3 = function(start) { // do 記法で実装 return do listM { first <- moveKnight(start) second <- moveKnight(first) third <- moveKnight(second) return third }; }; console.log(in3({c: 6, r: 2})); var canReachIn3 = function(start, end) { ・・・ }; console.log(canReachIn3({c: 6, r: 2}, {c: 6, r: 1})); console.log(canReachIn3({c: 6, r: 2}, {c: 7, r: 3}));
do 記法を使った .mjs ファイルのオーソドックスな実行方法が分からなかったので、-e オプションを使って実行してみました。
require('monadic').transform() で 'move_knight.mjs' を純粋な JavaScript コードへ変換し eval 実行しています。
do 記法 3手版の実行結果
> node -e "eval(require('monadic').transform(process.argv[1]))" move_knight.mjs [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
transform による変換結果は以下のようになります。
require('monadic').transform('move_knight.mjs') の変換結果(do の箇所)
・・・ var in3 = function (start) { return listM.mbind(moveKnight(start), function (first) { return listM.mbind(moveKnight(first), function (second) { return listM.mbind(moveKnight(second), function (third) { return function () { return listM.mreturn(third); }.bind(this)(); }.bind(this)); }.bind(this)); }.bind(this)); }; ・・・
N手版
N手版は、moveKnight を使ったバインド処理 (listM.mbind) を入れ子に N個連結するため reduceRight を使いました。
N手版 move_knight_many.js
var monadic = require('monadic'); var listM = monadic.list(); var moveKnight = function(p) { ・・・ }; // N手 (num) 先の移動位置を列挙(重複あり) var inMany = function(num, start) { // num 個の moveKnight から成る配列を作成 var items = new Array(num); for (var i = 0; i < num; i++) { items.push(moveKnight); } // num 個の moveKnight を listM.mbind で連結 return items.reduceRight( function(acc, elem) { return listM.mbind(acc, elem); }, [start] ); }; console.log(inMany(3, {c: 6, r: 2})); // 指定位置に N手(num) で到達できるか否かを判定 var canReachInMany = function(num, start, end) { return inMany(num, start).some(function(p) { return (p.c == end.c && p.r == end.r); }); }; console.log(canReachInMany(3, {c: 6, r: 2}, {c: 6, r: 1})); console.log(canReachInMany(3, {c: 6, r: 2}, {c: 7, r: 3}));
実行結果は以下の通り。
N手版の実行結果
> node move_knight_many.js [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
CoffeeScript 版
最後に、3手版とN手版のサンプルを CoffeeScript で実装し直してみました。
- CoffeeScript 1.4.0
Monadic の do 記法に関しては、TameJS の時 (id:fits:20120415) と同様に Embedded JavaScript を使えばいけるかもしれませんが今回は調査していません。
CoffeeScript 3手版 move_knight.coffee
monadic = require 'monadic' listM = monadic.list() moveKnight = (p) -> [ {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1}, {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1}, {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2}, {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2} ].filter (t) -> t.c in [1..8] and t.r in [1..8] in3 = (start) -> listM.mbind( listM.mbind( listM.mbind( [start], moveKnight ), moveKnight ), moveKnight ) console.log in3 {c: 6, r: 2} canReachIn3 = (start, end) -> in3(start).some (p) -> p.c == end.c and p.r == end.r console.log canReachIn3 {c: 6, r: 2}, {c: 6, r: 1} console.log canReachIn3 {c: 6, r: 2}, {c: 7, r: 3}
CoffeeScript 3手版の実行結果
> coffee move_knight.coffee [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
N手版は以下の通りです。
[1..num].map( -> moveKnight ) で num 個の moveKnight から成る配列を作っています。
CoffeeScript N手版 move_knight_many.coffee
monadic = require 'monadic' listM = monadic.list() moveKnight = (p) -> [ {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1}, {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1}, {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2}, {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2} ].filter (t) -> t.c in [1..8] and t.r in [1..8] inMany = (num, start) -> [1..num].map( -> moveKnight ).reduceRight (acc, elem) -> listM.mbind(acc, elem) , [start] console.log inMany 3, {c: 6, r: 2} canReachInMany = (num, start, end) -> inMany(num, start).some (p) -> p.c == end.c and p.r == end.r console.log canReachInMany 3, {c: 6, r: 2}, {c: 6, r: 1} console.log canReachInMany 3, {c: 6, r: 2}, {c: 7, r: 3}
CoffeeScript N手版の実行結果
> coffee move_knight_many.coffee [ { c: 8, r: 1 }, { c: 8, r: 3 }, { c: 4, r: 1 }, { c: 4, r: 3 }, ・・・ { c: 3, r: 4 }, { c: 3, r: 8 } ] true false
Scalaz で Codensity モナド
Scalaz の Codensity を試してみました。
Codensity モナドは継続モナドと基本的に同じですが、処理の型が以下のように異なっています。
継続モナドの場合 | Codensityモナドの場合 |
---|---|
(A => R) => R | (A => F[B]) => F[B] |
つまり、Codensity は何らかのコンテナ(List・Option 等)で包んだ値(上記の F[B] に該当)を返します。
処理結果をモナド化する用途等に使えそうですが、効果的なサンプルを思いつかなかったので、
とりあえずは id:fits:20121125 の継続モナドのサンプルと同等のものを実装してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20121222/
今回使用した sbt 用のビルドファイルは以下の通りです。
build.sbt
scalaVersion := "2.10.0-RC5" libraryDependencies += "org.scalaz" % "scalaz-core" % "7.0.0-M6" cross CrossVersion.full
Codensity の単純なサンプル
まずは、Codensity のメソッドをいくつか試してみます。
Codensity はトレイトとコンパニオンオブジェクトからなるシンプルな構成で、id:fits:20121125 で作成した継続モナドの runCont に該当するのが apply メソッドです。
また、以下のようなメソッドが Codensity コンパニオンオブジェクトに用意されています。
メソッド名 | 処理内容 |
---|---|
pureCodensity | 普通の値から Codensity を作成 |
rep | 値を格納したコンテナから Codensity を作成 |
下記サンプルの (1) 〜 (3) では apply の結果として Function0[Unit] ・ List[Int] ・ Option[Int] をそれぞれ返すようにしてみました。 println(x)" の部分が Function0[Unit] に該当">*1
以降のサンプルでは Codensity の apply を省略せずに使うようにしています。
CodensitySample.scala
package fits.sample import scalaz._ import Scalaz._ object CodensitySample extends App { // (1) Codensity[Function0, Int] で apply の結果は Function0[Unit] Codensity.pureCodensity(1).apply { (x) => () => println(x) }() // (2) Codensity[List, Int] で apply の結果は List[Int] Codensity.pureCodensity(2).apply { (x) => List(x) } |> println // (3) Codensity[Option, Int] で apply の結果は Option[Int] Codensity.pureCodensity(3).apply { Option(_) } |> println // (4) rep を使って List[Int] から Codensity[List, Int] を作成 Codensity.rep(List(1, 2)).apply { (x) => List(x, x * 10, x * 100) } |> println /* (5) バインド >>= を使うには improve を使う * 'Codensity.pureCodensity(5) >>= ・・・' とするとコンパイルエラー */ (Codensity.pureCodensity(5).improve >>= { (x) => Codensity.pureCodensity[Option, Int](x + 3) }) apply { (x) => Option(x * 10) } foreach(println) }
なお、pureCodensity を使う際にコンテナの型 (型パラメータ F) が自明でなければ (5) の "Codensity.pureCodensity[Option, Int](x + 3)" のように型を明示します。
実行結果
> sbt console ・・・ scala> fits.sample.CodensitySample.main(null) 1 List(2) Some(3) List(1, 10, 100, 2, 20, 200) 80
flatMap の処理
前回ののバインド処理サンプルと同様のものを Codensity で実装してみました。
ここではバインド >>= の代わりに flatMap で処理を繋げ、apply には値を Option に格納する関数を渡しています。
CodensitySample2.scala
・・・ object CodensitySample2 extends App { def cont[F[+_]](a: Int) = Codensity.pureCodensity[F, Int](a) def calc1[F[+_]](x: Int) = cont[F](x + 3) def calc2[F[+_]](x: Int) = cont[F](x * 10) def calc3[F[+_]](x: Int) = cont[F](x + 4) def calcAll[F[+_]](x: Int) = cont[F](x).flatMap(calc1).flatMap(calc2).flatMap(calc3) // a. 2 + 3 = 5 calc1(2).apply { Option(_) } foreach(println) // b. ((2 + 3) * 10) + 4 = 54 calcAll(2).apply { Option(_) } foreach(println) // c. 54 - 9 = 45 calcAll(2).apply { (x) => Option(x - 9) } foreach(println) }
実行結果
scala> fits.sample.CodensitySample2.main(null) 5 54 45
callCC の実装
実用性はともかく、次は callCC を Codensity で実装してみました。
継続モナドにおける callCC の肝は以下の処理ですが。
- 入れ子になった継続モナドにおいて、自分に渡された継続(関数)を無視して親の継続を呼び出す
Scalaz の Codensity では apply メソッドを実装しなければならない関係上、本来無視するはずの関数の戻り値の型に依存してしまい工夫が必要となります。
Codensity トレイトの apply メソッド
def apply[B](f: A => F[B]): F[B]
例えば、以下のように実装すると (1) の型パラメータ C と (2) の型パラメータ C は別物なのでコンパイル時に type mismatch エラーが発生します。
コンパイルエラー(type mismatch)が発生する callCC の実装例
def callCC[F[+_], A, B](f: (A => Codensity[F, B]) => Codensity[F, A]): Codensity[F, A] = { new Codensity[F, A] { // (1) def apply[C](k: A => F[C]) = { f { a: A => new Codensity[F, B] { // (2) ここでの C は (1) の C とは別の型 override def apply[C](f: B => F[C]) = { // この結果の型は (1) の C で // (2) の C とは型が異なり type mismatch となる k(a) } } }.apply(k) } } }
また、(2) で override def apply(f: B => F[C]) のように型パラメータを省略する事もできません。 *2
試行錯誤してみましたが、この問題に対する良い解決策を思いつかなかったので、とりあえず下記 (1) のように asInstanceOf でキャストして回避しました。
CodensityFunc.scala (callCC の実装)
・・・ object CodensityFunc { def callCC[F[+_], A, B](f: (A => Codensity[F, B]) => Codensity[F, A]): Codensity[F, A] = { new Codensity[F, A] { def apply[C](k: A => F[C]) = { f { a: A => new Codensity[F, B] { override def apply[D](f: B => F[D]) = { // (1) k(a).asInstanceOf[F[D]] } } }.apply(k) } } } }
なお、以前の Codensity トレイトには sealed が付いていたので、このように apply メソッドを自前で実装する事はできなかったのですが、最近のバージョンでは sealed が外れて実装できるようになっています。
callCC の処理1
それでは、callCC を使った単純なサンプルを実装してみます。
CallCCSample.scala
・・・ object CallCCSample extends App { def sample[F[+_]](n: Int): Codensity[F, Int] = CodensityFunc.callCC { cc1: (Int => Codensity[F, Int]) => if (n % 2 == 1) { cc1(n) // (1) } else { Codensity.pureCodensity(n * 10) // (2) } } sample(1).apply { Option(_) } foreach(println) sample(2).apply { Option(_) } foreach(println) sample(3).apply { Option(_) } foreach(println) sample(4).apply { Option(_) } foreach(println) }
sample の処理内容は以下のようになっており、apply には値を Option に格納する関数を渡しています。
- 引数 n が奇数なら n の値を適用する Codensity を返す (1)
- 引数 n が偶数なら n * 10 の値を適用する Codensity を返す (2)
実行結果は以下の通りです。
奇数ならそのままの値、偶数なら 10 倍した値が出力されます。
実行結果
scala> fits.sample.CallCCSample.main(null) 1 20 3 40
callCC の処理2
最後に callCC をネストさせたサンプルです。
CallCCSample2.scala
・・・ object CallCCSample2 extends App { def sample[F[+_]](n: Int): Codensity[F, Int] = CodensityFunc.callCC { cc1: (Int => Codensity[F, Int]) => if (n % 2 == 1) { cc1(n) // (1) } else { for { x <- CodensityFunc.callCC { cc2: (Int => Codensity[F, Int]) => n match { case x if (x < 4) => cc2(n * 1000) // (2) case 4 => cc1(n * 100) // (3) case _ => Codensity.pureCodensity[F, Int](n * 10) // (4) } } } yield (x + 1) // (5) } } sample(1).apply { Option(_) } foreach(println) // (1) sample(2).apply { Option(_) } foreach(println) // (2) (5) sample(3).apply { Option(_) } foreach(println) // (1) sample(4).apply { Option(_) } foreach(println) // (3) sample(5).apply { Option(_) } foreach(println) // (1) sample(6).apply { Option(_) } foreach(println) // (4) (5) }
sample の処理内容は以下のようになります。
- 引数 n が奇数なら n の値を適用する Codensity を返す (1)
- 引数 n が偶数の場合
- 4 より小さいと 1000 倍した値に +1 した値を適用する Codensity を返す (2) (5)
- 4 なら 100 倍した値を適用する Codensity を返す (3)
- それ以外は 10 倍した値に +1 した値を適用する Codensity を返す (4) (5)
(3) のように 2つ目の callCC 内で cc1 を呼び出すと (5) は実行されず、(2) のように cc2 を呼び出した場合は (5) が適用される事になります。
実行結果
scala> fits.sample.CallCCSample2.main(null) 1 2001 3 400 5 61