Scalaの無限リスト(Stream)でいろいろと
宇宙に始まりはあるが、終わりはない。 ――無限
ScalaでRubyのObject#tapがぽぽぽぽ〜ん。っていうかいつでもmapしたい
「まほうのこーどで♪ たのしい♪ れいがいが♪ ぬるぽぽ〜ん♪ 」
作ってみた。
// 入れ物はタッパーです。 trait Tapper[A] { val obj:A // RubyのObject#tap的な。引数fに自分自身を適用させて自身を返す。 // 副作用専用メソッド。nullだったらなにもしなーい def tap(f:A => Unit):A = { Option(obj).foreach(f);obj } // 上記の、戻り値Option版。nullだったらNoneが返る def tapOption(f:A => Unit):Option[A] = { Option(obj).foreach(f);Option(obj) } // いつでもmapできたら便利よね? def map[B](f:A => B):Option[B] = Option(obj).map(f) // Option(obj)でもいいけど、何でもメソッドチェーンしたい病の人に def toOption:Option[A] = Option(obj) } // implict conversionで、タッパーに詰める implicit def any2Tapper[A](a:A):Tapper[A] = new Tapper[A]{ val obj = a }
こういう風に使えば。nullとか気にしないでよくなるよねー。ぽぽぽぽ〜ん。まぁこの例のカレンダーだったらブロックに初期化処理を詰めてもいんだけどね。nullチェックとかウザいし。
scala> import java.util.Calendar import java.util.Calendar scala> val c1 = Calendar.getInstance.tap{ c => | c.add(1, Calendar.MONTH ) | c.add(1, Calendar.YEAR ) | } c1: java.util.Calendar = java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=32400000,dstSavings=0,useDaylight=false,transitions=10,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2014,MONTH=2,WEEK_OF_YEAR=12,WEEK_OF_MONTH=4,DAY_OF_MONTH=23,DAY_OF_YEAR=82,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=1,HOUR_OF_DAY=13,MINUTE=30,SECOND=19,MILLISECOND=552,ZONE_OFFSET=32400000,DST_OFFSET=0] scala> (new java.text.SimpleDateFormat).map { fmt => fmt.format(c1.getTime) } res0: Option[java.lang.String] = Some(14/03/23 13:30) scala> val c2:Calendar = null c2: java.util.Calendar = null scala> val c3 = c2.tap{ c => | c.add(1, Calendar.MONTH ) | c.add(1, Calendar.YEAR ) | } c3: java.util.Calendar = null
書評:プログラミングScala - The Python Paradox is now the Scala Paradox
オライリージャパン様より献本頂きました。読了したので、感想など書いてみたいと思います。
The Python Paradox is now the Scala Paradox
なんですが、本の紹介に入る前に、とある記事の紹介をしたいと思います。
The Python Paradox is now the Scala Paradox — Martin Kleppmann‘s blog
In his 2004 short essay The Python Paradox, PG argues (perhaps controversially) that a company can hire smarter programmers if it chooses to write its code in a “comparatively esoteric” programming language. At the time, Python was probably considered by most people to be esoteric in comparison to Java – in the sense that not many people would learn it at university or for career purposes. Therefore, the programmers who knew Python were people who learnt it for fun; and learning languages for fun is an activity which typically only the bright and motivated people engage in. Which makes the language a good “quality filter” for people.
2004年のポール・グレアムのエッセイ"The Python Paradox"では、ある企業が「比較的難解な」プログラミング言語でコードを書くことを選択したなら、より賢い(Smartな)プログラマを雇うことができる、と言っています。
当時、PythonはJavaより難解である、と考えれていました(大学やキャリア目的でPythonを学ぶことがすくないので)。
したがって、Pythonを知っているプログラマは、純粋な興味でPythonを学んだ人々でした。そして、興味を持ってプログラミング言語を学ぶ人々は、活動的で明るく、やる気のあるプログラマです。
プログラミング言語は、よいプログラマを見分ける「上質のフィルタ」なのです。
http://martin.kleppmann.com/2009/09/18/the-python-paradox-is-now-the-scala-paradox.html
引用されている"The Python Paradox"も、短くて簡単な英文なので、是非読んでみてください。
さて、The Python Paradox is now the Scala Paradox — Martin Kleppmann‘s blogでは、" Scala in 2009 has the place which Python had in 2004.(2009年のScalaは2004年のPythonと同じ位置づけである)"と言っています。すなわち、あまり使われてなくて、やってる人はほとんど興味本位で勉強しはじめて、と言うわけです。そして、ポール・グレアムのエッセイの中で、"誰もが働いてみたい企業"としてGoogleがあげられており、実はGoogleはPythonの経験のあるプログラマを求めていたんだ、とありますが、GoogleをTwitterやLinkedlnなどScalaをベビーに採用している企業に置き換えてみたらどうでしょうか?
「達人プログラマ」には「毎年、新たなプログラミング言語を1つは学ぶこと」というのがあります。今年学ぶ言語として、Scalaはいかがでしょう?いい仕事が見つかるかも知れませんよ?
プログラミングScalaについて
ようやく、本題の「プログラミングScala」についての書評です。
結論から言うと、良書であることは間違いありません。Scalaを学ぶなら、本書と「Scalaスケーラブルプログラミング[コンセプト&コーディング] (Programming in Scala)(通称コップ本) 」を二冊セットで用意すべきです。
本書は、Scalaの機能や言語仕様について詳細に、時には処理系の内部に踏み込んで解説されています。また、著者の一人であるAlex Payneは元TwitterでプロダクトコードにScalaを投入してきた、いわば歴戦の勇士であり、随所に登場する実践的なTipsが非常に参考になります。
"6章 Scalaによる上級オブジェクト指向プログラミング"では、トレイトやコンパニオンオブジェクトなど、Scalaの特徴的なオブジェクト指向サポートをよりよい設計で扱うための実践的な内容が述べられており、是非従来のオブジェクト指向プログラミングに馴染んだ人に読んでもらいたいです。必ず新しい発見があります。
"8章 Scalaによる関数型プログラミング"においては、関数型プログラミングとはどのような思想なのか、実例のコードをもとにわかりやすく解説してあります。特に、関数型プログラミングとは切っても切れない関係であるListやOptionなどのデータ構造についても述べられています。同様のことはコップ本にも書いてありますが、すこし違ったアプローチで解説されています。Javaプログラマの方は、Java SE 8で導入予定のラムダ式に先駆けて、Scalaで関数型プログラミングを体験しておいてはどうでしょうか。
他にも"9章 アクターによる堅牢でスケーラブルな並行処理", "11章 ドメイン固有言語", "12章 Scalaの型システム", "13章 アプリケーションの設計"と、どの章も読み応えがあります。原著はScala2.7の頃に初版が発行されたのですが、翻訳版である本書は随所に脚注で2.8についても補足されています。
本書について、あえて苦言を述べるとしたら、「重厚すぎる」という点でしょうか?Scalaは豊富な機能を持つ言語ですので、それぞれの機能を細やかに解説していくと、どうしても分量が多くなりがちです。もし、初めてScalaに触れる方ならば、本書をじっくりと読み進めてもよいのですが、先にコップ本を読みつつ、気になったトピックを本書でさらに深く理解する、という進め方がよいでしょう。冒頭で、二冊セットで揃えるべき、と言ったのは、こういった理由です。
Wicket1.5で導入されるNew Event SystemをScalaでエロガントに実装する
もう2週間以上前にもなりますが、Wicket勉強会 2011-01 : ATNDに行ってきたんですよ。
そこでWicket1.5からNew Event Systemってのが導入されてこれはScalaと相性いいんじゃね?みたいな 話になって、そりゃ俺に対して振ってるんだな、と思って書いてみましたが、そんなことより その後のさゆりさんがどうなったのか気になります。届け!この想い!!
Wicket勉強会開催直前です。 - 矢野勉のはてな日記
はてなブックマーク - Wicket勉強会開催直前です。 - 矢野勉のはてな日記
New Event Systemって
くわしくは以下の資料を見てくださいね。
http://public.iwork.com/document/ja/?a=p38265472&d=Wicket勉強会201101.key
簡単に言うと、コンポーネントとかがイベントを発行したり、イベントを受け取って処理ができたりなようはおなじみのアレです。
イベントとして、任意のオブジェクトをメッセージとして投げることができる。わお!
だが、 いかんせん実装がダサイ。イベントが発行されると、受け取り側のonEventメソッドが呼ばれるので実装しなきゃならないんですが、 こんなコード書かなきゃいけない。
@Override public void onEvent(IEvent<?> event) { super.onEvent(event); if (event.getPayload() instanceof ImageRefresh) { if (selected) { source = null; selected = false; ImageRefresh<?> imageRefreshPayload = (ImageRefresh<?>) event.getPayload(); imageRefreshPayload.addComponent(this); } } }
引数にはIEvent<?>型で、そのIEvent#.getPayload()を呼ぶことで、イベント発行元が投げたメッセージオブジェクトを取得できます。
しかし、なんのオブジェクトが入ってるかわからんので、onEventの引数型はIEvent<?>で中身の型情報は失われており、従ってgetPayloadは Object型を返す、と。
そして、具体的なイベントの種別を判定するために"event.getPayload() instanceof ImageRefresh"とかやらなきゃいけない。
ダサすぎて牛乳吹くわ。"instanceof"が許されるのは(ry
Scalaでやってみる
この手の型に応じた処理の振り分けをScalaで行う場合、ふたつの選択肢があります。ひとつはパターンマッチングで型パターンを指定する方法、 もう一つはgeneralized type constraintsによる型パラメータによるメソッド定義です。
後者の方法は、様々な型のイベントを受け取れるようにするためには向いてないので、ストレートに前者の方法で実装しました。
では、デモで使用されたサンプルを改修してみます。元のソースコードはこちらです。
で、こちらがイベント処理周りをScalaで書き換えたものです。
yuroyoro/wicket15sample · GitHub
Eventを受け取ることができるtrait ReactableComponent
onEventを実装し、イベントを振り分けるtraitとしてReactableComponentを用意します。
trait ReactableComponent extends Component { // このtraitはComponentにmixinできる this: Component => // イベントを処理するPartialFunction[Any, Unit] private val handlers = scala.collection.mutable.ArrayBuffer.empty[PartialFunction[Any, Unit]] // 引数のPartialFunction[Any,Unit]をイベントハンドラとして登録する final def handler( f:PartialFunction[Any,Unit]):Unit = { handlers += f } override final def onEvent(event:IEvent[_]):Unit = { val e = event.getPayload // 登録されてるPartialFunctionで処理できるものにイベント処理を行わせる handlers.filter{ _ isDefinedAt e}.foreach{ f => f(e) } } }
このtraitは、self type annotationでComponentにmixiinされる前提です。
handler関数で引数にもらったPartialFunctionをイベントハンドラとして覚えておき、 onEventが呼び出されるとhandler関数で登録されたPartialFunctionのうち、イベントが処理できるものに処理を行わせるようになっています。
具体的なイベント処理を行うtrait EventReactor
次に、実際にそれぞれのイベント処理を行うtraitとして、EventReactorを用意しました。
メッセージに対応した処理を行う各handlerは、このtraitを継承します。
trait EventReactor extends Component { this: ReactableComponent => }
EventReactorは、self type annotationでさっきのReactableComponentがあらかじめmixinされてる前提です。
ReactableComponentで定義されているhandler関数をするためです。
では、実際のhandlerを見てみます。以下は、RemoveRequestというメッセージを受け取ったら自信を非表示にする処理を 行うRemoveReactorと、ImageRefreshイベントを受け取ったら画像のsrcのURLをリフレッシュするImageRefreshReactorです。
trait RemoveReactor extends EventReactor { this:Component with WebMarkupContainer with ReactableComponent => handler{ case event:RemoveRequest => setVisibilityAllowed( !event.isRemove ) } } trait ImageRefreshReactor extends EventReactor { this: Component with ReactableComponent with ImageSrcComponent with SelectableComponent => handler{ case event:ImageRefresh[_] => if (selected) { reload() selected = false event.addComponent(this) } } }
具体的なイベント処理は、handler関数にPartialFunctionで渡している箇所で行っています。handler関数は PartialFunctionを引数にとり、かつ{case x:Type => ...}のように書くとPartialFunctionに変換される、という仕組みを利用しています。
実際に、これらのtraitをmixinしたコンポーネントを見てましょう。
class RssImage(id:String) extends AjaxLink[java.lang.Void](id) with ReactableComponent with ImageSrcComponent with SelectableComponent with ImageRefreshReactor with RemoveReactor { import scala.util.control.Exception._ def getImageSource() = allCatch opt{ val app = WicketApplication.get val rss = app.getRssSource val entry = rss.getRandomEntry entry.getEnclosures.get(0).asInstanceOf[SyndEnclosure].getUrl } override def onClick(target:AjaxRequestTarget ) = { selected = !selected; target.add(this); } }
AjaxLinkを継承したRssImageは、ImageRefeshReactorとRemoveReactorをmixinしているため、RemoveRequestを受け取ると非表示になり、ImageRefreshを受け取ると画像を再描画します。しかし、このComponent自身にはどこにもイベント処理が記述されていません。
EventReactorトレイトがイベントの型を型パラメータまたは抽象型メンバーでもらわない設計になっているのは、ちゃんと理由があります。
trait EventReactor[A] {...} trait RemoveReactor extends[RemoveRequest] {...} trait ImageRefeshReactor extends[ImageRefresh] {...}
上記のような設計になっていないのは、RemoveReactorとImageRefreshReactorが同じComponentに同時にmixinされる可能性があるためです。同じ継承先のEventReactorの型変数をそれぞれ違う型でextendsしたtraitを同時にmixinすると、traitの線形化ができなくなってコンパイルエラーとなります。
traitによって個々の処理をPartialFunctionで登録するパターンマッチング
traitで処理を定義してる、handlers{ case x:Type => ...}って箇所、何かに似ていると思いませんか?
これは、scalaのActorライブラリでのメッセージ処理に似ています。ってか、このアイデアはActorからぱくりました。
加えて、self type annotationでmixinできる対象を限定し、self type annotationで指定した型に定義されている関数などを使ってtrait側で抽象化した処理を実装できるようになっているのがポイントです。
WicketのComponentのように複雑なクラス階層に対して横断的にtraitで処理を織り込む方法は、AOPに似てますがはるかに簡潔で安全です。
ね?Scalaって、エロガントでしょう?
(皿うどん)Structural Subtyping(構造的部分型)アレコレ
このまえ、夢の中でね、あるコレクションの中から特定のシグニチャを持つオブジェクトをより分けるようなコード書いてる夢見たんですよ。ええ、見たんです夢で。
で、ふと目が覚めて(深夜3時半)おもむろにREPLでいろいろやってみた結果を書こうと思います。ええ、書いてみます。
Structural Subtyping(構造的部分型)って何ぞ?
例から入ります。あるメソッドのシグニチャ(名前、引数の例えば{def mkString (start:String, sep:String, end:String):String}というシグニチャを持つ型Aを定義すると、上記のシグニチャを持つ型Bや型CはAの派生型と見なされるわけです。
このように、2つの型がもつメソッドなどの構造によって派生関係が決まるのがStructural Subtyping(構造的部分型)というものです。対して、通常のextendsなどの宣言によって派生関係を決定するのはNominal Subtyping(公称型)といいます。OCamlをご存じの方にはおなじみだと思います。
さて、このStructural Subtypingを用いることで静的なduck typingが可能になります。特定のシグニチャを持つオブジェクトのみを引数として受け入れるメソッドとかが定義できちゃって、コンパイル時に型チェックしてくれるわけですね。
さっきの例で、mkStringというString3つを引数に取ってStringを返すメソッドを持つ型として、Stringify型を定義します。ちなみに、指定できるメソッドは複数でも構いません(ついでにtoStringも追加してみました意味ないけど)。複数指定した場合は全てのシグニチャを満たさないと派生型と見なされませんので。
scala> type Stringify = { | def mkString (start:String, sep:String, end:String):String | def toString:String | } defined type alias Stringify
では、このStringify型を利用して、def mkString (start:String, sep:String, end:String):Stringを持つオブジェクトを引数に取るメソッドmkStringWithParenthesesを定義します。
scala> def mkStringWithParentheses(target:Stringify):String = | target.mkString("(",", ",")") mkStringWithParentheses: (target: Stringify)String
Stringify型のシグニチャを満たす型として、例えばList[Int]などが考えられます。mkStringWithParenthesesに渡してみましょう。
scala> mkStringWithParentheses( List(1,2,3,4,5) ) res4: String = (1, 2, 3, 4, 5)
問題ないですね。では、Stringify型のシグニチャを満たさない型を与えてみましょう。例えばjava.util.Dateなどです。
scala> mkStringWithParentheses( new java.util.Date ) <console>:8: error: type mismatch; found : java.util.Date required: Stringify mkStringWithParentheses( new java.util.Date )
mkStringを持っていないのでコンパイルエラーです。やったー!!
ところで、type aliasとして事前にStringify型を定義しましたが、いきなりメソッドの引数の型に書いてしまうこともできます。
scala> def mkStringWithParentheses(target:{def mkString (start:String, sep:String, end:String):String}):String = target.mkString("(",", ",")") mkStringWithParentheses: (target: AnyRef{def mkString(start: String,sep: String,end: String): String})String
応用編その1 パラメータ化されたStructural Subtyping
Structural Subtypingとして定義しようとしているメソッドが型パラメータを取るように定義されている場合はどうでしょうか? 要素へのランダムアクセスを行えるindexOfを持つ型としてRandomAccessable[A]を定義します。
scala> type RandomAccessable[A] = { | def indexOf[B >: A](elem:B):Int | } defined type alias RandomAccessable
注意点として、Structural Subtypingで定義する型のメソッドないで、その型に渡された型パラメータをメソッド引数に直接利用することはできません。以下のような定義はエラーになります。
scala> type RandomAccessable[A] = { | def indexOf(elem:A):Int | } <console>:6: error: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement def indexOf(elem:A):Int
これは型パラメータの共変/反変と同じ考え方で、Aより大きい型パラメータBを導入することで解決できます。
次は、もう少し複雑で、foldLeftとheadを持つ例です。
scala> type FoldLeftfy[A] = { | def foldLeft[B](z: B)(op: (B,A) => B) : B | def head:A | } defined type alias FoldLeftfy scala> val list:FoldLeftfy[Int] = List(1,2,3) list: FoldLeftfy[Int] = List(1, 2, 3) scala> list.foldLeft(0){ _ + _ } res4: Int = 6 scala> list.reverse <console>:8: error: value reverse is not a member of FoldLeftfy[Int] list.reverse
これは問題なく定義できますし、FoldLeftfy[Int]型のlistに対してfoldLeftを呼び出せます。逆に、reverseなど通常のList型で定義されているメソッドは利用できません。
もちろん、FoldLeftfy型を引数に取る関数も定義できます。以下は、sumおよびmaxを行う関数です。
// implicit parameterでNumericへのimplicit conversionを行う関数numをもらう def sum[B](xs:FoldLeftfy[B])(implicit num: Numeric[B]) = xs.foldLeft(xs.head){(b,a) => num.plus(b,a) } // context boundを利用してnumをimplicitlyで取得 def sum[B:Numeric](xs:FoldLeftfy[B]) = xs.foldLeft(xs.head){(b,a) => implicitly[Numeric[B]].plus(a,b) } // implicit parameterでOrderingへのimplicit conversionを行う関数cmpをもらう def max[B](xs:FoldLeftfy[B])(implicit cmp: Ordering[B]) = xs.foldLeft(xs.head){(b,a) => if(cmp.gt(b,a)) b else a } // context boundを利用してcmpをimplicitlyで取得 def max[B:Ordering](xs:FoldLeftfy[B]) = xs.foldLeft(xs.head){(b,a) => if(implicitly[Ordering[B]].gt(b,a)) b else a } // view boundを利用して、BはOrderedに変換された状態で扱える def max[B <% Ordered[B]](xs:FoldLeftfy[B]) = xs.foldLeft(xs.head){(b,a) => if( b > a ) b else a }
応用編その2 型の合成
応用編その1で、RandomAccessableとFoldLeftfyのふたつの型を定義しました。
type FoldLeftfy[A] = { def foldLeft[B](z: B)(op: (B,A) => B) : B def head:A } type RandomAccessable[A] = { def indexOf[B >: A](elem:B):Int }
このふたつの型を同時に満たす型は定義できるのでしょうか?結論から言うと可能です。
以下のようにすればよいのです。
type RandomAccessableFoldLeftfy[A] = RandomAccessable[A] with FoldLeftfy[A]
このRandomAccessableFoldLeftfy[A]は、indexOfとheadとfoldLeftを持っている型になります。以下のように、全てを持っているList[Int]型はRandomAccessableFoldLeftfy[Int]型の変数に代入できますが、indexOfを持たないIterable[Int]型は代入できません。
scala> val list:RandomAccessableFoldLeftfy[Int] = List(1,2,3) list: RandomAccessableFoldLeftfy[Int] = List(1, 2, 3) scala> val iter:RandomAccessableFoldLeftfy[Int] = List(1,2,3).toIterable <console>:8: error: type mismatch; found : Iterable[Int] required: RandomAccessableFoldLeftfy[Int] val iter:RandomAccessableFoldLeftfy[Int] = List(1,2,3).toIterable
また、通常のtraitとも合成することができます。
scala> type FoldLeftfySet[A] = Set[A] with FoldLeftfy[A] defined type alias FoldLeftfySet scala> val set:FoldLeftfySet[String] = Set("foo","bar") set: FoldLeftfySet[String] = Set(foo, bar)
応用編その3 implicit conversionとの組み合わせ
これはimplicit conversionを利用するのでリスキーですが、implicit conversionの変換元の型としてStructural Subtypingを指定できます。以下のような、FoldLeftfyをwrapするクラスFoldLeftfyProxyを用意し、FoldLeftfy[A]からFoldLeftfyProxy[A]への変換関数を用意します。
scala> class FoldLeftfyProxy[A:Numeric](xs:FoldLeftfy[A]){ | | def productAll:A = xs.foldLeft(xs.head){(b,a) => implicitly[Numeric[A]].mkNumericOps(b) * a } | } defined class FoldLeftfyProxy scala> scala> implicit def foldLeftfy2Proxy[A:Numeric](xs:FoldLeftfy[A]) = new FoldLeftfyProxy(xs) foldLeftfy2Proxy: [A](xs: FoldLeftfy[A])(implicit evidence$1: Numeric[A])FoldLeftfyProxy[A]
FoldLeftfyProxyには、通常のSeqトレイトなどにはないproductAllというメソッドが定義されています。'pimp my libraly'パターンで、List(1,2,3).productAllと呼び出すと、FoldLeftfyProxy[Int]にimplicit conversionで変換されてproductAllが呼び出されるはずです。
scala> List(1,2,3).productAll res11: Int = 6
ちゃんと動いてますね。ただ、むやみにimplicit conversionで変換しちゃうのは危険なので使いどころ注意ですよ。
注意点 パターンマッチとStructural Subtyping
パターンマッチでStructural Subtpingを指定してもマッチしません。このようなメソッドを用意してコンパイルすると、
def match_?[A](xs:Traversable[A]) = xs match { case xs:FoldLeftfy[A] => true case _ => false }
こんな警告でます。型情報がイレイジャにより消されるから、パターンマッチしないってわけです。
StructuralSubtyping.scala:36: warning: refinement AnyRef{def foldLeft[B](z: B)(op: (B, A) => B): B; def head: A} in type pattern Main.FoldLeftfy[A] is unchecked since it is eliminated by erasure case xs:FoldLeftfy[A] => true ^ one warning found
jadってみるとわかりますが、ふつーに消えてます
public boolean match_$qmark(Traversable xs) { Traversable traversable = xs; return traversable != null; }
(もりそば)Scalaによる高階型変数 - Higher-kind Generics
「ハイヤーーーーーッッ!!!」
と気合いを入れたところで、今日はScalaのコレクションライブラリで多用されている、Higher kind genericsについて軽く解説したいと思いハイヤーーーーーッッ!!!
Higher-kind Generics(type constructor parameter)って?
まぁぶっちゃけ@kmizu が解説してくれてるのでコレ読んでもらえばおけです。
ScalaのHigher-kind Genericsについて - Togetter
そういえば最近Javaでもid:Nagiseによるこんな話がありましたね。
Javaによる高階型変数の実装 - プログラマーの脳みそ
Java による高階型変数と Scala とジェネリクス - Togetter
要は、型パラメータを取る型パラメータ(?)が使えるよって話。
trait AttrHelper[+Holder[X]]
とあったときに、Holderってのは型パラメータで、そいつは更にXという型パラメータをとるなんらか、という用に定義できるんだなぁ。
ちなみにHigher-kind Genericsってのは水島さんが命名した名前で、とぅぎゃったーにあるとおり言語仕様書では"type constructor parameter"というようです。
"3.3.3 TypeConstructors"と"4.4 Type Parameters"、ChangeLogの"Changes in Version 2.5 (02-May-2007) Type constructor polymorphism"あたりを見るとよいでしょう。
型コンストラクタを型パラメータに取る例
論よりコード。例を見ます。
// 型コンストラクタ type IntMap[A] = scala.collection.immutable.Map[Int, A] type StringMap[A] = scala.collection.immutable.Map[String, A] // ある型のMapにproxyするtrait trait MapProxy[A,B, ReprMap[B] <: Map[A,B]] { val underlying:ReprMap[B] }
IntMap, StringMapのふたつの型を定義しました。どちらも、Aという型パラメータを取ってMap[Int, A]とかMap[String, A]とかいう"型"を返す型コンストラクタです。
で、この型コンストラクタを型パラメータReprMapで取る、MapProxyというtraitを定義しました。
こいつは、MapのKeyの型A、値の型Bと、ProxyするMapの型を返す型コンストラクタReprMapを型パラメータでもらいます。underlying は、Proxyする対象となる実際のMap型の変数です。
このtraitを使うクラスは、こんな風に書けます。以下は、String型のKeyをもつMapへproxyするクラスと、Int型のKeyを持つMapへproxyするクラスです。
class StringMapProxy[A](elems:(String, A)*) extends MapProxy[String, A, StringMap]{ val underlying:StringMap[A] = Map(elems:_*) def apply(key:String):A = underlying(key) } class IntMapProxy[A](elems:(Int, A)*) extends MapProxy[Int, A, IntMap]{ val underlying:IntMap[A] = Map(elems:_*) def apply(key:Int):A = underlying(key) }
実際に使ってみます。
scala> val ssm = new StringMapProxy("foo"->"bar","hoge"->"fuga") ssm: StringMapProxy[java.lang.String] = StringMapProxy@199ec67 scala> ssm.underlying res9: StringMap[java.lang.String] = Map(foo -> bar, hoge -> fuga) scala> ssm("foo") res10: java.lang.String = bar scala> val sim = new StringMapProxy("foo"->1,"hoge"-> 2) sim: StringMapProxy[Int] = StringMapProxy@741bb804 scala> sim.underlying res11: StringMap[Int] = Map(foo -> 1, hoge -> 2) scala> val ibm = new IntMapProxy( 1 -> true, 2 -> false) ibm: IntMapProxy[Boolean] = IntMapProxy@785850e3 scala> ibm.underlying res12: IntMap[Boolean] = Map(1 -> true, 2 -> false) scala> ibm(1) res13: Boolean = true
StringMapProxy, IntMapProxyともに、proxyするMapの型が型パラメータReprMapで与えた型になっていることがわかると思います。
scala.collectionパッケージでの利用例
scala.collectionパッケージでの利用例を見てます。
以下は、GenericCompanionの定義です。こいつは、型に応じたBuilderを返すためのコンパニオンで、CanBuildFromとかを型に合わせていい感じにimplicit conversionする、2.8のコレクションライブラリのキモッ!となる抽象クラスです。
// CCは型Xを取ってTraversable[X]以下の型を返す型コンストラクタ abstract class GenericCompanion[+CC[X] <: Traversable[X]] // Traversableを生成する抽象クラス // GenericCompanionを継承してる。 abstract class TraversableFactory[CC[X] <: Traversable[X] with GenericTraversableTemplate[X, CC]] extends GenericCompanion[CC] // 実際のTraversableの定義。ここでCCはTraversableになってる object Traversable extends TraversableFactory[Traversable] { implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Traversable[A]] = new GenericCanBuildFrom[A] def newBuilder[A]: Builder[A, Traversable[A]] = new mutable.ListBuffer }
実践的な例:特定の型のIterableへProxyするクラス
まぁコードと利用例見てください。Iterableをproxyしつつ、mapやfilterしたときに、Traversableとかじゃなく自分の型を返すようにするためのtraitです。
import scala.collection.{Iterable, IterableLike} trait TypedIterableProxy[A, Repr<: Iterable[A]] extends Iterable[A] with IterableLike[A, Repr]{ import scala.collection.generic.CanBuildFrom import scala.collection.mutable.{ListBuffer, Builder} val self:Iterable[A] def newTo(from:Iterable[A]):Repr def iterator = self.iterator override def newBuilder:Builder[A, Repr] = new ListBuffer[A] mapResult {x => newTo(x) } implicit def canBuildFrom: CanBuildFrom[Repr, A, Repr] = new CanBuildFrom[Repr, A, Repr] { def apply(from: Repr):Builder[A, Repr] = newBuilder def apply() = newBuilder } }
// 特定の型のIterableへProxyするクラス class StringItrableProxy(v:String*) extends Iterable[String] with TypedIterableProxy[String, StringItrableProxy] { val self= v.toIterable def newTo(from:Iterable[String]) = new StringItrableProxy( from.toSeq:_*) def hoge = map{ "hoge" + } } scala> val StringItrableProxy = new StringItrableProxy("aa", "bb", "cc", "abc") StringItrableProxy: StringItrableProxy = line56(aa, bb, cc, abc) scala> StringItrableProxy.filter{_.startsWith("a")} res8: StringItrableProxy = line56(aa, abc)
Scalaのバージョンを簡単に切り替えられる"svm"ってヤツ作った (冷やし中華終わりました)
まぁタイトルの通りなんですが(冷やし中華は終わってます)、RubyだったらrvmとかPythonだったらvirtualenvとか、使うバージョン切り替えられるヤツあんじゃないスカ?
Scalaでもあったらいーなーと思ってシェルスクリプトで書きました。Scalaの場合は、依存ライブラリをランタイムのバージョン毎に切り替える必要はないので、単純にSCALA_HOMEを切り替えれば事足りるってー訳で。
ここに置いてあります。
この前trunkに入ったDynamic typeの調査をするはずが、何故かシェルスクリプトを書き始めていた……。何を言って(ry
概要
インストールされてるScalaのバージョンを切り替えたり、指定したバージョンをダウンロードしてきたりできます。
こいつを使えば、気軽にnightly buildを試したり、stableに戻ったり、新しいリリースを素早くインスコできたりするって寸法でさぁ。
設定
シェルスクリプト(svm)を落としてきたらPATHが通ってるとこに配置して実行権限をつけてください。
あと、環境変数PATHに、"$HOME/.svm/current/rt/bin"を追加しておいてください。
export SCALA_HOME=~/.svm/current/rt export PATH=$SCALA_HOME/bin:$PATH
readlinkを使ってるのでcoreutilsが必要です。macportとかhomebrewで入れておいてください(ってかふつー入ってるよね?)
svmでは、"$HOME/.svm"以下にversion毎にディレクトリを作成して、Scalaのバージョンを配置します。カレントのバージョンは、"$HOME/.svm/current"というシンボリックリンクによって、対象バージョンのディレクトリを指すようにすることで設定されます。
ようは、こんなディレクトリ構成なわけです。
ozaki@mbp $ ls ~/.svm total 8 drwxr-xr-x 7 ozaki staff 238 1 21 20:28 . drwxr-xr-x 169 ozaki staff 5746 1 21 20:28 .. lrwxr-xr-x 1 ozaki staff 30 1 21 20:28 current -> /Users/ozaki/.svm/scala-latest drwxr-xr-x 5 ozaki staff 170 1 21 19:54 scala-2.8.1.RC1 drwxr-xr-x 3 ozaki staff 102 1 21 19:59 scala-2.8.1.RC2 drwxr-xr-x 5 ozaki staff 170 1 21 20:17 scala-2.8.1.final drwxr-xr-x 5 ozaki staff 170 1 21 20:28 scala-latest
各バージョンのディレクトリ以下には、3つのディレクトリがあります。
- rt : ランタイムが配置される
- devel-doc : ScalaDocが配置される
- sources : SDKのソースファイルが配置される
すでにインストールされているScalaをsvm管理下に置きたい場合は、上記のディレクトリ構成に従って配置すればおけです。
使い方
svm helpってやってもらえば大体わかるわけですが。
ozaki@mbp $ svm help Usage : svm [Action] [Scala-Version] [Options] Action : -h|help - show this usage information -c|current - show the currently use scala version -l|list|versions - show the scala version installed in svm_path(default is /Users/ozaki/.svm) -i|install - install specific scala version -r|remove|uninstall - uninstall specific scala version and remove their documents and sources -s|switch|-u|use - setup to use a specific scala version update-latest - install or update nightly build scala version latest - setup to use nightly build scala version stable - setup to use stable(x.x.x.final) scala version (any strings) - setup to use specific scala version(shortcut of svm switch) Options : --no-docs - with install, update-latest.skip to download scala-devel-docs. --no-sources - with install, update-latest.skip to download scala-sources.
"svm install 2.8.1.final"とやると、.svm以下にscala2.8.1.finalをダウンロードして来ます。ダウンロードが終わったらこいつをcurrentのversionに設定するか聞かれるのでお好みで設定して。
ozaki@mbp $ svm install 2.8.1.final Trying to installing version of 2.8.1.final. in /Users/ozaki/.svm/scala-2.8.1.final --2011-01-21 18:58:53-- http://www.scala-lang.org/downloads/distrib/files/scala-2.8.1.final.tgz Resolving www.scala-lang.org... 128.178.154.159 Connecting to www.scala-lang.org|128.178.154.159|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 20203153 (19M) [application/x-gzip] Saving to: `scala-2.8.1.final.tgz' 100%[===================================================================================================================================================>] 20,203,153 124K/s in 3m 19s 2011-01-21 19:02:13 (98.9 KB/s) - `scala-2.8.1.final.tgz' saved [20203153/20203153] ... Successfully installed version of 2.8.1.final. installed directory is /Users/ozaki/.svm/scala-2.8.1.final Do you want to change current runtime version to 2.8.1.final? y/N: y currently version is 2.8.1.final.
"svm current"で現在使用してるversionを表示します。
ozaki@mbp $ svm current currently version is /Users/ozaki/.svm/2.8.1.final
"svm 2.8.1.RC1"のようにバージョン番号を指定すると、そのバージョンに切り替えます。指定したバージョンが無い場合はダウンロードに行くか聞いてきます。
ozaki@mbp $ svm 2.8.1.RC1 currently version is 2.8.1.RC1 ozaki@mbp $ scala Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8 Welcome to Scala version 2.8.1.RC1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_22). Type in expressions to have them evaluated. Type :help for more information. scala>
"svm stable"とやると、インストールされてる中で最新の安定バージョンに切り替わります。
ozaki@mbp $ svm stable currently version is 2.8.1.final
"svm update-latest"は、nightly-buildをダウンロードします。既にダウンロードされてる場合は最新のものに置き換えます。
ozaki@mbp $ svm update-latest in /Users/ozaki/.svm/scala-latest --2011-01-21 20:04:22-- http://www.scala-lang.org/archives/downloads/distrib/files/nightly/distributions/scala-2.9.0.latest.tgz Resolving www.scala-lang.org... 128.178.154.159 Connecting to www.scala-lang.org|128.178.154.159|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 43338980 (41M) [application/x-gzip] Saving to: `scala-2.9.0.latest.tgz' ...
"svm uninstall 2.8.1.RC1"でディスクから消し去ります。あばばばばばばばば。
"svm list"または"svm versions"で、インストールされているバージョンを表示します。
ozaki@mbp $ svm list 2.7.1.final 2.7.3.final 2.7.4.final 2.7.5.final 2.7.6.final 2.7.7.final 2.7.7.RC2 2.8.0.Beta1-prerelease 2.8.0.Beta1-RC2 2.8.0.final 2.8.0.latest 2.8.0.RC1 2.8.0.RC2 2.8.0.RC3 2.8.0.RC5 2.8.0.RC6 2.8.0.RC7 2.8.1.final 2.8.1.RC 2.8.1.RC1 2.8.1.RC2 2.8.1.RC4 2.8.8.RC5 latest currently version is /Users/ozaki/.svm/latest
だいたいこんなところですわ。なんかバグ見つけたらレポートしてくれたりパッチくれたりpull-requestくれたりするとありがたいです。