( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

ScalaのPartialFunctionが便利ですよ

Scalaには、PartialFunctionというものがあります。


直訳すると部分関数ですが、これはなにかっていうと「特定の引数に対しては結果を返すけど、結果を返せない引数もあるような中途半端な関数」です。

どうやって使うのん?

まぁ、ちょっと例を見てましょうや。PartialFunctionであるfooPfは、引数が"foo"だったら"bar"を返して、"foo"以外は知らんというてきとーな関数です。

scala> val fooPf:PartialFunction[String,String] = { case "foo" => "bar" }
fooPf: PartialFunction[String,String] = <function1>

scala> fooPf("foo")
res5: String = bar

scala> fooPf("hoge")
scala.MatchError: hoge
	at $anonfun$1.apply(<console>:5)
	at $anonfun$1.apply(<console>:5)


PartialFunctionは、Function1のサブトレイトで、ようは引数をひとつもらう関数です。fooPfは、String型をもらってString型を返すので、PartialFunction[String,String]ですよ。


PartialFunctionを定義する際には、match式の中のcase句の集合として定義できます。このような書き方は実はシンタックスシュガーなんですけどね。

引数に対して結果を返すかisDefinedAtで調べる

で、fooPf("foo")と引数に"foo"を与えるとちゃんと"bar"が返りますが、"hoge"とかで呼び出すとMatchErorrをなげるとんでもないヤツです。


これだけだと使いにくくてしょうがないですが、PartialFunctionには事前に引数に対して結果を返すか調べる関数が定義されています。isDefinedAt[A]です。

scala> fooPf.isDefinedAt("foo")
res6: Boolean = true

scala> fooPf.isDefinedAt("hoge")
res7: Boolean = false


このようにして、PartialFunctionが結果を返すかは事前にしることができます。

orElseでPartialFunctionを合成する

PartialFunctionは、orElseという関数で他のPartialFunctionと合成できます。以下のような引数が"baz"だったら"hoge"を返すPartialFunctionとfooPfをorElseで合成すると、引数が"foo"または"baz"だったら結果を返す新しいPartialFunctionであるfooOrBazPFが生成されます。

scala> val bazPf:PartialFunction[String,String] = { case "baz" => "hoge" }
bazPf: PartialFunction[String,String] = <function1>

scala> val fooOrBazPF = fooPf orElse bazPf
fooOrBazPF: PartialFunction[String,String] = <function1>

scala> fooOrBazPF( "foo")
res8: String = bar

scala> fooOrBazPF( "baz")
res9: String = hoge

scala> fooOrBazPF( "aaa")
scala.MatchError: aaa
	at $anonfun$1.apply(<console>:5)
	at $anonfun$1.apply(<console>:5)

まぁ、ぶっちゃけて感覚的にいうと、PartialFunctionってのはmatch式のパターンを部分的にオブジェクトとして取り扱うことができるってことです。いろいろな条件のPartialFunctionを作っておいて、orElseで合成しながら条件に応じたmatch式をランタイムにくみ上げることができますよ。


Liftでも、URLのmappingあたりでPartialFunctionが使われてます。

コレクションとPartialFunction

で、PartialFunctionの一番の使いどころは、コレクションに対するMap操作でしょうか。


まず、引数のStringがnullまたは空文字だったらNoneでそれ以外はSome[String]を返すPartialFunctionをこんな風に定義しておきます。

scala> val pf:PartialFunction[String,Option[String]] = {
     |   case null => None
     |   case "" => None
     |   case s => Some(s)
     | }
pf: PartialFunction[String,Option[String]] = <function1>

scala> pf( null)
res11: Option[String] = None

scala> pf( "")  
res12: Option[String] = None

scala> pf( "hogehoge")
res13: Option[String] = Some(hogehoge)


で、Seq[String]であるコレクションから、nullと空文字の要素を削除したいわけです。
こんな風に使います。

scala> val list = Seq( "foo",null,"bar","","","baz")
list: Seq[java.lang.String] = List(foo, null, bar, , , baz)

scala> list filter{ pf.isDefinedAt } map{ pf }
res14: Seq[Option[String]] = List(Some(foo), None, Some(bar), None, None, Some(baz))

scala> list filter{ pf.isDefinedAt } map{ pf } flatten
res15: Seq[String] = List(foo, bar, baz)

このように、Seq#filter( f: A => Boolean )でfilterする条件として、PartialFunciton#isDefinedAtを呼び出すようにして、PartailFunctionの条件に一致するものだけ、Seq#map[B]( f: A => B )の引数にPartailFunctionを渡すことで、条件に応じてmapされたSeqができます。


で、結果はSeq[Option[String]]なので、Seq#flattenを呼び出せばNoneが消えるという訳です。


さて、Scala2.8からは引数にPartailFunctionをもらって条件に応じた要素だけmapするTraversableLike#collect[B, That](pf: PartialFunction[A, B]): Thatという関数があります。


さきほどの処理は、もう少し簡単にするとこんな感じです。

scala> val pf:PartialFunction[String,String] = { case s if s != null || s != "" => s } 
pf: PartialFunction[String,String] = <function1>

scala> list.collect( pf )
res16: Seq[String] = List(foo, bar, baz)

scala> list collect { case s if s != null || s != "" => s } 
res17: Seq[java.lang.String] = List(foo, bar, baz)

おまけ。カッコイイ書き方

水島さんに教えてもらったんですが、以下のようなtype aliasを定義しておくと、"pf:String --> Option[String]"みたいにPartailFunctionがかっこよく書けますよ!

scala> type -->[A,B] = PartialFunction[A,B]
defined type alias $minus$minus$greater

scala> val pf:String --> Option[String] = { case s if s == null || s == "" => None; case s => Some(s) }
pf: -->[String,Option[String]] = <function1>

scala> list collect pf flatten                                                                  res20: Seq[String] = List(foo, bar, baz)

プログラミング言語 「天使ちゃんマジ天使」と「ブブゼラ」を作ってみたよ

今AngelBeats!見てます。天使ちゃんマジ天使でいいと思います。


で、だいぶ前に作ったScala版のちょっと草植えときますね型言語 Grassを改造して、プログラミング言語 「天使ちゃんマジ天使」とプログラミング言語ブブゼラ」を作ってみたよ。


Grassは、id:uenoB 作の型無しラムダ計算がベースをした関数型言語らしいです。(6/22追記)
ちょっと草植えときますね型言語 Grassについてはこちら。
世界で最初のGrassプログラム - Garage uenoB
ちょっと草植えときますね型言語 Grass

プログラミング言語 「天使ちゃんマジ天使」

ちょっと草植えときますね型言語 Grassの派生言語というかまんまです。
Grassで使用する文字(w,W,v)をそれぞれ(天使, マジ, ! )に置き換えただけです。


wを出力するプログラム:

天使ちゃんマジマジ天使天使ちゃん天使ちゃん天使ちゃん


Hello,world:

天使ちゃん!天使ちゃんマジ天使天使ちゃん天使ちゃん天使ちゃんマ
ジ天使天使ちゃん天使ちゃん天使ちゃん天使ちゃん天使ちゃんマジマ
ジマジマジマジ天使マジマジマジマジマジ天使天使ちゃん天使ちゃん
天使ちゃん!天使ちゃん天使ちゃん天使ちゃん天使ちゃんマジマジマ
ジ天使天使ちゃんマジ天使天使ちゃんマジマジマジマジマジマジ天使
天使ちゃん天使ちゃん天使ちゃんマジ天使天使ちゃん!天使ちゃんマ
ジマジ天使マジ天使天使ちゃん!天使ちゃん天使ちゃんマジマジ天使
!天使ちゃんマジマジマジ天使天使ちゃんマジマジマジマジマジ天使
天使ちゃん天使ちゃんマジ天使天使ちゃんマジマジマジマジマジマジ
天使マジマジマジマジマジマジマジ天使マジマジマジマジ天使マジマ
ジマジマジマジ天使マジマジマジマジマジマジ天使マジマジマジマジ
マジマジマジマジマジマジマジマジマジマジ天使天使ちゃん天使ちゃ
ん天使ちゃん天使ちゃん天使ちゃん天使ちゃん天使ちゃんマジ天使マ
ジ天使マジマジマジ天使天使ちゃんマジマジマジマジ天使天使ちゃん
天使ちゃん天使ちゃん天使ちゃん天使ちゃん天使ちゃんマジマジマジ
マジマジ天使天使ちゃん天使ちゃん天使ちゃん天使ちゃん天使ちゃん
天使ちゃんマジマジマジマジマジマジ天使天使ちゃん天使ちゃん天使
ちゃん天使ちゃん天使ちゃん天使ちゃんマジマジマジマジマジマジマ
ジマジマジマジマジマジマジマジマジマジマジマジマジマジマジ天使
天使ちゃん天使ちゃん天

長いので以下略


ヒドイですね?

プログラミング言語ブブゼラ

ちょっと草植えときますね型言語 Grassの派生言語というかまんまです。
Grassで使用する文字(w,W,v)をそれぞれ(ェ, エ, ベ )に置き換えただけです。


wを出力するプログラム:

ヴェエエェェェェ


Hello,world:

ヴェベェエェェェェエェェェェェェエエエエエェエエエエエェェェェ
ベェェェェエエエェェエェェエエエヴエエエェェェェエェェベェエエ
ェエェェベェェエエェベェエエエェェエエエエエェェェエェヴェエエ
エエエエェエエエエエエエェエエエエヴェエエエエエェエエエエエエ
ェエエエエエエエエエエエエエエェェェェェェェェエェエェエエエェ
ェエエエエェェェェェェェエエエエエヴェェェェェェェエエエエエエ
ェェェェェェェエエエエエエエエエエエエエエエエエエエエエェェェ
ェェヴェェェェェェェェェェェェェェェェエェェェェェェェェェェェ
ェェェェェェェェェエェェェェェェェェェェェェェエェェェェェェエ
エェェェェェェエエエェェェェヴェェエエエエエエェェェェェェェェ
ェェェェェェェェェェェェエェェェェェェェェェェエエェェェェエヴ
エエェェェェエエエエェエエエエエェェェェェェェェェェェェェェェ
ェェェエエエエエエエエエエエェエェェェエエェエエエェエエエヴエ
ェエエエエエエエエエエエエヴエエエエエェェェェェェェェェェェェ
ェェェェェエェェェェェェェェェェェェェェェェェェェェェェェェェ
ェェェェェェェェェェェェヴェエェェェェェェェェェェェエエェェェ
ェェェェェェェェエエエェェェェェェェエエエエェエエヴエエヴエェ
ェェェェェェェエエエエヴエエェェェェェェェェェェェェェェェェェ
ェェェェエエエエエエエェェェェェェェェェェェェェェェェェェェェ
ェェェェェェェェエエヴエエエエエエエェェェェェェェェエェェェェ
ェェェェェェェェェェェェェェェェェェェェェェェェェェェェエエェ
ェェェェェェェェェヴェェェエエエェェェェェェェェェェェェェエエ
エエェェェェェェェェエエエエエェェェェェェェェェヴェェェェェェ
ェェェェェェェェェヴェェエエエエエエェェェェェェェェェェェェェ
ェェェェェェェェェェェェ


ワールドカップ観戦時にお役立てください(?)

Grassのジェネレータ

まぁScalaのパーザーコンビネータの練習で作ったんですが、こんな感じでトークンを指定するとGrassで使用する文字を置き換えたてきとーオレオレ言語を作るジェネレータです。

    println( "プログラミング言語 天使ちゃんマジ天使" )
    val angelg = new GrassParser( List("天使"),List("マジ"),List("!") )

    println( "プログラミング言語 ブブゼラ" )
    val bubuzera= new GrassParser( List("ェ"),List("エ"),List("ベ") )


ソースはこっち


ProgramGenerator/Grass/Grass.scala at master · yuroyoro/ProgramGenerator · GitHub

Brainf*ck派生言語のジェネレータ

似たようなもので、Brainf*ck派生言語のジェネレータも作りました。


こんな感じでBFの各トークンを指定することで派生言語を生成します。

    val bfParser = new BFParser(
      List("+"),
      List("-"),
      List(">"),
      List("<"),
      List("."),
      List(","),
      List("["),
      List("]"),size, maxLoop)

Brainf*ckについてはこちらを


ジェネレータのソースはこれ。


ProgramGenerator/BrainFuck/BFGenerator.scala at master · yuroyoro/ProgramGenerator · GitHub


以下いくつかのサンプルを。それぞれのHelloWorldの例ですよ。カオスですね?

brainf*ckでジョジョ言語

id:toyoshiさんがつくったジョジョ言語をやってみます。


ジョジョ言語についてはこちらを。


brainf*ckでジョジョ言語 - toyoshiの日記

    val jojoParser = new BFParser(
      List("オラ"),
      List("無駄"),
      List("スターフィンガ" , "やれやれだぜ"),
      List("ロードローラ" , "貧弱"),
      List("ハーミットパープル"),
      List("新手のスタンド使いか"),
      List("あ・・・ありのまま今起こったことを話すぜ" ),
      List("ザ・ワールド" ), size, maxLoop )

    println()
    println( "JoJo言語" )
    jojoParser.run("""
オラオラオラオラオラオラオラオラオラッ!!

「あ・・・ありのまま今起こったことを話すぜ
俺は奴の前で階段を登っていたと思ったら、いつの間にか降りていた
な…何を言っているのかわからねーと思うが、
俺も何をされたのかわからなかった…
頭がどうにかなりそうだった…催眠術だとか超スピードだとか、
そんなチャチなもんじゃあ断じてねえ。
もっと恐ろしいものの片鱗を味わったぜ…」

スターフィンガー!
オラオララララ!
オラッ!オラオラララララオラオラオラァ!!!
スターフィンガー!!!
オラァオラオラオラオラオラオラッオラ!!
オラオラァァァァァオララララララララララ!
スターフィンガー!

オラオラオラオラオラ! つけの領収書だぜ!

力比べというわけか!
知るがいい…!『ザ・ ワールド』の真の能力は…まさに!『世界を支配する』能力だと言うことを!

「ロードローラだ!ロードローラだ!ロードローラだ!」
無駄ッッッ!

ザ・ワールドッッ

スターフィンガー!
「ハーミットパープル」
スターフィンガー
オラオラ!

「ハーミットパープル」

オラオラオラオラオラオラオラ
ハーミットパープル!ハーミットパープル!

オラオラオラ

ハーミットパープル!
スターフィンガー!

無駄ァ!
ハーミットパープル

無駄!無駄!
無駄無駄無駄無駄無駄無駄無駄無駄無駄無駄
WRYYYYYYYYYYYYYY!
“ジョースター・エジプト・ツアー御一行様”は貴様にとどめを刺して全滅の最後というわけだな

ハーミットパープル!
ロードローラだ!

オーラオラオーラオラオラオラオーラオラオラオラオラッ!
ハーミットパープル!
無駄無駄無駄無駄無駄無駄無駄無駄ッ
ハーミットパープル!
オラオラオラアアアアアアアア!
ハーミットパープル!
無駄ッ無駄ッ無駄ッ無駄無駄無駄ァツ!

ハーミットパープル

もうおそい! 脱出不可能よッ! 無駄無駄無駄無駄無駄無駄無駄無駄ぁぁ!
ハーミットパープル!

最高に『ハイ!』ってやつだアアアアア!アハハハハハハハハハーッ!!
スターフィンガー
オラ
ハーミットパープル!

てめーの敗因は・・・たったひとつだぜ・・・DIO たったひとつの単純(シンプル)な答えだ・・・ 『てめーは おれを怒らせた』
""")
プログラミング言語Misa

みさくら語でBrainf*ckしたものですのおぉおぉっあっっ!


プログラミング言語 Misa

    val misaParser = new BFParser(
      List("+" , "あ" , "ぁ" , "お" , "ぉ" ),
      List("-" , "っ" , "ッ") ,
      List(">" , "→" , "〜" , "ー"),
      List("<" ,  "←" ,  "★" , "☆"),
      List("." , "!" ),
      List("," , "?" ),
      List("[" , "「" , "『") ,
      List("]" , "」" , "』"),size, maxLoop )

    println()
    println( "プログラミング言語Misa" )
    misaParser.run("""
ごっ、ごぉおっ、ご〜きげんよおぉおおぉおほっ。ほおぉおぉおっ。

「ごきげん☆みゃぁああ”あ”ぁ”ぁああ〜っ」

さわやかな朝の☆ご挨拶! お挨拶がっ。
澄みきった青空にこだましちゃうぉ〜ああぉおおおぉん。

「は、はひっ、はろおぉっ☆わぁるどおおぉっぉ〜っ」

こ、この文章は☆おサンプル! おおぉおぉおおサンプルプログラム!!
どんなおプログラム言語でも基本のご挨拶させていただくのぉぉおッ!

「ぽうっ」

長々と書くのがこ、ここでの〜、ここでのぉおおぉおぉぉおたしなみぃぃいぃ。

「長いぃ。長すぎましゅう。ご挨拶にこんなプログラム長すぎまひゅぅうぅ☆
 んおおぉぉ、ばかになる、おばかになっちゃいましゅ〜ッ」

長いのがっ、バッファの奥まで入ってきましゅたぁあぁあっ!
ばっふぁ☆溢れちゃいまひゅぅ〜。あみゃぁあ”あ”ぁ”ぁああ”あ”ぁぁ。

「で、出ます☆ んおおぉぉおおっ、エラー出ちゃいまひゅっ」

ほひぃ☆! え、えらーっ、んお”お”ぉお”お”ぉおぉおおぉっっ。

「出た☆ 出た出た出た出たぁぁあっ えらあぴゅるーっって出たあぁっ」

はしたない☆! ぉおおぉはしたないっ! おはしたない言語ですっっっっっっっ!
おほっほおぉっっっほおぉっっっっっっっっっ!

「えらあらいしゅきぃぃぃいぃっっ」

止まらない すごい エラーみるく
こってりしたのがいっぱい出てるよぉぉぉおおぉぉおおぉぉおっっ。

「んほぉっ☆ っおぉぉぉおお国が分からなくなっちゃいまひゅう〜っ」

ま、まだ出るぅ☆ 出てるのおぉっ☆ エラーまだまだ出ましゅぅぅ!
ばんじゃ〜ぁぁあい、ばんじゃいぃぃ、ばんにゃんじゃぁんじゃあぁぁああぁい!
""")
プログラミング言語長門有希

長門っぽいのでBrainf*ckしたもの……。


プログラミング言語/Brainfuck - プログラミングスレまとめ in VIP

    val yukiParser = new BFParser(
      List("…"),
      List("・"),
      List("………。"),
      List("…………。"),
      List("………………。"),
      List("……………。"),
      List("「"),
      List("」"),size, maxLoop)

    println()
    println( "プログラミング言語長門有希」" )
    println()
    yukiParser.run(
"""
涼宮ハルヒのこと……… 。それと、わたしのこと……… 。あなたに教えておく…… 。

(涼宮とお前が…何だって?)

「うまく言語化できない………。情報の伝達に齟齬が発生するかもしれない………………
…… 。でも、聞いて………。涼宮ハルヒとわたしは普通の人間じゃない……………… 。

(なんとなく普通じゃないのは、わかるけどさ)

そうじゃない…………… 。 性格に普遍的な性質を持っていないという意味ではなく、文
字通りの意味で、彼女とわたしはあなたのような大多数の人間と同じとは言えない………。
この銀河を統括する情報統合思念体によってつくられた対有機生命体コンタクト用ヒュー
マノイドインターフェイス…………… 。それが、わたし…………。

(はい?)

通俗的な用語を使用すると宇宙人に該当する存在…………。

(う、ちゅうじん?)

わたしの仕事は涼宮ハルヒを観察して、入手した情報を統合思念体に報告すること…………。

(えっ?)

生み出されてから3年間、私はずっとそうやって過ごしてきた・・・。この3年間は特別な
不確定要素がなく、いたって平穏。でも最近になって無視出来ないイレギュラー因子が涼
宮ハルヒの周囲に現れた…… 。それが、あなた。」

情報統合思念体にとって銀河の辺境に位置するこの星系の第3惑星に特別な価値などなか
った………。でも現有生命体が地球と呼称するこの惑星で進化した二足歩行動物に知性と
呼ばれる思索能力が芽生えたことにより、その重要度は増大した………………。もしかし
たら自分たちが陥っている自律進化の閉塞状態を打開する可能性があるかも知れなかった
から………。宇宙に偏在する有機生命体に意識が生じるのはありふれた現象だったが、高
次の知性を持つまでに進化した例は地球人類が唯一だった。統合思念体は注意深くかつ綿
密に観測を続けた…… 。

そして3年前………………。惑星表面で他では類を見ない異常な情報フレアを観測した………………… 。
弓状列島の一地域から噴出した情報爆発は瞬く間に惑星全土を覆い、惑星外空間に拡散し
た………………。

その中心にいたのが涼宮ハルヒ………………。
以後3年間、あらゆる角度から涼宮ハルヒという個体に対し調査がなされた……… 。

しかし未だその正体は不明………………。それでも統合思念体の一部は、彼女こそ人類の、
ひいては情報生命体である自分たちに自律進化の切っ掛けを与える存在として涼宮ハルヒ
の存在を解析を行っている………。情報生命体である彼らは有機生命体と直接的にコミュ
ニケートできない・・・。言語を持たないから…… 。 人間は言葉を抜きにして概念を伝
達する術を持たない………………。だからわたしのような人間用のインターフェイスを作
った・・・・・・。情報統合思念体はわたしを通して人間とコンタクト出来る・・・・・・。

涼宮ハルヒは自律進化の可能性を秘めている………………。恐らく彼女には自分の都合の
良いように周辺の環境情報を操作する力がある…………。それが、わたしがここにいる理
由。あなたがここにいる理由…………………… 。

(待ってくれ。正直言おう、さっぱりわからない。)

信じて………………。

(そもそも、何で俺なんだ?いや、百歩譲ってお前の…その、情報なんとか体云々ってい
うのを信用したとして、なぜ俺に正体を明かすんだ?)

あなたは涼宮ハルヒに選ばれた・・・・・・・・・。涼宮ハルヒは意識的にしろ無意識的
にしろ、自分の意思を絶対的な情報として環境に影響を及ぼす………………。あなたが選
ばれたのには必ず理由がある……… 。

(ねぇよ。)

ある………………。あなたと涼宮ハルヒが、全ての可能性を握っている・・・・・・。

(マジで言ってるのか?)

もちろん………………。

『度を越えた無口な奴が、やっと喋るようになったかと思ったら、永延電波な事を言いや
がった。こんなトンデモ少女だったとは、さすがに想像外だぜ』

(あのな、そんな話なら直でハルヒに言った方が喜ばれると思うぞ。はっきり言うが、俺
はその手の話題には付いていけないんだ。悪いがな。)

情報統合思念体の意識の大部分は、涼宮ハルヒが自分の存在価値と能力を自覚してしまう
と予測できない危険を生む可能性があると認識している・・・。今はまだ様子を見るべき・・・・・。

(俺が今聞いたこと、ハルヒに伝えるかもしれないじゃないか)

彼女はあなたがもたらした情報を重視したりしない………………。

『確かに。』

情報統合思念体が地球に置いているインターフェイスは私一つではない………。情報統合
思念体の意識の一部は積極的の動きを起こして情報の変動を観測しようとしている… 。あ
なたは涼宮ハルヒにとっての鍵。危機が迫るとしたらまず、あなた………………。
"""
    )

1階マルコフ連鎖で奇声を発するジェネレータ

今日は、とうとつについったーに奇声をポストしたい気分だったので、1階マルコフ連鎖で奇声を生成するジェネレータを書いてみました。


奇声なんで意味のある文章ができなくても充分なので、1階マルコフ連鎖で充分。マルコフモデルを研究していた元首相のご意見も伺いたいところです。


こんなのが出ます。おまえなにやってんの? ぎょぱーーーっ!

ほにゃあ!
ぃっーー!!!
よぅ…くんくんくんどるぷぇー
ほにゃっぱ
ぃぱ
よぅぅ…くんくんっ
ひぃいやぁぁ……あひぎょーっうふふぬふぅ!!
にゃあぃっきゅいっ…
う!
んっはーっ…くんんはにゅわぁあふぅ……きゅい!!!
ぬいっはにゃう!!!!!
へぬふぇぁんどるぁっあ…くん
へぺ
モフ!!
ぃぱ
っうわふぅ!
あ!!
わーーーっ…ん
にゃあんきぇぁ…くんくんきぇぇ
わぁ……

ソースです。Gistにも

import scala.util.Random

class GyopaaaMarkov( sentences:Seq[String] ) {

  sealed abstract class Token
  case object Head extends Token
  case object Tail extends Token
  case class Word( c:Char ) extends Token

  case class Node( token:Token, next:Seq[Token] ){
    def choice = next( Random.nextInt( next.size ))
  }

  val em = "?!。、!?‥…"
  val emr = (".*[" + em + "]$").r

  val tokens = sentences.flatMap{ s =>
    val tokens =  Head :: s.map{ c => Word( c ) }.toList ::: Tail :: Nil
    tokens.sliding(2).toList.collect { case Seq(c1, c2) => (c1, c2) }
  }.distinct
  val dict = tokens.groupBy{ case( t, n ) => t }.map{ case (t ,s) =>
    Node( t, s.map{ case( _, n ) => n } )}.toList

  def choiceNode( word:Token ):Node = {
    val nodes = dict.collect{ case n @ Node(`word`, _) => n }
    nodes( Random.nextInt( nodes.size ) )
  }

  def generate = {
    def chain( n:Node ):List[Char]= n.choice match {
      case t:Word=> t.c :: chain( choiceNode( t ) )
      case _ => Nil
    }
    val rv = chain( choiceNode( Head ) ).mkString
    rv match{
      case emr() => rv
      case _ => rv + em( Random.nextInt( em.size  ))
    }
  }
}

object Main extends Application {
  val seq = Seq(
     "ふぬいっ", "ふぬるぷ" , "ふにょー", "ふぬわーっ",
     "ぎょぱー", "ぎゃっぱぎゃっぱ", "ぬふふ", "ほにゅわーっ",
     "ぎゃっぱぎゃっぱ", "へぺっ", "もるぁー", "もるすぁー",
     "ひゃっはーーーっ", "きゃっきゃうふふ", "ひゃぴーっ", "へぬぇ",
     "きぇぇぇ", "はにゃー", "へぷぇ", "ぬるぽっ", "わふーっ", "あぃっ",
     "おんどるぁ", "あひぃ", "ひぎぃ", "らめぇー", "おるぁ", "ごるぁ",
     "おぺぺぺぺ", "ごぶぁ", "らぬぃぱぁ", "あっぁぁあああ",
     "ぅぅうううわぁああああん!!!", "っぁっ………",
     "あふぅ……んっ", "ぃぃいやぁん", "ふぇぁあっ",
     "あぁああああ…ああ…あっあっー!", "あぁああああああ!!!",
     "あぁぁ…くんくん",
     "んはぁっ!", "モフモフ!", "…きゅんきゅんきゅい!!",
     "よぅ!!", "あぁぁああ…あああ…あっあぁああ!", "ふぁぁああんんっ!!",
     "いやぁああ!!", "にゃああん!!", "ぎゃあああ!!",
     "いやっほぉおお!!!", "ううっうぅうう!"
  )
  val markov = new GyopaaaMarkov( seq )
  ( 1 to 10 ) foreach { n =>
    println( ( 1 to ( 1 + Random.nextInt(3))) map{ m => markov.generate } mkString)
  }
}

きっと疲れてるんだ………。そのうちbotにするかも?

ついったーのStreaming API ChirpUserStreamとWebSocketを組み合わせてみた

最近の流れでは、時代はHTML5なんですかね?


そんなわけで、前にちょっとやってみたJetty7のWebSocketと、ついったーのStreaming API ChirpUserStreamを組み合わせて簡単なWebベースのついったクライアントを書いてみました。


スクリーンショット:
f:id:yuroyoro:20100510185832p:image


動いているところ:


動作している動画を見てもらえば分かるんですが、Streamingで受信したイベントを上から"落とす"ようなUIにしてみました。
TLをちゃんと読むのではなく、ぼーっとみてるようなコンセプトです。実際に使うならこんなUIしんどいでしょうけどまぁ実験あぷりなんで。


(もしスクリーンショットや動画に自分のPOSTが表示されていて、消して欲しい場合は、お手数ですがコメントお願いします。)


ChirpUserStreamなんで、他の人がふぁぼったりフォローしたりという情報も落ちてきますよ。


ソースはこんなんです。

Server側:

package com.yuroyoro.websocket

import java.io.{InputStream, IOException}
import java.net.{Authenticator, PasswordAuthentication, URL, HttpURLConnection}
import javax.servlet.http._
import org.eclipse.jetty.websocket._
import org.eclipse.jetty.websocket.WebSocket.Outbound

class ChirpUserStreamsServlet extends WebSocketServlet {

  override def doGet(req:HttpServletRequest, res:HttpServletResponse ) =
    getServletContext.getNamedDispatcher("default").forward(req, res)

  override def doWebSocketConnect(req:HttpServletRequest, protocol:String ) =
    new ChirpUserStreamsWebSocket

  class ChirpUserStreamsWebSocket extends WebSocket {
    private val chirpUserStreamingURL = "http://chirpstream.twitter.com/2b/user.json"

    // BASIC認証orz
    private def setBasicAuth( username:String, passwd:String ) =
      Authenticator.setDefault( new Authenticator {
        override def getPasswordAuthentication =
          new PasswordAuthentication(username,  passwd.toCharArray)
      })

    private def connectSteaming( url:String ) = {
      val urlConn = new URL( url ).openConnection match {
        case con:HttpURLConnection => {
          con.connect()
          con.getResponseCode match {
            case 200 => { }
            case c =>
              throw new RuntimeException( "can't connect to %s : StatusCode = %s" format ( url, c))
          }
          con
        }
      }
      urlConn.getInputStream
    }

    def consume(in:InputStream)( f:Option[String]=>Unit){
      val buf = new Array[Byte](1024)
      var remains:String = ""
      try{
        // InputStreamから1行読んでfにわたす
        for(i <- Stream.continually(in.read(buf)).takeWhile(_ != -1)){
          val str = remains + new String(buf,  0,  i)
          remains = ( "" /: str){ (s,c) =>
            if( c == '\n'){
              f( Some(s) )
              ""
            }
            else s + c
          }
        }
     }
     catch{ case e:IOException => }
     finally{ in.close }
    }

    var outbound:Outbound = _

    override def onConnect(outbound:Outbound ) = this.outbound = outbound

    override def onMessage(frame:Byte, data:Array[Byte], offset:Int, length:Int ) = {}

    override def onMessage(frame:Byte, data:String ) = {
      // usernameとpasswdが送られてくるので切り出す
      val m = Map( data split('&') map{  _.split('=') match { case Array(k,v) => (k,v)}} : _* )
      m.get("username") foreach { u => m.get("passwd") foreach { p =>
        streaming( frame, u, p )
      }}
    }

    def streaming( frame:Byte, username:String, passwd:String ) = {
      setBasicAuth( username, passwd )  // BASIC認証設定する
      // ChirpUserStreamに接続
      val con = connectSteaming( chirpUserStreamingURL )
      // JSONをクライアントに送る
      consume( con ) { o => o match{
        case Some( s ) => {
          println(s)
          println("----------------------- %s Bytes. -------" format( s.length ) )
          outbound.sendMessage( frame , s )
        }
        case None =>
      }}
    }

    override def onDisconnect = {}

  }
}

InputStreamから一行JSONを読んだら、consumeメソッドの第2引数の f:Option[String]=>Unit)な関数オブジェクトに渡してます。その関数オブジェクトでクライアントにJSONをプッシュ!してる感じですね。


