Scalaのimportは任意の場所に記述できる
Scalaのimport
句は概ねJavaのそれと同じような使い方をしますが、いくつか異なる点も存在します。
その内の一つとして、プログラム内の任意の場所(クラス内や関数内など)に記述できることが挙げられます。
def absPlus(x: Int, y: Int): Int = {
import scala.math.abs // ファイルの先頭じゃなくてもいい
abs(x) + abs(y)
}
参考:
パッケージとインポート
Scalaのインポートについてメモ
Scalaのimportは「import 変数名._」という書き方もできる
そして実に面白いことに、Scalaでは「スコープ内の変数」に対してもimportすることが可能です。
// これを
def lengthTimes2(str: String): Int = {
str.length * 2
}
// こう書くこともできる!
def lengthTimes2(str: String): Int = {
import str.length // 「引数のstrの」lengthをimport
length * 2 // ここのlengthはstr.lengthと同じもの
}
上記の例では見ての通り、引数に使われている変数str
に対するimportが行われています。
またファイル先頭で行うimportと同じく、ワイルドカードなどの各種記述も使用可能です。
// ワイルドカードも可
def lengthTimes2(str: String): Int = {
import str._
length * 2
}
// 別名インポートもできる
def lengthTimes2(str: String): Int = {
import str.{length => strLength}
strLength * 2
}
「import 変数名._」が使われるケース
上記のような書き方はあくまで「できる」だけであって、実際に上記のような使われ方をすることはまずありません。
ですが、使い道がないというわけでもありません。
例えば、値をimplicit parameter
(暗黙のパラメータ)として渡したいときに活用されていたりします。
どういう風に使われているのか見てみましょう。
暗黙のパラメータの渡し方
実際の使われ方を見る前に、まず以下のようなクラスを考えてみます。
// 暗黙のパラメータとしてExecutionContextを受け取るクラス
class Foo(implicit val executionContext: ExecutionContext) {
val fooFuture = Future {
... // 何らかの処理
} // 実際はここで暗黙のパラメータとしてexecutionContextを渡している
}
上記では暗黙のパラメータの例としてscala.concurrent.Future
を使用しています。
ここではFuture
の詳しい説明はしませんが、上記のFuture {}
の部分(※実際にはFuture.apply
メソッド)は通常のパラメータ(ブロック式の部分)とは別に、暗黙のパラメータとしてExecutionContext
を要求します。
def apply[T](body: => T)(implicit executor: ExecutionContext): Future[T]
このケースではコンストラクタの時点で既にExecutionContext
型の変数を受け取っているので、それをimplicit
にするだけでそのままFuture.apply
メソッドに(暗黙的に)渡すことができます。
では、以下のようなクラスの場合はどうでしょうか?
class Bar(foo: Foo) {
val barFuture = Future {
... // 何らかの処理
} // 暗黙のパラメータとして渡すExecutionContextが見つからないためコンパイルエラーとなる
}
Bar
クラスはFuture
の実行コンテクストとして、コンストラクタで受け取ったfoo
内のexecutionContext
フィールドを使いたいとします。
ですがこの時点ではexecutionContext
フィールドはスコープ内に存在しないため、Future.apply
に渡すことはできません。
そのため、上記のコードはコンパイルエラーとなります。
このようなケースの場合、大きく分けて3つの解決策があります。
1. 暗黙のパラメータを明示的に渡す
何も、暗黙のパラメータに渡すパラメータは必ず暗黙でなければならないというわけではありません。
下記のように、明示的に渡すことも可能です。
class Bar(foo: Foo) {
val barFuture = Future {
... // 何らかの処理
}(foo.executionContext) // ExecutionContextを明示的に渡す
}
可能ではあるのですが、これだと必要とされる度に常に明示的に記述する必要があります。
このようなボイラープレートをなくせるのが暗黙のパラメータのメリットの1つでもあるので、これだとちょっと本末転倒感がありますね。
なのでこの方法はまず採られず、このあと紹介する2.または3.の手法が多く使われています。
2. スコープ内でimplicitキーワードをつけた変数に代入する
これは、実際にソースを見たほうが早いと思います。
class Bar(foo: Foo) {
// Barクラス内でExecutionContext型の変数を改めて定義する(スコープに入る)
implicit val executionContext: ExecutionContext = foo.executionContext
val barFuture = Future {
... // 何らかの処理
} // 上で定義したExecutionContextを暗黙的に渡す
}
見ての通り、Bar
クラス内で改めてExecutionContext
型の変数を宣言しています。
これでexecutionContext
がクラス内のスコープに入ったため、クラス内のどこでもこの実行コンテクストを暗黙のパラメータとして渡せるようになります。
強いて問題点を挙げるとすると、Foo
クラス内の他の暗黙の値も必要とする場合、必要分だけ変数を再定義する手間が生じます。
3. 対象の暗黙の値を持つ変数をimportする
最後の方法として、ようやく本題ですが、対象の暗黙の値を持つ変数をimportするという手法が挙げられます。
この手法を採用する場合のソースは下記のようになります。
class Bar(foo: Foo) {
// 対象の暗黙の値を変数からimportする(executionContextがスコープに入る)
import foo.executionContext
val barFuture = Future {
... // 何らかの処理
} // foo.executionContextがスコープに入ったので、それを暗黙的に渡している
}
このようにimportを使うと、変数foo
内のexecutionContext
フィールドがスコープに入るため、それをそのまま暗黙のパラメータとして使えるようになります。1
今回のようにimportだけで済む場合は、この手法を採用しているケースが多いようです。2
おわりに
そもそも変数をimportするという事自体がJavaなどのから入った方にとっては衝撃かもしれません。
基本的には暗黙のパラメータ、その中でもいわゆる「Implicit Contexts」と呼ばれる機能で使われることがある、くらいに考えておけばいいと思います。
自分で書くことがあるかどうかは別として、既存のコードにそういうものがあったときに読めるようになるためにも、この記事がお役に立てれば幸いです。
最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。
-
executionContext
フィールド自身がimplicit
で定義されている場合に限ります。 ↩ -
例えばLightbendのakka-samplesでも、
ExecutionContext
に関しては変数importを使っています。 ↩