finagel-http path の紹介
これは Finagle Advent Calendar 2012 (だいたい俺) の16日目です。
Finagle には HTTP通信用の http サブプロジェクトがありますが、この中に path というパッケージと Path によって Routing を行う RoutingService というものが提供されています。今日はこれの紹介をしようと思います。
Routing
RoutingService は リクエストパスによって実行する Service を切り替える Service です。
具体的に使い方を見てみましょう。
def main(args: Array[String]) { val service = RoutingService.byPathObject { case Root => IndexService case Root / "users" / userId => UserDetailService(userId) } ServerBuilder() .codec(RichHttp[Request](Http())) .bindTo(new InetSocketAddress(8080)) .name("Finagle Advent Calendar 2012") .build(service) }
こんな感じで `RoutingService.byPathObject` に `PartialFunction[Path, Service[REQ, RES]]` 渡すことで、リクエストで渡された path に応じて呼び出す Service を切り替えてくれます。
`PartialFunction` に match する case が存在しなかった場合は、`NotFoundService` が使用されます。
/ Extractor
path の階層区切りには / を使うことができます。
val service = RoutingService.byPathObject { case Root / "users" => UsersListService case Root / "users" / userId => UserDetailService(userId) case Root / "users" / userId / "followers" => FollowersService(userId) }
基本的に文字列と / で階層を表現します。間の文字列もパターンなので、例の `userId` のように変数パターンを使うことで渡されてきた値を変数として使用することができます。
:? :& extractor
クエリパラメータに対してマッチさせたい場合には、 :? や :& を使うことができます。
事前に `ParamMatcher` を利用して、クエリパラメータ名に一致する Extractor を用意します。たとえば `/details?userId=xxx&targetid=yyy` のような URL にマッチさせたい場合、以下の様になります。
object UserId extends ParamMatcher("userId") object TargetId extends ParamMatcher("targetId") val service = RoutingService.byPathObject { case Root / "details" :? (UserId(u) :& TargetId(t)) => ... case ... }
ちなみに Extract する値の型を Int や Long や Dobule などを使いたい場合、`IntParamMatcher` や `LongParamMatcher` や `DoubleParamMatcher` などを使うことで特定の型で取得することができます。
また、ポイントとしてクエリパラメータの順序はきちんと無視して解釈してくれます。`Root / "details" :? (UserId(u) :& TargetId(t))` という定義であれば `/details?targetid=yyy&userId=xxx` という値が送られてきても適切にマッチします。
Integer, Long Extractor
またパラメータだけでなく、パスの値を Int などで取得したい場合は、 Integer Extractor などを使うことができます。
val service = RoutingService.byPathObject { case Root / "users" => UsersListService case Root / "users" / Integer(userId) => UserDetailService(userId) }
~ Extractor
ファイルの拡張子によって分岐をしたい場合は ~ を使うことができます。
val service = RoutingService.byPathObject { case Root / "users" ~ "json" => UsersListService.map(_.asJson) case Root / "users" ~ "xml" => UsersListService.map(_.asXml) }
-> Extractor
HTTP メソッドによって分岐をしたい場合 -> を使うことができます。
import Method._ val service = RoutingService.byPathObject { case Get -> Root / "users" => UsersListService case Post -> Root / "users" => CreateuserService }
まとめ
というわけで finagle-http の path パッケージの紹介でした。公式でもドキュメントがなく、現状ではテスト を見るしかないというマイナー機能ですが、 Finagle で HTTP サーバを立てる場合には使えるかもしれません。
twitter-util Spool の紹介
この記事は Play or Scala Advent Calendar 2012 の9日目です。
はじめに
twitter-util には cuncurrent に関する基本的なクラスが提供されています。Scala本体に取り込まれる予定の `Future` や、非同期にオブジェクトを仲介する `Offer` や `Broker` などはよく知られているかと思います。
今日はそんな中であまり話題に上らない `Spool` を紹介したいと思います。
Spool とは
`Spool` は `Stream` とそっくりな lazy な評価戦略を持つ非正格なリストデータ構造です。`Stream` との決定的な違いは、要素の評価計算が非同期に行われるという所です。
つまり、 `head` の呼び出しで先頭の1要素を取得しようとした場合には、同期呼び出しで即座に値が返されますが、`tail` で2件目以降の要素を取得しようとすると、`Future[Spool[A] ]` 型の `Future` が即座に返却されます。実際の `Spool[A]` を取得するにはこの `Future` の完了を待つ必要があります。
それ以外は `Stream` とあまり変わりません。内部的には非同期に解決されますが、`foreach` や `map` といったメソッドも持っています。また、`Stream` が `#::` といったメソッドと Extractor を持っているように、`Spool` にも `*::` と `**::`というメソッド及び Extractor があります。
サンプル
試しに REPL で遊んでみましょう。前準備として、呼び出したら非同期に3秒後の `Date` を返してくれる `threeSecondsAfter` メソッドを定義してみます。
scala> :paste // Entering paste mode (ctrl-D to finish) import com.twitter.util._ import java.util.Date import com.twitter.conversions.time._ import com.twitter.concurrent._ import com.twitter.concurrent.Spool._ implicit val timer = new JavaTimer def threeSecondsAfter: Future[Date] = timer.doLater(3.seconds)(new Date) // Exiting paste mode, now interpreting. import com.twitter.util._ import java.util.Date import com.twitter.conversions.time._ import com.twitter.concurrent._ import com.twitter.concurrent.Spool._ timer: com.twitter.util.JavaTimer = com.twitter.util.JavaTimer@ecfd5a threeSecondsAfter: com.twitter.util.Future[java.util.Date] scala>
`threeSecondsAfter` の 戻り値型は `Future[Date]` なので `onSuccess` で実際の値を確認します。
scala> threeSecondsAfter.onSuccess(println) res0: com.twitter.util.Future[java.util.Date] = Promise@24539713(ivar=Ivar@21448365(state=Waiting(List(),List())), cancelled=Ivar@3420708(state=Waiting(List(<function1>),List()))) scala> Sun Dec 09 22:27:35 JST 2012
`onSuccess` が即座に評価されて res0 として表示された後、3秒後に日時が表示されるのが確認できるかと思います。
っていうかもう22時過ぎてる!やばい!Advent Calendarの担当日過ぎてしまう!
気を取り直して、このメソッドを使って、3秒間隔の `Date` の無限列 つまり `Spool[Date]` を作ってみましょう。
scala> def threeSeconds: Future[Spool[Date]] = threeSecondsAfter.map(_ *:: threeSeconds)
threeSeconds: com.twitter.util.Future[com.twitter.concurrent.Spool[java.util.Date]]
`Stream` の `#::` と同じように `*::` メソッドで先頭に要素を追加することができます。これで `Spool[Date]` が手に入りました。これの要素を全て表示してみましょう。
scala> threeSeconds.onSuccess(_.foreach(println)) res2: com.twitter.util.Future[com.twitter.concurrent.Spool[java.util.Date]] = Promise@25639455(ivar=Ivar@16315671(state=Waiting(List(),List())), cancelled=Ivar@10668522(state=Waiting(List(<function1>),List()))) scala> Sun Dec 09 22:37:31 JST 2012 Sun Dec 09 22:37:34 JST 2012 Sun Dec 09 22:37:37 JST 2012 Sun Dec 09 22:37:40 JST 2012 Sun Dec 09 22:37:43 JST 2012 Sun Dec 09 22:37:46 JST 2012 Sun Dec 09 22:37:49 JST 2012 Sun Dec 09 22:37:52 JST 2012 Sun Dec 09 22:37:55 JST 2012
REPL上で `foreach` が解決された後に延々に3秒間隔で表示されるのがわかるかと思います。こんな感じで `Spool` を使う事で、非同期な計算を必要とする処理のシーケンスを得る事ができます。
実用例
さてさて、この `Spool` を使用しようした具体例として、これから発生するであろうイベントの連続を `Spool` を使って表すというものがあります。
Finagle には、Zookeeper を利用してサーバクラスタを構成する機能があります。このクラスタを表現するためのクラスが、 `Cluster` なのですが、この `Cluster` は `snap` というメソッドを持っています。
この `snap` メソッドの戻り値型は `(Seq[T], Future[Spool[Cluster.Change[T] ] ])` となっています。タプルの第一要素は現在のクラスタを構成しているオブジェクトの集合を表しています。そして第二要素が、これからこのクラスタに訪れるであろう変更イベント`Cluster.Change[T]`つまり、クラスタへの参加や離脱といったイベントですね、これらの `Spool` となっています。
この `Spool` を使う事で、`snap` を呼び出した側は現在のクラスタ構成だけでなく、将来に渡るクラスタの変更にも対応できるようになっているのです。
まとめ
という訳で、lazy かつ非同期な評価戦略を持つシーケンスである `Spool` の紹介でした。個人的には中々面白いデータ構造だと思っています。使う場所によっては `Broker` 等よりも使う側が楽なインターフェイスを提供できるかと思います。
みなさんも是非 `Spool` で遊んでみてください。
2013-01-04 18:00 修正
一部用語の使い方が正しくなかったので修正。参考: 遅延評価いうなキャンペーンとかどうか
Doma で Joda-Time を使う
Annotation Processing を活用する Java製 ORM の Doma ですが、version 1.25.0 から外部のクラスをDomainとして使う事ができるという機能が実装されました。
そこで、Joda-Time の DateTime などを使えるように DomainConverters を作成してみました。https://github.com/gakuzzzz/doma-joda-time
以下の様に Entity に直接 DateTime や LocalDate などを持たせて Timestamp型などのデータをマッピングさせることができます。
public class Employee { EmployeeId id; Name name; DateTime createdAt; }
現状なぜか、 DomainConverters を利用したオブジェクトを使用している Entity を変更すると Doma-Tool が クラス[...]は、永続対象の型としてサポートされていませんというエラーを出すという状況に遭遇しています。プロジェクトをクリーンすると正常にコンパイルされるのですが、何かEclipseの設定の仕方が悪いのでしょうか? もし何かご存知の方がいましたら教えてください。
2012-12-09 19:10 追記
Eclipse側の問題だったようです。Doma 1.26.0 にてこの問題の対応を盛り込んで頂きました。1.26.0 では問題なく開発できています。ありがとうございます!
Scalaz の命名規則
この記事は Scalaz Advent Calendar 2012 の4日目です。
どこかで Scalaz7 の package や class/trait の命名規則について書いてというのを見た気がするのでその辺を書いてみようと思います。
ちなみにあくまで現状のソースコードを見た上での推測で、MLなり何なりで裏取った情報ではない事を先に明記しておきます。内容に誤りなどありましたらぜひぜひご指摘ください。
Suffix無し
主に scalaz パッケージ直下にある、Suffix のついていないクラス/トレイトは大きく分けて2種類に分類されます。
一つが Scalaz が提供する型クラス。Functor とか Foldable とかそういったモノですね。あくまで型クラスの定義であって、型クラスのインスタンスではありません。
もう一つが Scalaz が提供するデータ構造。Validation とか InsertionMap とかですね。余談ですが InsertionMap なかなか興味深いですね。^-^ メソッド とか味わい深いです。
HogeInstances
そして、Suffix に Instances とついているものが、そのデータ構造に対する型クラスインスタンス郡を提供するトレイトになります。 OptionInstances が Option に関する色々な型クラスのインスタンスを提供している感じですね。
この HogeInstances がトレイトになっているのは、importを細かく制御できるように、package object だったり個別の object だったりと複数の object に mixin できるようにするためと思われます。
HogeFunctions
Suffix に Functions がついているものは、そのデータ構造に関するグローバル関数的なものを提供するトレイトです。none[String] で Option[String] 型の None が取得できるのは OptionFunctions が none メソッド を提供しているからですね。
これも import範囲を制御できるようにトレイトとして定義され、複数のオブジェクトに mixin されています。
HogeOps
Suffix に Ops がついているものは、そのデータ構造あるいは型クラスインスタンスに、便利メソッドを提供したりなどの拡張を行うラッパーです。基本的にはimplicit conversionによって変換されるだけで明示的に HogeOps型に対して操作を行う事は無いでしょう。Option#getOrElse を | と書けるようになるのは OptionOps のおかげですね。
HogeSyntax
Suffix に Syntax がついているものは、その型クラスに対して、便利メソッドや省略記法を提供するものです。何も考えずに型クラスを大量につかってカタカタするとソースコード上の型表現が大変になってしまうので、なるべく Scalaz のユーザが明示的に型を書かなくても済むようにする働きがあるそうです。
正直Syntaxに関しては理解が曖昧なので識者に補足して頂けることを期待しております;-p
HogeLaw (2012-12-10 23:54 追記)
一つ重要なものを忘れていました。Scalazが提供する型クラスには、Suffix に Law がつくトレイトを提供しているものがあります。FunctorLaw や MonadLaw といったものですね。これらのトレイトは何を表しているかというと、その型クラスインスタンスが満たすべき法則が定義されています。
これらの法則は戻り値として Boolean を返すメソッドとして記述されていて、テストにて其々の型クラスインスタンスがその法則を満たしているかチェックを行っています。例えば、MonadLaw であれば ScalaCheck にて其々の law をまとめた laws が定義され、EitherTest 内で Either が MonadLaw を満たすかチェックしています。
(追記ここまで)
package 分け
package については、大きく分けて以下の4つの様になっています。
- scalaz パッケージ直下に、Scalaz が提供する型クラスとデータ構造およびそのデータ構造の型クラスインスタンスが収められています。
- scalaz.std パッケージ以下に Scala/Java の標準ライブラリにあるデータ構造に対する型クラスインスタンスが収められています。
- scalaz.syntax パッケージ直下には、Scalaz が提供するデータ構造と型クラスに対する Ops/Syntax が収められています。
- scalaz.syntax.std パッケージ以下 には、Scala/Java の標準ライブラリにあるデータ構造に対する Ops/Syntax が収められています。
まとめ
以上が簡単な Scalaz の命名規則になります。自分で型クラスを提供するようなライブラリを作成している場合に、型クラスインスタンスの置き場など参考になるのではないでしょうか。また、implicit のスコープを制御するために import しやすい形式になっているのは中々便利だと思います。この辺も自作ライブラリで参考にできるかと思います。
おしまい。
2012-12-18 00:45 追記
HogeSyntax について、さっそく識者の方々が補足してくれました!
- Scalaz の Syntax という仕組みの解説 @xuwei さん
- Scalazのかきかた @halcat0x15a さん
JavaOne Tokyo 2012 の JVM言語BoF でスピーカーしてきたよ
4/4-5 に行われた JavaOne Tokyo 2012 の JVM言語BoFに、スピーカーとして参加してきました。
BoFの概要は以下のような感じです。
JavaVM上で動作するさまざまなプログラミング言語 (Groovy、JRuby、Scalaなど) のコミュニティや興味を持つ個人が集まって、各言語のチュートリアルや最新情報、使用実績などの情報交換を行います。プレゼンテーション、各言語を使ったコーディング大会、ライトニング・トークなどの形式で、ユーザ間の交流を促すBoFとして開催し、言語を超えて活用が進むJavaVMの日本ユーザの裾野を広げます。飛び入り参加大歓迎、楽しい時間にしたいと思います。
https://oj-events.jp/public/session/view/173
僕は ScalaJP からコーディング大会の Scala 担当として発表させて頂きました。
あのじゅんいち☆かとうさんをしてエロガントと言わしめたScalaのコードがこちらになりますw
セッション中の時間では説明し切れなかった部分も多かったので、改めて補足してみようかなと思います。
Twitter連携
一つ目のお題はTwitter連携でした。レギュレーションの詳細はこちらに、Scalaのソースはこちらになります。
直前で Google翻訳が有料になっていたことが判明したため、急遽 Bing! の翻訳を使うことに。
このテーマでは Scala の特徴でもある関数型プログラミングの親和性を見せられると JRuby や Groovy とうまく差別化できるかな、と思って関数合成を中心に添えています。
ゆろよろさんの「オブジェクト指向プログラマへ捧げる関数型言語への導入」 その1, その2 を参考にさせて頂きました。
mainとなる部分が以下ですね
Console.in :*: read :>: translate :>: addHashTag :>: tweet
:*: も :>: も標準で用意されている apply と compose の別名です。単に左から順に書きたかったのと、合成する対象の関数名と見た目で区別しやすいように記号メソッド名として別名をつけた感じです。
こうすると 標準入力が read に渡って translate に渡って addHashTag に渡って tweet に渡される一連の流れが見たままになってるのが一目瞭然ですね。実は Groovy版もメソッドチェーンを使って同じ流れを表現しようとしていたのが面白かったです。
余談ですが、当初はみんな大好き Twitter4j を使おうかと思ってました。が、実際には Twitter には post するだけで Status の操作も何も行わないため、scalaj-http で実装してしまいました。
Benchmark
もう一つのお題がベンチマークでした。内容は赤黒木のMapを実装してそのパフォーマンスを測定する、というもの。細かいレギュレーションについてはリンク先を参照ください。
Scalaの思想にしたがって immutable版 と mutable版 の二つを作成しました。immutable版については、以前に Haskell で Purely Functional Data Structures の写経を行っていたためそれほど悩むことなく実装できました。
mutable版は left-leaning red-black tree というアルゴリズムで実装しています。
Groovyのコードが194行、JRubyのコードが195行に対して、Scala版では mutable版が112行、immutable版にいたってはなんと驚きの77行。
コメント行を取り除いてカウントしてもまだ倍近い差があります。動的型の二つの言語では書く必要の無い型情報まで書いてあるにもかかわらずこれだけの差が出るというのは Scala の記述力の高さが示されますねw
(もっとも Groovy版 はパフォーマンスの為に型情報を記述していますが)
(JRuby版 も縦に長いだけで一行の文字数は Scala版 に比べ圧倒的に短いので、文字数で比べると実は大差なかったりします)
そして気になるベンチマークの結果は以下のような感じになりました。
https://docs.google.com/spreadsheet/ccc?key=0AiZsKd8d4hSJdFpaejE4U3pLaUViZHRJQl9RbkFpRXc#gid=1
Groovy++ 圧倒的ですね。さすが。JRuby も Scala immutable版より若干速いです。
immutable版でメモ化などのimmutableならではの最適化など行えないのか?という疑問を頂いたんですが、レギュレーション的にメモ化が使えるところが少なく効果を発揮するには至りませんでした。地味に height 計算はメモ化というか再計算を行わないようになっているんですが、パフォーマンスの計測では heightの呼び出しを行わない形になっています。
Scala Compiler の理論的には Java とほぼ同じバイトコードが生成できる筈なので Java とこれだけ差がついているのが若干不思議ですね。Java版は一部再帰を使わずにループで処理していたそうで、ちゃんと末尾再帰にしてなかったのが影響してるのかなとも思っています。この辺は機会があれば試してみたいところです。
DSL
最後のお題がDSLです。レギュレーションの詳細はこちらに。
おそらく Groovy版 も JRuby版 も内部DSLで来るだろうなーと思ったので Scala版 では 内部DSL と 外部DSL の二つを作成しました。
それぞれの使い方を Spec に。内部DSLのSpec, 外部DSLのSpec
実はこれが難産でした。当初はパトリシアトライを模した階層化したデータ構造を作成して、そのデータ構造に変換するDSLを作ったんですが、DSLそのものよりもそのデータ構造の方が複雑になってしまうという結果になってしまった為、素直に HashMap[String, String] を生成する形に変更しました。java.util.Properties への変換は Map[String, String]型であれば何でも変換できるようにして内部DSLと外部DSLで共通化しました。
外部DSLはパーサコンビネータを使っての素直な実装になってます。 properties と nested のように相互再帰な文法もそのまま表現できるところが強いですね。
内部DSLは水島さんからDynamicVariableで現在のコンテキストを表す事で簡潔な表現ができるよというアドバイスを頂いて、DynamicVariableで実現してみました。標準はレキシカルスコープで、必要に応じてダイナミックスコープが使えるというのは非常に便利です。
ちなみに、Groovy や JRuby の実装で使用されている method missing の仕組みですが、Scala にも applyDynamicという仕組みがあり、-Xexperimental オプションを付けることで同じような事が実現できます。
まとめ
という訳でつらつらと書いてきましたが、最後に全体の感想をば。
初っ端からハッシュタグが違うというトラブルから始まり、ほどよく砕けた雰囲気で楽しかったです。各言語の解説もプレゼン自体に特徴がでてて面白かったですし、コーディング大会はナチュラルにdisりあう素敵空間でしたw
ねこはるさんのLTは静的型のScalaならではのもので最後まで皆に見ていただけなかったのは残念でしたね。僕のPCをねこはるさんにお貸ししたのですが、操作しづらかった様で申し訳なかったです><
ゆろよろさんの秘密兵器には爆笑させられました。流石としかいいようがないw 他のLTもみんな高度に笑いを取りに来るのでお腹痛かったですw
そんなこんなでJVM言語BoFはじめJavaOneでは非常に楽しい体験をさせて頂きました。JGGUGの皆様、日本JRubyユーザ会の皆様、ScalaJPの皆様、そして寺田さんをはじめJavaOne Tokyo 2012に関わった全ての皆様へ、心よりお礼申し上げます。
PlayBay で Play2.0 + pjax の話をしてきたよ
パソナテックさんのエンジニアカフェで行われた PlayBay - Play!Framework + WebSocket勉強会に講師として参加してきました。
Ustでも動画がみれるそうです。
WebSocketと謳ってるのに全く関係ない pjax の話をさせてもらいました^^;;;
本当は Play2.0 上で pjax を実現するうえで注意しなければいけない点や苦労ポイントだとかを発表できればと思ってたんですが、Play2.0 の柔軟さと Scala の強力さのお陰で、思いのほか苦労もせず実現できちゃったので、スライドは pjax に至るまでの流れが中心となってます。
ともあれ、話を聞いてくださった皆様、他の講師の方々、会場を提供してくださった株式会社パソナテック エンジニアカフェ運営事務局様、ありがとうございました。
implicit value の定義方法いろいろ
このエントリは Scala Advent Calendar jp 2011 の四日目です。
Scala には implicit parameter という機能があります。
どういう機能かと言うと、引数に implicit という修飾子をつけると、関数呼び出しの際にその引数を省略することが可能になるというものです。
省略された引数はどう解決されるかというと、現在のスコープから見える範囲にあるこれまた implicit という修飾子がついた値からコンパイラが適切なものを選択して解決してくれます。
コンパイラが適切な値を選択できなかった場合、コンパイルエラーになります。
簡単な例を見てみましょう。
scala> trait ClassName[-A] { def apply(value: A): String } defined trait ClassName scala> def getClassName[A](value: A)(implicit className: ClassName[A]): String = className(value) getClassName: [A](value: A)(implicit className: ClassName[A])String
メソッド getClassName は型パラメータAを取り、A型の value と ClassName[A]型の className を引数として受け付けます。そして戻り値に引数 className の apply を実行した結果を返します。
この二つ目の引数 className に implicit がついています。したがってスコープ内に適切な ClassName[A]型 のオブジェクトが implicit な値として存在していれば、getClassName は引数 value を与えるだけで実行することができるようになります。
ためしに実行してみましょう。
scala> getClassName("test") <console>:10: error: could not find implicit value for parameter className: ClassName[java.lang.String] getClassName("test") ^
エラーになりました。エラーメッセージを見てわかる通り、現在のスコープ内に ClassName[java.lang.String] 型の implicit な値が存在しないため、コンパイラが見つけられなかった訳ですね。
では ClassName[java.lang.String] 型の implicit な値を定義してみましょう。
scala> implicit val StringClassName = new ClassName[String] { | def apply(value: String) = "String(%s)".format(value) | } StringClassName: java.lang.Object with ClassName[String] = $anon$1@4d8040 scala> getClassName("test") res1: String = String(test)
めでたく実行することができました。
さてここからが本題です。このような implicit な値を定義することで implicit parameter の機能を使うことができるのですが、implicit な値の定義には implicit val だけではなく他の方法も使うことができます。その方法を紹介していきたいと思います。
まずは単純な object を使う方法。
scala> implicit object IntClassName extends ClassName[Int] { | def apply(value: Int) = "Int(%d)".format(value) | } defined module IntClassName scala> getClassName(10) res2: String = Int(10)
シングルトンオブジェクトは言ってみれば唯のインスタンスの参照なので値として扱えるのも不思議ではありません。
そして実はメソッドも implicit な値として扱うことができます。
scala> implicit def LongClassName = new ClassName[Long] { | def apply(value: Long) = "Long(%d)".format(value) | } LongClassName: java.lang.Object with ClassName[Long] scala> getClassName(3L) res3: String = Long(3)
implicit def は implicit conversion の為だけに使われるとは限らないのですね。
implicit val と implicit def で何が違うかというと、まず implicit def は、implicit parameter として補完される度、毎回評価されます。
scala> implicit def BooleanClaeeName = { | println("evaluateBooleanClassName") | new ClassName[Boolean] { def apply(value: Boolean) = "Boolean(%s)".format(value) } | } BooleanClaeeName: java.lang.Object with ClassName[Boolean] scala> getClassName(true) evaluateBooleanClassName res4: String = Boolean(true) scala> getClassName(false) evaluateBooleanClassName res5: String = Boolean(false)
そして特に大きな違いなのは、implicit def は型パラメータをとることができるという点です。
scala> implicit def OptionClassName[A] = new ClassName[Option[A]] { | def apply(value: Option[A]) = "Option(%s)".format(value) | } OptionClassName: [A]=> java.lang.Object with ClassName[Option[A]] scala> getClassName(Some(10)) res6: String = Option(Some(10)) scala> getClassName(Some("test")) res7: String = Option(Some(test))
さらに踏み込むと implicit def で定義した場合、そのメソッドにも implicit parameter を使うことができるのです。
scala> implicit def ListClassName[A](implicit c: ClassName[A]) = new ClassName[List[A]] { | def apply(value: List[A]) = value.map(c(_)).mkString("List(", ", ", ")") | } ListClassName: [A](implicit c: ClassName[A])java.lang.Object with ClassName[List[A]] scala> getClassName(List(1, 2, 3)) res8: String = List(Int(1), Int(2), Int(3)) scala> getClassName(List(List(1, 2), List(), List(3, 4, 5))) res9: String = List(List(Int(1), Int(2)), List(), List(Int(3), Int(4), Int(5)))
これを利用することで再帰的に implicit parameter を解決することが可能になります。
もちろん通常の再帰関数の様に、再帰の終了が存在しないようなケースではコンパイルエラーになるので注意が必要です。
scala> implicit def Product2ClassName[A, B](implicit ca: ClassName[A], cb: ClassName[B]) = | new ClassName[Product2[A, B]] { | def apply(v: Product2[A, B]) = "(%s, %s)".format(ca(v._1), cb(v._2)) | } Product2ClassName: [A, B](implicit ca: ClassName[A], implicit cb: ClassName[B])java.lang.Object with ClassName[Product2[A,B]] scala> case class Foo(_1: Int, _2: Foo) extends Product2[Int, Foo] defined class Foo scala> getClassName(Foo(10, Foo(20, null))) <console>:15: error: diverging implicit expansion for type ClassName[Foo] starting with method Product2ClassName in object $iw getClassName(Foo(10, Foo(20, null))) ^
以上の事を抑えておけば、自分のコードで implicit parameter を使いこなしたり、型クラスを多用するようなライブラリを読み解く助けになると思います。
ぜひ implicit parameter を活用してみてください。