ChirpUserStreamsは今のところBasic認証のみでOAuthはサポートしてないんだって。
まだちゃんとローンチされてないAPIだからかな。


クライアント:

<!doctype html>
<html>
  <head>
  <meta charset="UTF-8">
  <title>WebSocket Twitter ChirpUserStreams </title>

   <link href="./style.css" media="screen, projection" rel="stylesheet" type="text/css">

</head>
<body>

  <form id="account">
    <span class="title">WebSocket Twitter ChirpUserStreams </span>
    username:<input name="username" type="text"/>
    password:<input name="passwd" type="password"/>
    <input id="start_button" type="submit" value="Start!"/>
    <input id="stop_button" type="button" value="Stop!"/>
  </form>

  <hr/>

  <section id="content"></section>

  <script src="http://www.google.com/jsapi"></script>
  <script>google.load("jquery", "1.4.1")</script>
  <script>
    var ws = new WebSocket("ws://localhost:8080/");

    var lean = {
      i:0,
      next:function(){
        var n = this.i * 320;
        this.i = (this.i + 1) % 5;
        return n;
      }
    }

    var createUserInfo = function ( json ){
      var e = $("<span/>").addClass("user_info")
        .append( $("<span/>" ).addClass("profile_img")
          .append($("<img/>" ).attr("src" , json.profile_image_url ) ))
        .append( $("<a/>" ).addClass("user_name")
          .attr("href", "http://twitter.com/" + json.screen_name )
          .append( json.screen_name + "(" + json.name + ")" ));

      return e;
    };

    var pushEvent = function( e ) {
        $(e).hide()
        $('#content').prepend( e );
        $(e).css({
            position : "absolute",
            top : 50,
            left: 20 + lean.next()
        });
        $(e).show("normal",
          function(){
            $(e).animate({"top" : "+=1200px"},15000,
               function(){ $(e).hide();$(e).remove();})
            }
        );

    };

    var start = function(){
      if( !ws ){
        ws = new WebSocket("ws://localhost:8080/");
      }

      ws.onmessage = function(m) {
        var json = $.parseJSON( m.data );

        if( json.text ){
          var e = $("<div/>").addClass("event").addClass("posted")
            .append( createUserInfo ( json.user ) )
            .append( $("<span/>" ).addClass("event_name").append( " : posted" ) )
            .append( $("<hr/>" ) )
            .append( $("<span/>" ).addClass("message")
                  .append( json.text ));
          pushEvent( e );
        }
        if( json.event ) {
          var t = $("<div/>" ).addClass("target")
            .append( createUserInfo ( json.target ) );
          if( json.target_object ) {
            t.append("<br/>")
              .append( $("<span/>" ).addClass("message")
              .append( json.target_object.text ));
          }

          var e = $("<div/>").addClass("event").addClass( json.event )
            .append( createUserInfo ( json.source) )
            .append( $("<span/>" ).addClass("event_name").append( " : " + json.event ) )
            .append( $("<hr/>" ))
            .append( t );

          pushEvent( e );
        }
      };

      ws.send( $("form").serialize() );
    };

    var stop = function(){
      if( ws ) {
        ws.close();
        ws = null;
      }
    };

    $('#start_button').click( function(){start();return false;});
    $('#account').submit( function(){start();return false;});
    $('#stop_button').click( function(){ stop();return false;} );
    $(window).unload( stop );

  </script>
