ElmのDebug.logをScalaの拡張メソッドで再現する
ElmからScalaに入門したときにprintlnでのデバッグが難儀だと感じたため、ElmのDebug.logを再現する方法を探していたところimplicitsのうちのpimp my libraryパターン(enrich my libraryや拡張メソッドとも呼ばれている)を使うことで再現ができたため解説を試みる。
ElmのDebug.logの再現
ちなみに、implicitsはscalaのもつ3つの機能の総称であり、曖昧な表現であるため使わない方が良いとされているようだ。なので、日本語訳として存在していて呼びやすい拡張メソッドという呼び方をここからは採用する。
「implicits」は異なる3つの機能を一つにまとめた呼び方に過ぎず混乱を招くので、使うのをやめよう(個々の機能名で呼ぼう)。
引用:Scalaでimplicits呼ぶなキャンペーン | kmizuの日記
拡張メソッドとは、要するに既存のクラスにメソッドを追加する(ように見せかける)実装のこと。実際に動かすことのできるコードを以下に記載する。
object MyApp extends App { implicit class Tap[T](self: T) { def tap[U](block: => T => U): T = { block(self) //値 は 捨 て る self } } 1.tap { s => println(s.getClass) // int } .toString .tap { s => println(s.getClass) // string } }
解説
implicit class Tap[T](self: T) {
implicit class
は拡張メソッド専用の書き方で、implicit def
でも書くことができるが、既存の型への変換など可読性を落とすことになるのでimplicit class
で記述する。クラス名のTapの部分は特に使うことはないので任意の名前でいい。
def tap[U](block: => T => U): T = {
block
のアノテーションが=>
から始まっている通り、名前渡し(call-by-name)にすることで引数に渡される式の評価をメソッドの内部に遅延することができる。
{
block(self) //値 は 捨 て る
self
}
ブロック式形式で記述した複式は、最後の値が返り値となるためprintlnなど副作用を表すUnit型の値は捨て、ジェネリクスによって呼び出し元のクラスを返り値に設定する。
1.tap { s => println(s.getClass) // int } .toString .tap { s => println(s.getClass) // string }
これによって、ElmでDebug.logを用いてパイプでの値の変換や関数合成の任意のタイミングで値をコンソールに表示するように、Scalaでもメソッドチェーンの任意のタイミングで標準出力にアウトプットすることができるようになった。
ただ、注意点としてElmのDebug.logと違って、Scalaではimplicit conversionの関係か、tapメソッドを経過することでときおり返り値が変わってしまうことがある。どういったメカニズムかはまだはっきりとわかっていないので、知っている方がいらっしゃれば是非教えていただきたい所存である。
以上。