2008年8月31日日曜日

ScalaのCaseクラスとパターンマッチ

アクターモデルの説明あたりで謎が残ったCaseクラスとパターンマッチ。ようやくわかるのだろうか。

概要
Caseクラスとパターンマッチは、相互に関連するクラス群をグループ化して定義し、クラス群のそれぞれに対して異なる操作が必要になった際にクラス群を分解して個別に処理を行わせることができる仕組みの1つ。なんのこっちゃ。
OO言語のポリモーフィズムについて考えてみると、これは完全に上記の説明に当てはまることがわかると思う。ポリモーフィズムでは関連するクラス群を型階層としてグループ化し、個別の型に対して異なる操作が必要になった場合には、それぞれのオブジェクト型にふさわしい操作が動的に選択されて実行される。
Caseクラスとパターンマッチは、これを違う角度から実現するもの。イメージ的には、Enumとsiwtch + instanceofをまぜあわせて言語仕様に組み込んだような感じ。Enumライクにグループ化したクラス群に対して個別の操作が必要になった場合、Case文+パターンでマッチしたクラスに対して任意の処理を行う。
実際、instanceofで処理を切り替えるのと何が違うのかは疑問だ。パターンマッチは型の一致以外にも色んなルールが扱えるからより柔軟なのは確かだけど、本質的にはあまり変わらない気がするんだけど。instanceofによるメソッド内での処理の選択も、OOにとらわれなければそれなりの正当性があったということなんだろうか。

Caseクラス
case classesの定義とその特徴については、アクターモデルでの説明以上の情報は特にない。classの前にcaseをつけるだけで作成できる。ちなみに、これまでの例で出てきたようなCase共通の親クラスは必須ではない。
object ExprSample extends Application {
abstract class Expr
case class Number(n: Int)
case class MyString(n: String)

println(MyString("aaaaa"))
println(MyString("aaaaa").n)
}
こんなしょうもないサンプルも一応動く。ただし、たいていの場合には親クラスを継承する一連のグループを作ることになると思われる。でないと、Caseクラスを扱う操作で型がAnyRefとかになっちゃうし。