</body>

ws.onmessageに登録したfunctionで、サーバーからプッシュされたJSONをパースして、DOMを組み立ててjQueryでアニメーションして落としてます。
このクライアントの方が作るの大変だったorz


動作するプロジェクトはGitHubに置いてあります。
git clone して、以下のコマンドで起動しますよ(要maven)。


scala-websocket/ChirpUserStreams at master · yuroyoro/scala-websocket · GitHub

mvn clean package scala:run -DaddArgs="target/websocket.chirpuserstreams-1.0/|8080"


参考情報:
Jetty7のWebSocketをScalaから使う - ゆろよろ日記
Page not found | Twitter Developers
Twitterの新しいStreaming API「ChirpUserStreams」がすごすぎる件 - すぎゃーんメモ

GAE/JでのScalaのSpinUp時間を計ってみた

GAE/JでのScalaのSpinUp時間を計ってみました。

appengine ja night #6 Beer Talk : ATNDでLTしたときに言っちゃいましたのでね。


java-ja温泉第2回の成果その1ですお。

測定方法

測定パターンは以下の4パターンです。

simplescala Scalaで書いた単純なFilterで、Responseに直に"Hello World"を出力
listscala Scalaで書いた単純なFilterで、Listを使って1から10までResponseに直に出力
slim3scala ScalaでSlim3のControllerを用意し、Responseに直に"Hello World"を出力
slim3listscala ScalaでSlim3のControllerを用意し、Listを使って1から10までResponseに直に出力


