Scala 勉強会 in 渋谷第32回 Scala Hack-a-thon に参加してきました

今更ながら参加レポートみたいな何か。

Scala 勉強会 in 渋谷第32回 Scala Hack-a-thon に参加してきました。

なにやった

何をやるか全く考えておらず、型レベルプログラミングとか面白そうだよねーなんて言っていたのですが、なんか生産性低そうなので諦めて ScalaJython で遊んでいました。

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 には対応しきれません。
なんか良い方法はあるのでしょうか。