暗黙の変換

1.暗黙の型変換

これもScalaを使い始めの頃は、まったく意味がわからない言語仕様です。型変換(キャスト)とかのレベルではありません。 勝手に別のクラスに変換してくれて、そのクラスの関数を呼び出し可能にしてくれます。 まずは簡単な例(String からInt)へ変換する例です。

val numJP1 = "一二三四"
val numEn:Int = numJP1
// Int 型で宣言しているので、文字列から数値型にはもちろん変換できません

簡単な例ですが。

// 暗黙の型変換
implicit def jpStringToInt(x: String) = {
  x.replace("一","1").replace("二","2").replace("三","3")
  .replace("四","4").replace("五","5").replace("六","6")
  .replace("七","7").replace("八","8").replace("九","9")
  .replace("零","0").toInt
}

// 同一スコープ内に宣言しておくと
val numJP1 = "一二三四"
val numEn:Int = numJP1
println(numEn)
// Int型 かつ "1234"

文字列型とInt型の簡単な暗黙の型変換の例ですが、本来は完全に違う型通しでも変換して、各種関数呼び出しができます。

2. 暗黙のパラメータ

暗黙の型変換は、既存の型を暗黙的に変換して関数を呼び出す事ができる言語仕様でしたが、暗黙のパラメータは、関数の引数を暗黙的に指定するための言語仕様です。同一スコープ内で、『暗黙の値』を宣言しておき、ある関数の引数を『暗黙の引数』を宣言しておくと自動でその値が引数に適用されます。

// とりあえず共通で呼ぶ処理を定義
class MyLogger {
  def beforeComment(msg:String) = println("これは、" + msg + "の前に実行されます。準備運動は大切です。")
  def afterComment(msg:String) = println("ようやく" + msg + "が終わりました。")
}

// 共通処理を『暗黙のパラメータ』で宣言
def running(kyori:String)(implicit myLog:MyLogger) = {
  myLog.beforeComment(kyori)
  println("今日は、" + kyori + " 走ります。")
  println(".....")
  myLog.afterComment(kyori)
}

Javaとかだといちいちパラメータに指定する(とか継承とか)とかをしないといけなかったのです。 が、Scalaでは暗黙のパラメータで宣言することで、呼び出し側で共通の処理を暗黙的に渡す事ができます。

// 暗黙の値宣言
implicit val myLog = new MyLogger
// myLogは渡してない
running("100Kmマラソン")
// これは、100Kmマラソンの前に実行されます。準備運動は大切です。
// 今日は、100Kmマラソン 走ります。
// .....
// ようやく100Kmマラソンが終わりました。

このようにスコープが同じであれば、暗黙的にパラメータを渡す事が可能です。

3.暗黙クラス

暗黙クラスは、既存のクラスを拡張可能にする言語仕様です。implicit class 宣言したクラスのコンストラクタの引数型に対して、機能を追加できます。

// implicit classを宣言する時は、必ず外側にobject/class/traitが必要。
object JPNumber {
  // string型に toEnNumbersの関数を追加するようなイメージ
  implicit class StringJpNumbers(x: String){
   def toEnNumbers = {
     x.replace("一","1").replace("二","2").replace("三","3")
     .replace("四","4").replace("五","5").replace("六","6")
     .replace("七","7").replace("八","8").replace("九","9")
     .replace("零","0").toInt
   }
  }
}

StringJpNumbersのように、Implicit class 宣言をしておき、以下のように利用します。

// implicit class をimport
import chapter21ImplicitConv.JPNumber._
// string から 関数 toEnNumbersを呼び出せる
"一二三四".toEnNumbers
// 1234

このように暗黙クラスは、既存のクラス(文字列とかだけだなく!)に自分で定義した関数を追加できます。 ここでは、暗黙の***を3つ紹介しましたが、利用する側からコードを追いにくくなる場合があります。 特にimplicit parameter は、知らないところで宣言された値が、いつのまにか特定の関数の引数に渡されていたりします。 なるべく自明な事で、何度も同じコードを書く事になる場合には適用した方が良いのではと思いました。