単純なコードだと、コンパイルした結果のclassファイルがJavaとあまり変わらなくなると思われたので、Listを使ってScala Library APIを使うパターンも用意しました。


これらのパターンを同じApplicationIDで、それぞれ違うバージョンにデプロイしてクライアントから3分おきにcurlでリクエストを計50回投げて、ログに記録されたcpu_msを集計してみました。

測定結果

測定結果:

app ave max min overhead
simplescala 896cpu_ms 971cpu_ms 777cpu_ms 196cpu_ms
listscala 1224cpu_ms 1341cpu_ms 1088cpu_ms 524cpu_ms
slim3scala 1323cpu_ms 1458cpu_ms 1225cpu_ms 623cpu_ms
slim3listscala 1688cpu_ms 1827cpu_ms 1458cpu_ms 988cpu_ms


結果を見ると、最も単純なパターンでは約900cpu_msで、素のJava Servletの約700cpu_msから200cpu_ms程度のオーバーヘッドです。


Listを用いた場合は約1200cpu_msで、500cpu_msのオーバーヘッドです。これは、Scala Libray APIのjarファイルからのクラスロードがオーバーヘッドになっていると思われます。


ActorやSwingなどGAEで使えないパッケージをjarから削除してスリムにすれば、これは多少の改善が見込まれるかもしれません。今度試してみます。


