前回ではdefでFizzBuzzの処理を整理してましたが、どうもmapした後のタプルの処理が汚いなー
という事でそこだけ吸収出来そうな仕組みを考えてみました。
Scala的な要素
- Implicit Conversion
- 無限リスト
- Extractor Patternsによるパターンマッチ
ソース
FizzBuzzクラスを作成し、FizzBuzz#applyでどの数までやるか貰っています。
中では前回同様
3つ置きで"Fizz"が出てくるcycle
5つ置きで"Buzz"が出てくるcycle
1から始まるincrement
をzipp(拡張したメソッド)して、mapしてFBでマッチさせて、出す。感じです。
zippは内部的にzipとmapをしていて、単純にTuple2の中身をtoStringして連結しています。
これによって3つのリストをzippで連結した結果は
1, 2, Fizz3, 4, Buzz5, Fizz6, 7・・・
といったリストになってます。
FBオブジェクトのunapplyの中で、要素にFizzかBuzzが含まれるか判定しています。
マッチする場合は
case FB(x) => x.replaceAll("[0-9]","")
が実行され、数字が除去された文字列になります。
class FizzBuzz {
//Seq系のクラスを拡張する。zippメソッドを追加している。
class Zipper[T](val l : Seq[T]){
def zipp[T](l1:Seq[T]):Seq[Any] ={
l.zip(l1).map(p=>p._1.toString + p._2.toString);
}
}
//implicit conversionでSeqをZipperに暗黙的に変換する
implicit def zipper[T](l:Seq[T]) = new Zipper(l)
//指定したIterableを繰り返す無限リスト
def cycle[T](i:Iterable[T]) = Stream.continually(i).flatMap(v=>v)
//指定した値からインクリメントしていく無限リスト
def increment(begin:Int):Stream[Int] = Stream.cons(begin, increment(begin+1))
//Extractor Patterns
object FB {
def unapply(x: String) = if ((x.indexOf("Fizz")> (-1))||(x.indexOf("Buzz")>(-1))) Some(x) else None
}
//実行
def apply(i:Int)={
cycle(List("","","Fizz")).zipp(
cycle(List("","","","","Buzz"))).zipp(
increment(1))
.take(i)
.map(p => p match{
case FB(x) => x.replaceAll("[0-9]", "")
case x => x
})
}
}
for(i <- new FizzBuzz()(100)) println(i)
課題
- zippが問答無用でタプルをtoString連結してる
- map内部の処理が結構無理やり
おわり
コードはSimply Scalaで試せます。
Scalaは面白いなーとつくづく感じました。まさにDSLですね。
FizzBuzzの為だけに用意された拡張。
これだけ色々出来ると、それぞれの担当がオレオレDSLを作ってカオスな感じになってしまいそう。
Scalaをチームでやる時は特にその辺のルール決めが重要だなぁとかなんとか感じました。
あと、可読性が微妙。Scalaのscaladocから実装を眺めてみると、大体いつも挫折します。
謎過ぎて。その辺は慣れなんですが、普及の妨げにはなるだろうなぁという印象です。
でも面白いよ!
おわり。
0 件のコメント:
コメントを投稿