implicit conversion と type class
第5回 スタートHaskell2という 5 なのか 2 なのかはっきりしろみたいな勉強会があったようです。そこで Haskell と Scala という LT を id:xuwei さんがされたようです。行ってないのでなんともかんともですが…
さて LT のスライドは前述の通り既に公開されているので早速読みました。特に気になった一点について書いてみたいと思います。
implicit は、型クラスのための構文です(キリッ
implicit conversion と implicit parameter という2種類がある
implicit conversion は型クラスには、ほとんど関係ないので忘れてください
ボクが気になったのは最後の行「implicit conversion は型クラスには関係ない」の部分です。
結論からいうと、一応関係はあります。
例えば型 A から型 B への implicit conversion は以下のように定義できます。
class A class B { def f = 1 } implicit def aToB(a: A): B = new B
通常 implicit conversion はその名が示すとおり暗黙に適用されるものです。
val af = (new A).f
これは implicit conversion のルールが適用された結果、以下のようなコードになります。
val af = aToB((new A)).f
特に難しいところはないですね。
implicit conversion の定義は、implicit parameter として扱うこともできます。
def af(a: A)(implicit convert: A => B) = convert(a).f
型パラメタを追加して、もう少し generic な定義にしましょう。
def inject[T, U](t: T)(implicit inj: Function1[T, U]) = inj(t)
これはもう Function1 という二つの型変数を取る type class のインスタンスを要求するメソッドにしか見えませんね!
ということで implicit conversion の定義は、ある型からある型への変換を提供する type class のインスタンスの定義として見なせるよ、という話でした。本当はこの後に、実はそういう type class はうまく使うと便利なんだよ、という話を書くつもりだったのですが、カラテが尽きたのでここで終わりです。そこが本題だったんですが…イメージせよ…?
追記:本当はもっと話が続くので inject みたいな定義を書いたんですが、ある rigid な型 A へ変換可能な型制約は以下のように書けます、というかまあ普通はこう書きますよね。
type CanConvertToDouble[T] = T => Double def tod[T : CanConvertToDouble](t: T)
蛇足なんですが Context Bounds って型パラメタを一つしか取らないものにしか使えないから不便なわけですが、Const みたいなものがあるとすると、
type Const[A] = { type Type[_] = A }
パラメタを取らないもの(T => Double は型変数を含んでいるとはいえ、型コンストラクタではなく型です)は以下のように書けてしまいます。
def tod[T : Const[T => Double]#Type](t: T):Double = implicitly[T => Double].apply(t)
複数パラメタを取るものも、一旦目的の型コンストラクタに適用してしまえばなんのその。
def toseq[T : Const [T => U]#Type, U](t: T, u: U): Seq[U] = Seq(implicitly[T => U].apply(t), u)
非常にどうでもいいですね。
Play 2.0.3 で BodyParser を含めたアクションのテストを行う
久しぶりの更新です。
Play 2.0(2.0.3) で、ルーターとアクションのテストを行いたい時は普通以下のように書きます。ドキュメントからの引用です。
"respond to the index Action" in { val Some(result) = routeAndCall(FakeRequest(GET, "/Bob")) status(result) must equalTo(OK) contentType(result) must beSome("text/html") charset(result) must beSome("utf-8") contentAsString(result) must contain("Hello Bob") }
通常はこれで問題ないのですが、自分で定義した BodyParser を利用しているアクションのテストを行いたい場合に問題が生じます。
Helpers.routeAndCall の実装は、2.0.3 では Action#apply を単に呼び出すだけなので、Action#bodyParser は使われないのです。困ります。
それ以外にも、GlobalSettings#onRouteRequest を無視してルーティングを行う問題もあります。困ります。
BodyParser の問題に関しては、このコミットで修正されています。
onRouteRequest の問題に関しては、issueにはあるのですが、2.1.0 ではそもそも routeAndCall が deprecated になるため、修正されることはないでしょう。
まあ何にせよ困る、ので前述のコミットや routeAndCall を deprecated にしたコミットを参考にしつつ、自分で 2.0.3 用のヘルパーメソッドを書いてみました。
import play.api.{Play, Application} import play.api.mvc.{Action, Result} import play.api.http.Writeable import play.api.libs.iteratee.Input import play.api.test.FakeRequest object Helpers { def route[T](app: Application, rh: FakeRequest[_], body: T)(implicit w: Writeable[T]): Option[Result] = { app.global.onRouteRequest(rh).map { case action: Action[_] => val rawBody = w.transform(body) val parsedBody = action.parser(rh).feed(Input.El(rawBody)).await.get.run.await.get.asInstanceOf[Either[Result, Any]] val onSuccess = { (a: Any) => action.asInstanceOf[Action[Any]](rh.copy(body = a)) } val onFailure = (r: Result) => r parsedBody.fold(onFailure, onSuccess) } } def route[T: Writeable](rh: FakeRequest[_], body: T): Option[Result] = route(Play.current, rh, body) }
この code snippet は NYSL ということにしますので、自分で BodyParser 書いちゃうような奇特な人は必要なら適当に使ってください。そういう奇特な人は自分で解決しちゃってそうですが…
以下余談。
Read moreoverload で邪魔な type erasure をなんとかする
型レの話書くの忘れてた…まあいいか。
値を何かしらでラップするみたいなことは、まれによくあります。
trait Nanka { type Rep[T] } trait Add extends Nanka { def add(lhs: Rep[Int], rhs: Rep[Int]) def add(lhs: Rep[Int], rhs: Rep[Double]) ... }
でまあこういうのはコンパイルエラーになる。add メソッドはどれも type erasure された結果、同じシグネチャを持つメソッドになってしまうからですね。全部 Java が悪い。まあ type erasure のおかげで higher order type param とか実現できたりもするしそれはそれで…いやそれはおいといて。
前こういうのに遭遇したときは、仕方がないので implicit parameter で type class 的に解決したのですが、それだと外部からの侵入に弱いとか、なんか書きづらい読みづらいとかでダサい。
そこで implicit parameter をもうちょっと違った使い方をする。
trait Add extends Nanka { class Dummy0 class Dummy1 implicit val dummy0 = new Dummy0 implicit val dummy1 = new Dummy1 def add(lhs: Rep[Int], rhs: Rep[Int])(implicit dummy: Dummy0) def add(lhs: Rep[Int], rhs: Rep[Double])(implicit dummy: Dummy1) ... }
implicit parameter を追加すれば、シグネチャは別になるし、使う側は何もわからないし、書く側も少し煩雑だけれど、まあ type class ぽく書くよりはましだし、外部から侵入されたりもしない…
めでたしめでたし。
Lightweight Modular Staging のスライドで見たテクニックです。implicit object と path dependent type 使えないかなーとか思ったんですがダメでした。
type level programming in scala introduction
もう旬を逃した感が少しありますが JavaOne Tokyo の JVM 言語 BOF で、よしださんが Scala 型レベルプログラミングという LT をされました。ボクはその場では聞けなかったのですが、後日 Scala 勉強会第 76 回で聞く機会がありました。JavaOne では 5 分間の LT だったらしいのですが、なんとも…
というわけで、触発されてボクも似たような話をさらりと書いてみたいと思います。
Read moresum type in Java
ってどうするのが Java らしいのかなあみたいな。
public class Base { public static class A { ... } public static class B { ... } private A a; private B b; private Base(A a, B b) { this.a = a; this.b = b; } public static Base createA(...) { return new Base(new A(...), null); } public static Base createB(...) { return new Base(null, new B(...)); }
ここで普通にゲッターとか書くと、
public Base.A getA() { return a; } public Base.B getB() { return b; }
なんか (a != null && b == null) || (a == null && b != null) って見えにくいなあっていう…
それで仕方なく、
public interface Folder<T> { T a(A a); T b(B b); } public <T> fold(Folder<T> folder) { return a != null ? folder.a(a) : folder.b(b); } }
とかしてみるわけですけど、
Base base = Base.createA(...); Integer n = base.fold(new Base.Folder<Integer> { public Integer a(Base.A a) { return 1; } public Integer b(Base.B b) { return 2; } });
ウーンダサイ…
どうするのが Java way なのかなあ、と考えています。自分ひとりで書くなら、最初ので書くのも読むのもいいわけですが、複数人となるとそうはいかないよなあ…という。