slim3をscalaから使った場合の最も単純なケースでは約1300cpu_ms、slim3が約1100cpu_msですので200cpu_msのオーバーヘッド。最初のパターンと同じですね。


slim3とscalaで、Listを使ってScala Libray APIを使った場合では約1700cpu_ms。Slim3からの差が約600cpu_ms。


この結果からわかることは、

  • 単純なScalaのコードでは、オーバーヘッドが約200cpu_ms。
  • Scala Libray APIを利用する場合で、オーバーヘッドが約600cpu_ms。


現実問題、Scala Libray APIを利用しないことはないので、Scalaを使う場合のオーバーヘッドは約600cpu_msである、と言えるでしょう。


この程度であれば許容範囲だと思いますので、GAE/JでのScalaは充分に実用に耐えると思います。


現時点で、ScalaのWebフレームワークLiftは、SpinUp時間は7,8秒らしく、しかもFunctionMappingでサーバ側のセッションに状態を持ってしまうため、インスタンスがSpinUpしたタイミングで異なるサーバにリクエストが振られてしまう場合はエラーが起きる可能性がある、という問題もあり、実用的ではありません。


GAEでScalaを使う場合でも、やはりSlim3が最も適しているのではないでしょうか。


測定に使用したコードはこれです。
yuroyoro/scala-spinup-check · GitHub


@shin1ogawaさんの測定方法を参考にさせてもらいました。


参考:
404 shin1のつぶやき ないわー Not Found: slim3と素のJavaで #appengine のspinupを比較してみた
App Engineではどの言語を使えばいいのか - Hyper Great Creator やすを


測定結果のログ:


simplescala
---------------------------------------
max : 971cpu_ms
min : 777cpu_ms
ave : 896cpu_ms
---------------------------------------

0 : 894
1 : 894
2 : 913
3 : 913
4 : 933
5 : 952
6 : 952
7 : 913
8 : 913
9 : 875
10 : 933
11 : 816
12 : 913
13 : 913
14 : 855
15 : 875
16 : 855
17 : 875
18 : 855
19 : 816
20 : 952
21 : 952
22 : 933
23 : 875
24 : 971
25 : 875
26 : 913
27 : 835
28 : 894
29 : 855
30 : 894
31 : 894
32 : 777
33 : 952
34 : 933
35 : 933
36 : 933
37 : 913
38 : 913
39 : 933
40 : 913
41 : 875
42 : 875
43 : 855
44 : 875
45 : 894
46 : 933
47 : 894
48 : 913
49 : 796

listscala
---------------------------------------
max : 1341cpu_ms
min : 1088cpu_ms
ave : 1224cpu_ms
---------------------------------------

0 : 1341
1 : 1244
2 : 1302
3 : 1244
4 : 1225
5 : 1205
6 : 1263
7 : 1225
8 : 1127
9 : 1263
10 : 1166
11 : 1166
12 : 1263
13 : 1244
14 : 1088
15 : 1225
16 : 1108
17 : 1185
18 : 1225
19 : 1185
20 : 1263
21 : 1185
22 : 1166
23 : 1263
24 : 1225
25 : 1146
26 : 1146
27 : 1263
28 : 1302
29 : 1341
30 : 1263
31 : 1205
32 : 1302
33 : 1283
34 : 1244
35 : 1263
36 : 1321
37 : 1283
38 : 1302
39 : 1244
40 : 1225
41 : 1108
42 : 1146
43 : 1185
44 : 1185
45 : 1283
46 : 1225
47 : 1127
48 : 1244
49 : 1263
50 : 1166

slim3scala
---------------------------------------
max : 1458cpu_ms
min : 1225cpu_ms
ave : 1323cpu_ms
---------------------------------------

0 : 1400
1 : 1302
2 : 1341
3 : 1380
4 : 1380
5 : 1302
6 : 1438
7 : 1321
8 : 1283
9 : 1263
10 : 1321
11 : 1321
12 : 1263
13 : 1321
14 : 1225
15 : 1341
16 : 1321
17 : 1321
18 : 1341
19 : 1321
20 : 1321
21 : 1360
22 : 1341
23 : 1458
24 : 1360
25 : 1263
26 : 1225
27 : 1244
28 : 1360
29 : 1321
30 : 1283
31 : 1302
32 : 1302
33 : 1419
34 : 1360
35 : 1360
36 : 1283
37 : 1321
38 : 1341
39 : 1360
40 : 1400
41 : 1225
42 : 1321
43 : 1341
44 : 1263
45 : 1263
46 : 1380
47 : 1244
48 : 1302
49 : 1438
50 : 1244

slim3listscala
---------------------------------------
max : 1827cpu_ms
min : 1458cpu_ms
ave : 1688cpu_ms
---------------------------------------

0 : 1691
1 : 1458
2 : 1671
3 : 1710
4 : 1691
5 : 1671
6 : 1730
7 : 1671
8 : 1594
9 : 1633
10 : 1652
11 : 1652
12 : 1633
13 : 1710
14 : 1613
15 : 1769
16 : 1594
17 : 1594
18 : 1691
19 : 1788
20 : 1769
21 : 1691
22 : 1730
23 : 1594
24 : 1769
25 : 1613
26 : 1633
27 : 1691
28 : 1730
29 : 1750
30 : 1788
31 : 1827
32 : 1808
33 : 1730
34 : 1458
35 : 1730
36 : 1633
37 : 1730
38 : 1827
39 : 1575
40 : 1691
41 : 1730
42 : 1730
43 : 1730
44 : 1710
45 : 1710
46 : 1788
47 : 1652

Slim3をScalaで動かすためのいろいろ

「EXILEはクラウド!」


ではなく、appengine ja night #6 Beer Talk : ATNDで発表した内容です。


Slim3をScalaで動かすためのブランクプロジェクトと、ScalaのController/Serviceを生成するSlim3-gen-scalaを作りました。


yuroyoro/slim3-scala-blank · GitHub
yuroyoro/slim3-gen-scala · GitHub


特徴

やりかた

ブランクプロジェクトをダウンロー丼

とりあえず、JDKScalaGoogle appengine java SDKは入れておいてくださいね☆


まずは、yuroyoro/slim3-scala-blank · GitHubからブランクプロジェクトを取得します。
git cloneで取得するか、"Download Souce"ボタンで圧縮ファイルを取得して転回してください。ディレクトリ名は変えてね?