パターンマッチ
パターンマッチは、Caseクラスを分解するための仕組み。サンプル見た方が早い。
def eval(e: Expr): Int = e match {
case Number(x) => x
case Sum(l, r) => eval(l) + eval(r)
}
という感じで、引数に対して規定のパターンマッチ書式でマッチングをかけ、マッチした場合は 「=>」の右側の式が実行される。パターンマッチは以下から構成される。
  • Caseクラスのコンストラクタ。Number(~ や Sum(~ 。コンストラクタの引数もパターンに含まれる。
  • パターン変数。x, l, rなど。=> の右側の式でそのまま使える。
  • ワイルドカード(_)。 なんでもマッチするけど => の右側の式で使えない。
  • 定数識別子。よくわかんない。
なんかHaskellチックだ。想像の通り、書かれた順に評価され、最初にマッチしたら以降は無視される。一つもマッチしないと例外がスローされる。

パターンマッチと匿名関数
ここでようやく、Actorのパターンマッチの謎が解けた。
Case式は常にmatchと一緒に現れるはずだが、単独で使える場合もあるらしい。それがCase式のブロックで、こんなようなもの。
{ case P1 => E1 ... case Pn => En }
これってActorのとこで出てきたやつだよね。ブロック引数に対してパターンマッチするらしい。つまりこれは、以下の匿名関数と等価。
(x => x match { case P1 => E1 ... case Pn => En })
謎が半分解けてすっきり。後はブロック引数の仕様がわかれば完璧。

Scalaのクラスとオブジェクト

あまりJavaとの違いが大きくないので、気になったところだけ。
  • overrideする場合は、defの前に「override」をつける。C#みたい。
  • メソッド呼び出しに「( )」は不要。属性/操作への透過的なアクセスは、OOSCで述べられているOO言語の要件に合致する。
  • Javaのインタフェースに相当するものはない。同じ事がやりたければ、abstract classかtraitを使う。
  • abstract classはほぼJavaと同じで、Objectを作ることもできない。
  • traitはMix-in用の構成物。
  • 抽象クラスを継承し、メソッド定義に実装を与える場合にはoverrideは必要ない。
  • 組み込みSingletonサポートとして、objectが存在する。classのかわりにobjectで定義されたものは、Singletonとなる。
  • objectは、newでインスタンス化できず、コンストラクタも存在しない。最初にメンバーにアクセスされた時に自動的にインスタンス化される。
  • Scalaではprimitive型もオブジェクトだが、内部的にはJavaのAuto-Boxingを使って処理されている。つまり、必要に応じてprimitive型も使われている。

自宅の音楽環境がまた一歩前進

iPod Touch/iPhoneの公式アプリ「Remote」が使えるようになったことで、自宅の音楽環境が1歩前進した。ちなみにRemoteは、iTunesをiPhone/iPod Touchからリモートコントロールできるアプリ。音は、iTunesが動作しているPCの出力か、AirMacExpressのリモートスピーカーから再生される。手元のiPhone/iPod Touchで再生されるわけではないので注意。って、その仕様だとあまり役に立たないんだけど。



今までの状況としては、音楽自体はiTunesサーバ/DLNAサーバ機能がついたNASにすべて格納してあったので、あとはそのデータを再生できるクライアントがあればOKだった。家にはそれなりの音を出せるミニコンポがあまっていたので、できれば純粋にiTunes/DLNAクライアントとして動作し、出力は外部に流せるようなものを探していた。
ところが、DLNAクライアントは音楽の再生環境としてはいまいち(少なくともiTunesの使い勝手には遠くおよばない)だったり、クライアント機能単体だけの製品がほとんどない(CDプレイヤーやスピーカーが一体になっている)など、なかなか手頃なものが見つからない。唯一よさそうだったのが TRANSGEAR APX-300だったんだけど売り切れちゃったし、使い勝手に多少の問題(というより値段なりの妥協)があるようだったので、結局見送ってしまった。
なので、結局はクライアントPCを立ち上げてiTunesを操作し、PCに接続されているBOSEから聴くか、せいぜいAirMacExpressに接続されたスピーカーから聴くくらいしかできなかった。

このような状況でRemoteが出てきた。これで、PC+iTunesさえ立ち上げておけば、ベッドからでも好きなスピーカーで好きな音楽を再生できる。AirMacExpress持ってるし。
と、ここまではみんなやってるんだと思う。ただ、自分にはこれがどうも納得いかない。iTunesサーバーはNASであがっているんだから、なんでわざわざiTunesサービスをもう1つ、しかもPCで立ち上げねばならんのだ。うちのPCはPen4 3GHzでファンの音がすごくうるさいのだ。消費電力も高い。こんなの24時間つけっぱなしなんてあり得ない。リビングがうるさくてしょうがない。
Remoteが直接NASのiTunesサーバーを制御できればいいんだけど、RemoteはiTunesサーバー機能とは別に、iTunesソフトウェア同士の独自のインタフェースを持っているようなので、NASのiTunesサーバー機能と連携できる目は薄い。

というわけで、年度末の大安売り?で、購入していたDELLのPCサーバー(2万円)にWindowsをインストールし、iTunesを常駐させることにした。今まで動いていたCentOSは、VMWareServer上に移動。

DELL PCサーバー SC440

デカイ筐体なだけにファンの音は小さいし、設置場所もリビングじゃないから音はほとんど気にならない。消費電力はそれなりに高いだろうけど、そこには今のところは目をつぶろう。DELLのPCサーバーにはサウンドボードがついていなかったので、ベース練習用に買ったUSBサウンドボード(EDIROL UA-4FX)を接続することにした。
NAS上の音楽ファイル置き場をWindowsのネットワークドライブとして割り当て、iTunesからはそちらを参照することで二重管理しなくてすむ。とりあえず、一歩前進。

Scalaの高階関数

無名関数
書式はこんな感じ。

(x1: T1, ..., xn: Tn) => E

これは、以下と等しい。

{ def f (x1: T1, ..., xn: Tn) = E ; f _ }

うむ。すっきり。

カリー化
ここでなんだか残念な事態が判明する。
def sum(f: Int => Int, a: Int, b: Int): Int =
if (a > b) 0 else f(a) + sum(f, a + 1, b)

def sumInts(a: Int, b: Int): Int = sum((x: Int) => x, a, b)
def sumSquares(a: Int, b: Int): Int = sum((x: Int) => x * x, a, b)
この高階関数をカリー化する際には、sumを
def sum(f: Int => Int): (Int, Int) => Int = {
if (a > b) 0 else f(a) + sum(f)(a + 1, b)
}
こんな風に書き換えた上で、呼び出し時には
def sumInts = sum(x => x) _
def sumSquares = sum(x => x * x) _
とか
val sumInts = sum(x => x) _
val sumSquares = sum(x => x * x) _
とか書かなければならないらしい。なんということだ。これじゃ、カリー化したい組み合わせが変わるごとに別々に定義が必要じゃないか。いけてない…。しかも、呼び出し時によくわからん「_」をつけなければならないとは…。これが静的言語の宿命なのか?残念だ。

2008年8月30日土曜日

Scalaの式と関数

定義
  • def x = e のような定義が評価される際には、eは評価されない
  • eはxが使われるたびに評価される
  • val x = e の定義では、eも評価される
評価
情報の専門教育を受けていない自分にとっては、整理のためにも役に立つ話が多い。
  • 先行評価はcall-by-value、遅延評価はcall-by-nameと言うらしい
  • call-by-valueは重複評価を避けられるが、無限ループを起こすケースがある
  • call-by-nameは無駄な評価を避けられ、無限ループも回避できる
最後のは、
scala> def loop: Int = loop
loop: Int
scala> def first(x: Int, y: Int) = x
first: (Int,Int)Int
こういう状況で、 first(1, loop) を呼び出す場合を考えればわかりやすい。Haskellでも良くでてきた気がする。ScalaはHaskellとは違って基本は先行評価だが、引数の型定義で「:」のかわりに「=>」 を使うことで遅延評価になる。この書式明らかに、関数だよなぁ…変数を関数化するようなイメージだろうか。いや、結局Call-by-nameにする、というのが一番わかりやすいかもしれない。こんな感じ。
scala> def constOne(x: Int, y: => Int) = 1
constOne: (Int,=> Int)Int
scala> constOne(1, loop)
unnamed0: Int = 1
scala> constOne(loop, 2) // gives an infinite loop.
Scalaいいねぇ。わくわくするわぁ…。

条件式
Javaと違い、文の選択だけではなく式の選択に使うことができる。よって、Javaの三項演算子みたいに使える。…と書いてあるけど、Rubyみたいにif~else自体が式だ(値を持つ)と言った方がいいのかもしれない。

ニュートン法による平方根の計算

このアルゴリズム自体を知りませんでした。知りたい人はWikipediaとか見た方がいいよ。面白い例なんだけど、特に新しいことが出てくるわけでもないので省略。

関数のネスト
Scalaではブロック自体が式であり、値はブロック最終行の値となるらしい。Ruby?
ブロック内のすべての定義は、セミコロンで終わらなければならないが、推測できる場合は勝手に挿入してくれるらしい。
また、外部ブロックで定義された変数・定数は内部ブロックから参照できる。

末尾再帰
末尾再帰に関するScala処理系の話。末尾再帰の場合には、書き換えが行われてスタックフレームを無駄に消費しない。末尾再帰に関してはWikipedia見た方が早い。

はー・・・意外と頑張った話が多いよ、サンプルで説明、なのにさー。例によって数学的な話が多いし。もうちょっと数学屋さんじゃなくても身近な例があるといいのに。

Scalaのアクターモデル

アクターモデルは最後らへんかなー…と思ってたら、ScalaByExampleのChapter3はいきなりアクターモデルの話(Programming with Actors and Messages)だった。ちなみにChapter2は言語仕様の特徴的な部分のOverviewのような感じ。

Chapter3ではまず、丁寧に(Erlangスタイルの)アクターについて説明してくれている。

Actors are objects to which messages are sent. Every actor has a “mailbox” of its incoming
messages which is represented as a queue. It can work sequentially through
the messages in itsmailbox, or search for messages matching some pattern.

ということで、アクターというのは…
  • メッセージを受信するオブジェクト
  • キューで表現されるメールボックスを持っている
  • キュー内のメッセージを通じて順次動作する、またはパターンマッチでメッセージを選択して動作する
というものらしい。勉強になります。このドキュメントはScalaByExampleなので、以降はネットオークションのシステムを例として説明が進んでいく模様。

アクター
まずはネットオークションのシステムに存在するアクターとして、
  • Auctioneer(オークションを仕切る人)
  • Client(落札しようとする人)
というのが想定されている。Auctioneerは、オークションアイテムに関する情報を提供したり、オファーを受けたり、出品者と落札者の間を取り持ったりする。

メッセージ
で、まずはメッセージを決めるらしい。素人はおおせのままにするしかない。ぶっちゃけていうと、以下の二種類のメッセージが必要になる。
  • AuctionMessage
  • AuctionReply
へぇ。で、この二種類のメッセージが持つバリエーションが以下のようになる。

import scala.actors.Actor
abstract class AuctionMessage
case class Offer(bid: Int, client: Actor) extends AuctionMessage
case class Inquire(client: Actor) extends AuctionMessage

abstract class AuctionReply
case class Status(asked: Int, expire: Date) extends AuctionReply
case object BestOffer extends AuctionReply
case class BeatenOffer(maxBid: Int) extends AuctionReply
case class AuctionConcluded(seller: Actor, client: Actor) extends AuctionReply

case object AuctionFailed extends AuctionReply
case object AuctionOver extends AuctionReply
なんということだ。case classとか良くわかってないんだが…。しょうがないからTutorialに戻ろう…。
case calssesの特徴は、
  • インスタンス化にnewキーワードが不要
  • コンストラクタ仮引数に対するgetterメソッドは自動的に定義される
  • (IDベースでなく)値ベースのequals/hashCodeが自動的に定義される
  • いけてるtoStringが自動的に定義される
  • これらのクラスのインスタンスは、パターンマッチングを通じて分解できる
最後のやつ以外はどうでもいいっぽい。で、

abstract class Tree
case class Sum(l: Tree, r: Tree) extends Tree
case class Var(n: String) extends Tree
case class Const(v: int) extends Tree

代数演算を行うための構造をこんな感じで定義したとする。Javaだと型階層で表現するところ。これがパターンマッチングを通じて分解できるとは、

def eval(t: Tree, env: Environment): int = t match {
case Sum(l, r) => eval(l, env) + eval(r, env)
case Var(n) => env(n)
case Const(v) => v
}

こういうことらしい。これって嬉しいのか…?バリエーションの制御を型にカプセル化するんじゃなく、操作の方に書いちゃってるけど。と思ったら、使い分けの方法も書いてある。

Deciding whether to use pattern matching or methods is therefore a matter of taste, but it also has important implications on extensibility:
  • when using methods, it is easy to add a new kind of node as this can be done just by defining the sub-class of Tree for it; on the other hand, adding a new operation to manipulate the tree is tedious, as it requires modifications to all sub-classes of Tree,
  • when using pattern matching, the situation is reversed: adding a new kind of node requires the modification of all functions which do patternmatching on the tree, to take the new node into account; on the other hand, adding a new operation is easy, by just defining it as an independent function.
端的に言うと…
  • 継承を使った場合、新しいノード(型)を追加するのは簡単な一方で、新しい操作を追加するのは困難になる。型階層の上位に操作が新規に定義された場合には影響が広範囲に及ぶ。多態性をうまく使っていればなおさら。
  • case classes+パターンマッチングは継承とは逆で、新たなノード(型)を追加するのは難しい。いろいろな場所の操作でcaseを追加する必要があるから。ただし、操作を追加するのは簡単。単に新たな関数を追加すれば良い。
というわけで、何が可変性なのかを意識し、その可変性が「概念そのもの」ではなく「操作」である場合には、型階層よりもうまく扱うことができるメカニズムだというわけですね。やー、さすが後発言語。
というわけで、Messageに戻ろう。と思ったらMessageはこれで終わりかよ!

アクターの実装
てなわけで、もうアクターの実装がでちゃいました。

class Auction(seller: Actor, minBid: Int, closing: Date) extends Actor {
val timeToShutdown = 36000000 //msec
val bidIncrement = 10

def act() {
var maxBid = minBid bidIncrement
var maxBidder: Actor = null
var running = true
while (running) {
receiveWithin ((closing.getTime() - new Date().getTime())) {
case Offer(bid, client) =>
if (bid >= maxBid + bidIncrement) {
if (maxBid >= minBid) maxBidder ! BeatenOffer(bid)
maxBid = bid; maxBidder = client; client ! BestOffer
} else {
client ! BeatenOffer(maxBid)
}
case Inquire(client) =>
client ! Status(maxBid, closing)
case TIMEOUT =>
if (maxBid >= minBid) {
val reply = AuctionConcluded(seller, maxBidder)
maxBidder ! reply; seller ! reply
} else {
seller ! AuctionFailed
}
receiveWithin(timeToShutdown) {
case Offer(_, client) => client ! AuctionOver
case TIMEOUT => running = false
}
}
}
}
}
アクターはどうやら、Actorというクラスを継承するようだ。アクターの動作はact()で定義されているが、これはActorクラスで定義されたメソッドだろうか。scala.actors.Actorクラスのソースを見てみると、ビンゴのようだ。Actorsフレームワークみたいな感じなのね。

receiveWithinメソッドもActorで定義されているメソッドで、引数で渡された時間内に、ブロックを処理するようにスケジュールするらしい。
メインの処理は、1つめのreceiveWithinのcase Offer ~ case Inquireが相当し、オークション開催時間内に実行される処理が定義されている。

引き渡された時間内にメッセージが到着しなかった場合、自身に向けてTIMEOUTメッセージを送信する。これに対応した処理がcase TIMEOUT~の部分で、再びreceiveWithinを使ってオークションの終了処理を定義している。

基本的にはわかりやすいんだけど、receiveWithinについていくつかわからないことが。このメソッドの宣言は
  def receiveWithin[R](msec: Long)(f: PartialFunction[Any, R]): R =
self.receiveWithin(msec)(f)

こんな感じになっているんだけど、このPartialFunctionがどういう仕様になっているのかがよくわからない。Rubyのブロックみたいなもんだと思うんだけど、ブロック内のcaseからなぜメッセージにアクセスできるのかがよくわからないのだ。このあたりはこのドキュメントのこの段階ではあまり触れられていない。

scala.actors.Reactionの f(msg) ってあたりを見ると、引数で渡してるみたいなんだけど…ブロック引数いまいちわからず。このあたりはまた先でわかるといいな。

Scalaを本格的に調べてみる

JavaScript => Java => C => Ruby => Haskellと渡り歩いてきた(とはいえ、メインは常にJava)自分としては、かなり理想的な言語に見えるScala。
端的に言うと、「Javaを今風に変えてみました。ただしJVM上でJavaと同じように動きます。」って感じだろうか。今風の"風"は、Rubyテイスト+Haskellテイストというイメージ。C#3.0とも微妙にかぶるところがある。
全体的に自由度が増していて、マルチパラダイムデザインなんかで言うところの「共通性」「可変性」を扱うためのメカニズムが言語仕様レベルで豊富に導入されている雰囲気。

オブジェクト指向言語の側面では、Javaをより純粋なオブジェクト指向言語に近づけた上で、Rubyの柔軟性を足したような感じ。
  • プリミティブを含め、すべてがオブジェクトに
  • 演算子がメソッドに
  • Singletonをデフォルトでサポート
  • traitを使ってMixinが可能に(実装定義可能なインタフェースがある感じ)
  • 特性(メソッド)と属性(フィールド)の区別がなくなった
  • 既存クラスにメソッドを追加可能
  • 型推論を導入
関数型言語としては、HaskellからMonadをとっぱらったような感じ。参照透過性とかないけど、(Monadのようなものはあります。それに、参照透過性は言語レベルでは保証されないけど、参照透過性が保たれるように書くことはできます。もし知っている人がいれば、OCamlに近いといえばわかるのかもしれない。OCamlの文献をあわせて読むと参考になります。)JavaScriptよりは関数型の性格が強い。パターンマッチとか部分適用とかあるし。

というわけで、しばらくはScala公式サイトで配布している「ScalaByExample」をもとにScalaさわっていきます。ついでに最近ずっと話題だった、アクターモデルとやらを理解しておきたい。最終的にはScalaベースのWebアプリケーションフレームワーク、「Lift」でアプリ作ったり、Liftのソース読んだりしてみる予定。Rubyの大クラス主義やHaskellのMonadになじめなかった自分の安住の地になるといいなぁ…。

関連エントリ

やっぱり自鯖は面倒くさかった

BloggerでRubyやXMLのコードを表示するのがあまりに面倒くさかったのと、URIがいまいち気に入らなかったことから、自鯖を立ててBlogを引っ越ししようと思ったのだが…あきらめた。MovableTypeもWordPressも思ったほどよくないんだもん。自鯖の更新めんどくさいし…ホームサーバーと共用だから、しょうもない用事で再起動したりするし。

というわけで、結局Bloggerに戻ってくることにした。Bloggerでコードをうまいこと表示するやり方も、エラい人が色々考えてくれてるみたいだし!