The N of Mod

そして、エデンの東へ

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メソッドを経過することでときおり返り値が変わってしまうことがある。どういったメカニズムかはまだはっきりとわかっていないので、知っている方がいらっしゃれば是非教えていただきたい所存である。

以上。

参考

pimp my library | ScalaText

16.1.4 練習問題 | ScalaText