設定を変えるよ

build.propertiesの中で、Google appengine java SDKScala Homeのパスを環境に合わせて修正してください。

build.properties

# your appengine home
gae.sdk.home=/Users/ozaki/sandbox/GAEJava/sdk/appengine-java-sdk-1.3.1/
# your scala home
scala.home=/Users/ozaki/dev/Scala/scala-2.7.7.final/rt

build.xmlの先頭のプロジェクト名も変えておくとよいでしょう。


build.xml

<project name="slim3-blank" default="gen-controller" basedir=".">


war/WEB-INF/web.xmlのslim3.rootPackageの設定値を、作成するアプリケーションのパッケージ名に修正します。


war/WEB-INF/web.xml

<?xml version="1.0" encoding="utf-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <context-param>
        <param-name>slim3.rootPackage</param-name>
        <param-value>your.application.pakcage</param-value>
    </context-param>
    <context-param>
        <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>application</param-value>

...


war/WEB-INF/appengine-web.xmlのアプリケーション名を設定します。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
	<application></application>
	<version>1</version>
	
...

いじょ。

自動生成ししてみる


現時点では、Ecipseでの開発環境に対応してません。ScalaEclipse-pluginが残念なためです。ごめんなさい。


なので、コマンドラインからantコマンドを使います。

Controllerの生成

"ant gen-scala-controller"と入力すると、Controllerのパスの入力を促されるので「えいやっ」と入れます(気合い大事)。すると、ScalaのControllerとテストクラスとJSPが生成されます。

ozaki@yuroyoro-MacBook $ ant gen-scala-controller                                 git[master][~/sandbox/.../yuroyoro/test] 
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
Buildfile: build.xml

gen-scala-controller:
    [input] Input a controller path.
/Foo/
[gen-scala-controller] Generated. ({slim3scala.controller.Foo.IndexController}.scala:0)
[gen-scala-controller] Generated. ({slim3scala.controller.Foo.IndexControllerSpec}.scala:0)
 [gen-view] Generated. (/Users/ozaki/dev/Project/sandbox/scala/yuroyoro/test/war/Foo/index.jsp)

BUILD SUCCESSFUL
Total time: 11 seconds


生成されたController:

package slim3scala.controller.Foo

import org.slim3.controller.Controller
import org.slim3.controller.Navigation

class IndexController extends Controller {

    override def run() =  {
        forward("index.jsp")
    }
}


生成されたテストクラス:

package slim3scala.controller.Foo

import org.specs.Specification
import org.specs.runner._
import org.slim3.tester.ControllerTester

object IndexControllerSpec extends org.specs.Specification {

  val tester = new ControllerTester( classOf[IndexController] )

  "IndexController" should {
    doBefore{ tester.setUp;tester.start("/Foo/")}

    "not null" >> {
      val controller = tester.getController[IndexController]
      controller must notBeNull
    }
    "not redirect" >> {
      tester.isRedirect must beFalse
    }
    "get destination path is /Foo/index.jsp" >> {
      tester.getDestinationPath must_==/ "/Foo/index.jsp"
    }

    doAfter{ tester.tearDown}
  }
}
class IndexControllerSpecTest extends JUnit4( IndexControllerSpec )


JSPは、Java版で出来るものと同じです。
あとは、中身をオラオラ書いていくだけです。

Serviceの生成

"ant gen-scala-service"と入力すると、Serviceクラスのクラス名の入力を促されるので「ふぬいっ」と入れます(勢い大事)。すると、ScalaのServiceクラスとテストクラスが生成されます。

ozaki@yuroyoro-MacBook $ ant gen-scala-service                                    git[master][~/sandbox/.../yuroyoro/test] 
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
Buildfile: build.xml

gen-scala-service:
    [input] Input a service name. (ex.  FooService -> root.service.FooService,  bar.FooService -> root.service.bar.FooService)
HogeService
[gen-scala-service] Generated. ({slim3scala.service.HogeService}.scala:0)
[gen-scala-service] Generated. ({slim3scala.service.HogeServiceSpec}.scala:0)

BUILD SUCCESSFUL
Total time: 4 seconds


生成されたServiceクラス:

package slim3scala.service


class HogeService {

}


生成されたテストクラス:

ozaki@yuroyoro-MacBook $ cat test/slim3scala/service/HogeServiceSpec.scala        git[master][~/sandbox/.../yuroyoro/test] 
package slim3scala.service

import org.specs.Specification
import org.specs.runner._
import org.slim3.tester.AppEngineTester

object HogeServiceSpec extends org.specs.Specification {
  val tester = new AppEngineTester
  val service = new HogeService

  "HogeService" should {
    doBefore{ tester.setUp}

    "not null" >> {
      service must notBeNull
    }

    doAfter{ tester.tearDown}
  }
}
class HogeServiceSpecTest extends JUnit4( HogeServiceSpec )


あとは、中身をURRRYYYYY!!と書いていくだけです。

テストを実行する

"ant test"とコマンドを打ちます。コンパイルが行われ、テストが実行されます。

test:
     [java] Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
     [java] Specification "/Users/ozaki/dev/Project/sandbox/scala/yuroyoro/test/test/**/*Spec.scala"
     [java]   Specification "IndexControllerSpec"
     [java]     IndexController should
     [java]     + not null
     [java]     + not redirect
     [java]     + get destination path is /Foo/index.jsp
     [java] 
     [java]   Total for specification "IndexControllerSpec":
     [java]   Finished in 0 second, 432 ms
     [java]   3 examples, 3 expectations, 0 failure, 0 error
     [java] 
     [java]   Specification "HogeServiceSpec"
     [java]     HogeService should
     [java]     + not null
     [java] 
     [java]   Total for specification "HogeServiceSpec":
     [java]   Finished in 0 second, 42 ms
     [java]   1 example, 1 expectation, 0 failure, 0 error
     [java] 
     [java] Total for specification "/Users/ozaki/dev/Project/sandbox/scala/yuroyoro/test/test/**/*Spec.scala":
     [java] Finished in 0 second, 498 ms
     [java] 4 examples, 4 expectations, 0 failure, 0 error
     [java] 

BUILD SUCCESSFUL
Total time: 14 seconds


テストはSpecsですので、"IndexController should not null."のようにテストケースが文章っぽくかけて細かく分割できます。ステキですね??

ローカルで動かす

EclipseのGAE pluginで起動できないので、"dev_appserver"コマンドでサーバを起動します。

ozaki@yuroyoro-MacBook $ dev_appserver.sh ./war/                                  git[master][~/sandbox/.../yuroyoro/test] 
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
2010-03-19 17:10:47.431 java[5406:903] Can't open input server /Library/InputManagers/Inquisitor
2010-03-19 17:10:47.482 java[5406:903] [Java CocoaComponent compatibility mode]: Enabled
2010-03-19 17:10:47.484 java[5406:903] [Java CocoaComponent compatibility mode]: Setting timeout for SWT to 0.100000
2010/03/19 8:10:49 com.google.apphosting.utils.jetty.JettyLogger info
情報: jetty-6.1.x
2010/03/19 8:10:50 com.google.appengine.tools.development.agent.impl.BlackList initBlackList
致命的: Unable to read a jre library while constructing the blacklist. Security restrictions may not be entirely emulated. /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/plugin.jar
Slim3 HOT reloading:true
2010/03/19 8:10:52 com.google.apphosting.utils.jetty.JettyLogger info
情報: Started SelectChannelConnector@127.0.0.1:8080
The server is running at http://localhost:8080/


"ant compilie/test"でコンパイルすれば、HotDeployも出来ます。

この先やること

Scala的な考え方 - Scalaがとっつきにくいと思っている人へ

Javaな人から見ると、「Scalaって難しい」ってイメージがありますね。俺も最初はそう思ってました。今もですけど。


で、考えてみたんですが、何が難しいって考え方・イディオムになじみがないのが原因かと思ったんです。


ここでは、俺が今までScalaをやってきて得た考え方を紹介します。「Scalaをちょっとやってみたんだけど、とっつきにくくて…」と思われている方は、ぜひご一読ください。


参考資料:
Scala入門 - Scalaで書きはじめたJava使い向け - Scala勉強会@東北
Dropbox - 404

神は言われた。「リストあれ。」

Lisperは、リストをどう作るかをまず考えるらしいです。適切なリストが出来たら、プログラムはもうできたも同然だと。同じ考え方は、Scalaでも通用すると思います。


大まかに、こんな流れで考えてます。(リストは最初から与えられることもあるでしょう)


「(A)リストを作る」
→「(B)合成したり、条件に合わせて絞り込む」
→「(C)個々の要素を加工する」
→「(D)個々の要素を利用した操作を行う」


