Scala 勉強会 in 渋谷第32回 Scala Hack-a-thon に参加してきました
今更ながら参加レポートみたいな何か。
Scala 勉強会 in 渋谷第32回 Scala Hack-a-thon に参加してきました。
なにやった
何をやるか全く考えておらず、型レベルプログラミングとか面白そうだよねーなんて言っていたのですが、なんか生産性低そうなので諦めて Scala と Jython で遊んでいました。
Scala の Collection をそのまま Jython に持っていってもイマイチ使い勝手が悪い! ということで Collection から PyList へのコンバータを作ろうとこんなコードを書いていたのですが、
import java.util import org.python.core import scala.collection val py = new org.python.util.PythonInterpreter val props = new java.util.Properties org.python.util.PythonInterpreter.initialize(System.getProperties, props, Array()) def toPyObject(obj:Int): core.PyObject = new core.PyInteger(obj) def toPyObject(obj:String): core.PyObject = new core.PyString(obj) def toPyObject(obj:Float): core.PyObject = new core.PyFloat(obj) def toPyObject[T](obj:Iterable[T]): core.PyObject = { new core.PyList( collection.JavaConversions.asJavaCollection( obj.map{x => toPyObject(x)})) } py.exec("import sys; print sys.path") py.set("aaaa", toPyObject(List(1,2,3))) py.execfile("test.py") println(py)
こんなエラーが出て動かない!
jython.scala:24: error: overloaded method value toPyObject with alternatives: [T(in method toPyObject)](obj: Iterable[T(in method toPyObject)])org.python.core.PyObject <and> (obj: Float)org.python.core.PyObject <and> (obj: String)org.python.core.PyObject <and> (obj: Int)org.python.core.PyObject cannot be applied to ((some other)T(in method toPyObject)) obj.map{x => toPyObject(x)})) ^ one error found
どうやらオーバロードした関数の解決に失敗している様子。
ウンウン唸っていると近くにいたみずしまさんに「それ implicit parameter を使えばできるよ!」と implicit parameter の使い方を解説してもらいました。
問題としては
- toPyObject[T](obj:Iterable[T]): core.PyObject の関数に入った時点で T に関する型情報が消えてしまう (型消去/Type Erasure)
- そのために T の型を元に次の呼び出し先を決定する toPyObject(x) が解決できなくなってしまう
ということらしい。
単純置換な C++ のテンプレート脳にはなにやら難しいお話ですよ。
implicit parameter ってなに?
こんな場合は Implicit parameter を上手く使うといいらしい、のですが、そもそも Implicit parameter ってなによというところから。
implicit parameter とは、関数/メソッドを呼び出す際に適合する型の値を呼び出し元のスコープから探し、暗黙的に渡されるパラメータです。
以下のコードを見れば大体わかると思います。
implicit val impval:Int = 10 def test(x:Int)(implicit v:Int): Int = { x*v; } // v には impval すなわち 10 が渡される println(test(10)) // => 100 def test2() { implicit val impval2: Int = 30 // v には impval2 すなわち 30 が渡される println(test(20)) // => 500 } test2()
で、これをどう使うかというと、このように使います。
import java.util import org.python.core import scala.collection val py = new org.python.util.PythonInterpreter val props = new java.util.Properties org.python.util.PythonInterpreter.initialize(System.getProperties, props, Array()) implicit def toPyObject(obj:Int): core.PyObject = new core.PyInteger(obj) implicit def toPyObject(obj:String): core.PyObject = new core.PyString(obj) implicit def toPyObject(obj:Float): core.PyObject = new core.PyFloat(obj) /* implicit parameter の conv には T によって自動的に T=>core.PyObject に該当する値が渡される。 渡されるオブジェクトは *呼び出し元の* スコープから選ばれる。 */ implicit def toPyObject[T](obj:Iterable[T])(implicit conv: T=>core.PyObject): core.PyObject = { new core.PyList( collection.JavaConversions.asJavaCollection( obj.map{x => conv(x)})) } py.exec("import sys; print sys.path") py.set("aaaa", toPyObject(List(1,2,3))) py.execfile("test.py") println(py)
このように toPyObject[T](Iterable[T]) => PyObject な関数に implicit parameter として T=>PyObject な引数を一つ追加してやることで、呼び出し時にどの関数を使用して T=>PyObject の変換を行うかという情報を渡しています。
ただ、この方法だと List(List(1,2,3)) といったような入れ子の Collection には対応しきれません。
なんか良い方法はあるのでしょうか。