LoginSignup
1
1

More than 3 years have passed since last update.

【Scala】「import 変数名._」の使いどころ

Last updated at Posted at 2020-11-07

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を要求します。

Future.scala
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までお願いします。


  1. executionContextフィールド自身がimplicitで定義されている場合に限ります。 

  2. 例えばLightbendakka-samplesでも、ExecutionContextに関しては変数importを使っています。 

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1