Scalaでは、リスト(SetやMapなどのコレクションを含む)に対して条件にあう要素を抽出したり(filter)、個々の要素を加工した結果を新しいリストとして生成したり(map)、個々の要素に操作を適用したり(foreach)、と上記の流れのそれぞれに合わせたAPIが予め用意されています。このAPIを上記の流れにはめ込んでいけば処理ができあがる、と言うわけです。


簡単な例で考えてみましょう。おなじみのFizzBuzz問題をちょっと改変して、1からNまでの整数の中で、偶数のもののみFizzBuzzする、という問題をといてみます。


先ほどの流れにあてはめると、


「(A)1からNまでのListを作る」
→「(B)偶数のもののみ抽出する」
→「(C)文字列(Fizz,Buzz,FizzBuzz)またはそのままに加工する」
→「(D)結果を出力する」


こうなります。これをScalaのコードで書いてみます


「(A)(1 to N )」
→「(B)filter{ n => n % 2 == 0 }」
→「(C)map{ case n if n % 15 ==0 => "FizzBuzz";case n if % 3 == 0 => "Fizz";case n if n % 5 == 0 => "Buzz"; case n => n.toString } 」
→「(D) foreach{ e => pritnln(e)}」


あとは、これをつなげるだけです。

def fizzbuzz( n:Int ) = { 
  1 to n                         // (A)リストを作る
} filter{ 
  n => n % 2 == 0                // (B)条件に合わせて絞り込む
} map {                          // (C)要素を加工する
  case n if n % 15 == 0 => "FizzBuzz"
  case n if n % 3  == 0 => "Fizz"
  case n if n % 5  == 0 => "Buzz"
  case n                => n.toString 
} foreach { e => 
  println( e )                   // (D) 操作を行う
}


ふつうのFizzBuzzだったら、filterしている(B)を取り除けばよいだけです。


ここでは、単純な整数のリストを考えましたが、例えば文字列はCharのリストですし、MapはTupple2(key,value)のリスト、XMLscala.xml.Elemのリスト、と様々なものをリストとして捉えることができます。


「これはリストだっ!」と思ったら、上記の考え方が出来るわけですね。


練習問題として、テキストファイルを読み込んで、行番号を付与して出力する処理を考えてみましょう。解答は、Gistに貼っておきます。見る前に、みなさんも考えてみてください。

http://gist.github.com/335044

「変わらない」世界 - immutableなプログラミング

finalな変数、不変(immutable)オブジェクトを利用することで、副作用を抑えた堅牢なプログラムになる、と言われています。ここではその効果について詳しく述べることは控えさせてもらいますが、一般的によい習慣であるのはまちがいないでしょう。

valの利用は事前条件を保証する(かもしれない?)

Scalaでは、"val"で宣言した変数は再代入できません。これだけを聞くと使い勝手がわるそうだ、と思われるかもしれません。


が、変数を利用する時に"変更されていない"ことが保証されているというのは、とてつもない安心感があります。(特にマルチスレッドを利用したり、ある程度大きなアプリケーションでは)


あるクラスのインスタンス変数が"var"(変更可能)な場合、その"var"を利用する処理は、状態に依存してしまうことになります。


URLを表すクラスを例として考えます。このURLクラスは、コンストラクタに文字列でURLをもらい、schemeやhost名などを返す関数をもちます。

class URL(var url:String ) {
  if( url == null )throw new IllegalArgumentException
  
  def scheme = url.split(":").first
  def hostname = url.split("/").toList.tail.dropWhile("" == ).first
  def pathinfo = url.split("/").toList.tail.dropWhile("" == ).tail
}


このURLオブジェクトのプロパティurlが、どこかのタイミングでnullを代入されてしまったら、hostname関数などを呼び出すとNullPointerExceptionがthrowされてしまうでしょう。


これを防御するには、urlプロパティを利用する処理で事前にnullチェックを行うしかありません。
そもそも、urlがnullの場合にこのURLオブジェクトはどのように振る舞うのが正しい仕様でしょうか? 例外? それともnullを返す?


このurlプロパティが変更不可であれば、上記のような問題に煩わされることはありません。
urlプロパティは、nullではないことが保証されているからです。


URLクラスの宣言で、"var"になっている箇所を"val"に変更すれば、心の平安を得ることができます。

immutableなクラス - caseクラスの利用

先ほどのURLクラスのようなimutableなクラスを作る手っ取り早い方法がScalaにはあります。


caseクラスにすればよいのです。


classキーワードの前に"case"をつけて、コンストラクタ引数のvar/valを外す、だけです。

case class URL( url:String ) {
  if( url == null )throw new IllegalArgumentException
  
  def scheme = url.split(":").first
  def hostname = url.split("/").toList.tail.dropWhile("" == ).first
  def pathinfo = url.split("/").toList.tail.dropWhile("" == ).tail
}

これだけで、このURLクラスはimutableなクラスになります。他にも様々な恩恵を得ることができます。

  • newキーワードなしでURL("http://d.hatena.ne.jp/yuroyoro")のようにオブジェクトを生成できる
  • 引数のプロパティが自動的に読み取り専用で公開される
  • equals,hashCode,toStringが適切に実装される
  • パターンマッチ(後述)で使えるようになる
// newなしでインスタンス生成
scala> val url = URL("http://d.hatena.ne.jp/yuroyoro/" )
url: URL = URL(http://d.hatena.ne.jp/yuroyoro/)

// プロパティに代入できない
scala> url.url = null
<console>:21: error: reassignment to val
       url.url = null
               ^
// 読み取りできる
scala> url.url
res22: String = http://d.hatena.ne.jp/yuroyoro/

// 同じプロパティを持つインスタンス同士は"同値"
// (equals,hashCodeが適切に実装されている)
scala> url == URL("http://d.hatena.ne.jp/yuroyoro/" )
res23: Boolean = true

// toStringも自然に
scala> url.toString
res24: String = URL(http://d.hatena.ne.jp/yuroyoro/)


Scalaでクラスを作るときはまず、caseクラスで問題ないか考えるところから始まる、といっても過言ではありません。

再帰とimmutable

ScalaのListクラスは、基本はimmutableです。つまり、後から要素を追加したり削除したりできません。(mutableなコレクションを使いたい場合は、scala.collection.mutableパッケージのクラスを使います。


あとからListに要素追加できないなんて不便すぎる、と思うかもしれません。


たとえば、ある文字列中に出現する文字の回数をMap(文字 -> 回数)という形で数える関数を考えてみます。


javaではこんな感じでしょう。最初にMapを用意して、文字が出現するたびにMapの値をインクリメントしていく形です。

static Map<Character,Integer> countChar( String s ){
    Map<Character,Integer> m = new HashMap<Character,Integer>();
    for ( char c :s.toCharArray() ){
        if( m.containsKey(c)){
          int cnt = m.get(c);
          m.put( c, cnt + 1);
        }
        else{
          m.put(c, 1 );
        }
    }
    return m;
}


同様の処理をScalaで書いてみましょう。

def countChar( s:String ) = {
  val m = Map.empty[Char,Int]

  s foreach { c => 
    val cnt = m.getOrElse(c,0)
    // ここでmに要素を追加したいがmはimmutable 
    m + ( c -> cnt + 1 )  
  }
  m
}


ScalaのMapはimmutableなので、このアプローチは使えません。ではどうするのでしょうか?


手段は二つあります。
一つは、利用するMapをmutableなMapに変更することです。
もう一つは、再帰を利用して書き直す方法です。


再帰で書き直すと、こんな感じです。

def countChar( s:String ) = {
  // 再帰させるワーカー関数
  // 引数lのCharリストの先頭要素の出現数を+1したMapを返す
  def count( l:List[Char], m:Map[Char,Int] ):Map[Char,Int] = {
    if( l.isEmpty ){  // lが空Listだったら再帰終了
      m
    }else {
      val cnt = m.getOrElse( l.head, 0 ) + 1
      // lの残りと先頭要素の出現数を+1したMapを再帰で渡す
      count( l.tail, m + ( l.head -> cnt ) )
    }
  }
  
  // ワーカー関数を空Mapを作って呼び出す
  count( s.toList, Map.empty[Char,Int] )
}


これで、immutableなMapのままで目的の処理ができました。再帰のためにワーカー関数を作るのはよくやるパターンです。


ちなみに、この集計処理はfoldLeftを利用して一行で書けたりします。この手のコレクションの集計系の処理でfoltLeftを使うのも常套手段です。

def countChar( s:String ) = 
(Map.empty[Char,Int] /: s ){( m ,c ) => m + ( c -> (m.getOrElse( c, 0 ) + 1)) }
なにがなんでもimmutableがいいの?

そうではありません。immutableなプログラミングは、一般的には簡潔な記述になることが多いですが、varを利用した方がわかりやすいコードになることもあるでしょう。
また、immutableなプログラミングではパフォーマンス面で不利になることがありえます。


このような場合は、すなおにvarを利用した方がよいでしょう。
例えば、ScalaのListクラスは利用者からするとimmutableなクラスですが、内部の実装はvarを使って性能面を考慮しています。



/scala/tags/R_2_7_5_final/src/library/scala/List.scala – Scala

「パターン青!使徒です!」 - ifよりパターンマッチ

条件判断を行わないプログラムというのはまれでしょう。普通はif文で条件分岐を書きますよね?


Scalaで条件分岐を行うには、もちろんif文も利用できますが、パターンマッチ(match式)を利用すると柔軟な記述ができるようになります。


コップ本からの引用ですが、javaのswitchとscalaのmatch式の記述の比較です。

Java:

switch( <セレクター式> ){ 
  case 場合 : 処理
  ...
}

Scalaのmatch式:

<セレクター式> match { 
  case パターン => 処理
  ...
}


これだけですと対して違いが無いように見えます。しかし、Scalaではパターンとして記述できる表現がJavaに比べてはるかに柔軟なのです。

マッチ式の基本

Javaでは、caseの後に書けるのは整数型(Enum含む)と文字型(char)だけでした。
Scalaでは、以下のようなパターンを書くことができます。

ワイルドカードパターン case _ あらゆるものにマッチ。ようはdefault
定数パターン case "foo" "foo"など値にマッチ。
変数パターン case n 全てにマッチするが、マッチした結果をnという変数名で使える。
型付きパターン case d:Double Double型の場合にマッチ
パターンガード case n if n % 2 == 0 ifで条件を指定。例では偶数のみマッチ
コンストラクタパターン case URL( u ) caseクラスであるURLクラスにマッチし、URLクラスのプロパティurlを変数uに束縛
match式で何がうれしいの?

match式は、caseクラスと組み合わせて使うと幸せ度がヤバイことになります。


電話番号のcaseクラスTelを定義します。市外局番、市内局番、加入者番号をプロパティで持つシンプルなものです。

case class Tel( area:String, local:String, sub:String)


携帯電話番号(ここでは090のもののみとします)とそれ以外で処理を分けたい場合、match式でこんなに簡単に書くことができます

scala> val tel = Tel( "090","1234","5678")
tel: Tel = Tel(090,1234,5678)

scala> tel match {
     |   case Tel("090", l, s ) => println("携帯:090-%s-%s".format( l, s ) )
     |   case Tel( a, l, s ) => println("電話番号:%s-%s-%s".format( a, l, s ) )
     | }
携帯:090-1234-5678


「case Tel("090", l, s ) => ...」はTelオブジェクトのareaプロパティが"090"だったらマッチして、残りのlocalプロパティを変数lに、subプロパティを変数sに束縛させています。


このように、caseクラスに対してコンストラクタパターンでmatchさせることで、そのcaseクラスのオブジェクトが持つプロパティを、マッチした時に変数に束縛して利用できるのです。


正規表現もパターンマッチで使えます。URLを正規表現scheme,hostname,pathinfoに分割するのはこんな感じです。(正規表現自体は適当です><)

scala> val url = "http://d.hatena.ne.jp/yuroyoro"
url: java.lang.String = http://d.hatena.ne.jp/yuroyoro

scala> val r = """(.+)://([^/]+)/?(.*)*""".r 
r: scala.util.matching.Regex = (.+)://([^/]+)/?(.*)*

scala> url match {
     |   case r( s, h, _ ) => println( "%sの%s" format( s, h ) )
     |   case _ => println("URLじゃなくね?")
     | }
httpのd.hatena.ne.jp


Scalaでは「val r = """(.+)://([^/]+)/?(.*)*""".r 」のように正規表現の文字列に.r関数を呼び出すことで正規表現オブジェクトを取得できます。この正規表現オブジェクトはmatch式で利用できて、正規表現内のパターンをmatch式で変数パターンとして束縛して得ることができるのです。


便利ですね?
もう、「条件分岐はmatch式、if文は三項演算子くらいの勢いのほうがいいかもです。

nullだって? そんなものは忘れたよ - Option

Option型っていうのが便利すぎて鼻血がほとばしります。
scala.Option


「MayBeモナドっ!」なんていうと各方面からフルボッコにされてしまいそうなので控えますが、Optionってのは値が存在するかもしれない状態をあらわすものです。


Optionはcaseクラスです。Option型には、サブタイプとして値が存在する場合のSomeと、値がない場合のNoneがあります。


論よりソースです。javaのMap#getは、値が存在しない場合はnullを返しました。
ScalaのMap#get関数は、引数のkeyに対して値をOption型で"包んで"かえします。値がある場合はSome(値)で、ない場合はNoneが返ります。

scala> val m = Map('a' -> 1 , 'b' -> 2 )
m: scala.collection.immutable.Map[Char,Int] = Map(a -> 1, b -> 2)

scala> m.get('z')
res97: Option[Int] = None ← 値がないのでNone

scala> m.get('a')
res96: Option[Int] = Some(1) ← 値があるのでSome(1)

scala> m.get('a').get ← Someから値を取り出すのはOption#get
res99: Int = 1


なんでわざわざOptionに"包む"のでしょうか?取り出すのが面倒じゃないですか?


そうではないのです。Optionはcaseクラスですので、match式が使えます。match式と組み合わせると、値がある場合の処理とない場合の処理を自然と分離できます。

scala> m.get('b') match {
     |   case Some( n ) => println(n)
     |   case None => println("none.")
     | }
2


値がある場合のみ処理を行いたい場合は、match式すら必要ありません。Option#foreachでSomeの場合のみ処理を行わせることができます。nullチェックなんて不要です!

scala> m.get('a').foreach{ n => println( "find! %s" format( n ) )}
find! 1


Scala LibraryのAPIではnullを返すものは存在しません。値がないかもしれいないAPIはすべてOption型が返されるようになっているのです。


Scalaでnullチェックが必要な局面は、Javaのライブラリを利用する場合でしょう。その場合も、nullだったらNoneを返すようにラップしてあげるのが便利だと思います。

関数を"ものあつかい"する - 関数オブジェクト

リストのところで説明した考え方ですが、なぜ段階にわけているのでしょうか。一つのループの中で、絞り込みや加工や操作を一度にやってもいいじゃないか、と思われる方も多いと思います。


さきほどのFizzBuzz問題、条件を偶数ではなく奇数にしたり、呼び出し側が条件を指定できるようにしたい場合はどのようなアプローチをとりますか?


Javaの場合ですと、偶数か奇数かを判定する処理をメソッドとして抽出する、というアプローチがあります。あるいは特定のinterfaceを実装したオブジェクトに条件判断を委譲する、などでしょうか?


Scalaでは、もっとストレートなアプローチをとります。条件を判断する"関数オブジェクト"を引数にもらうようにするのです。

def fizzbuzz( n:Int, cond:Int => Boolean ) = { 
  1 to n                         // (A)リストを作る
} filter{ 
  cond                           // (B)条件を引数の関数オブジェクトで絞る
} map {                          // (C)要素を加工する
  case n if n % 15 == 0 => "FizzBuzz"
  case n if n % 3  == 0 => "Fizz"
  case n if n % 5  == 0 => "Buzz"
  case n                => n.toString 
} foreach { e => 
  println( e )                   // (D) 操作を行う
}


これが改良版のfizzbuzz関数です。引数に、"Int型を引数で受け取ってBooleanを返す関数"を追加しました。(B)の条件の絞り込みは、この引数でもらった関数オブジェクトにやらせています。


これで、このfizzbuzz関数の利用者は絞り込みの条件を好きに指定できるようになりました。奇数だろうが、3の倍数だろうがなんだろうが対応できます。

scala> fizzbuzz( 30 , (n:Int) => n % 2 == 1 )
1
Fizz
Buzz
7
...

つなげてわたす - もしかして:UNIX

最初に説明しましたがリストを処理させるときに、「リストを作って、条件を絞って、加工して…」というように、ちいさな処理をつなげていきました。


この考え方、どこかで聞いたことありませんか?


UNIXという考え方―その設計思想と哲学 」と言う本にありますが、UNIXの思想の一つに、"小さい機能のコマンドを、パイプでつないで複雑な処理をする"というものがあります。


UNIXという考え方―その設計思想と哲学
Mike Gancarz
オーム社
売り上げランキング: 16112


さきほどの考え方と似ていますね? 俺がScalaでプログラムを書くときには、この"つなげてわたす"というのを意識しています。


何か入力を受け取って結果を返す関数を出来るだけちいさい単位でたくさん用意しておけば、あとあと使い回しが効きます。途中に処理を挟んだり、差し替えたり…。


なので、関数を作るときは、できるだけちいさく、そして結果をListやMapやOptionなど"わたせる形"で返すようにしておくのを心がけています。ListやOptionなら、つないでいる途中で結果が空のListやNoneになってしまった場合は以降の処理は単純にスキップされますし。


結果を返せないUnit型の関数は、この"つなぎ"の終端にしか接続できないので、使い勝手があまりよくありません。Unit型をできるだけ小さな単位にすることも、また意識しています。


くりかえしますが、"つなげてわたす"はとっても大事なことだと思います。