自動適用:Scala 3で廃止予定の機能(14)

Scala 3 (Dotty 0.26.0-RC1)
最終更新:2020年9月20日

[AD] scalapediaでは記事作成ボランティアを募集しています

この記事では、Scala 3で廃止予定の「自動適用(Auto Application)」について解説します。

空引数メソッドの呼び出しには必ず()をつける

Scala 3では、「空引数メソッド(nullary method)」を呼び出す際には、明示的に空の引数リスト()を付ける必要があります。

「空引数メソッド」とは、引数リストに一つも値をとらないメソッドのことです。

例えば、以下のメソッド

def go(): T = ???

を呼び出すには、

go()

と記述する必要があります。

それはそう、当たり前ではありますね。

オーバーライドする場合も()をつける

空引数メソッドをオーバーライドする際も同様です。

無引数メソッドを空引数メソッドとしてオーバーライドしたり、空引数メソッドを無引数メソッドでオーバーライドすることはできません。

空引数メソッドだけでなく「無引数メソッド」なるものが出てきましたね。いったい何なのでしょうか。

「無引数メソッド」とは、そそもそ引数リストをもたないメソッドです。

たとえば、以下の例を見てみましょう。

class X { def go(): T } class Y extends X { def go: T }

Xでは、空引数メソッドgo()が定義されています。

そしてYでは、これを無引数メソッドgoとしてオーバーライドしようとしています。

このコードはコンパイルエラーになります。

正しくはYにおいても、Xに合わせて空引数メソッドgo()として定義する必要があります。

class X { def go(): T } class Y extends X { def go(): T }

こちらはコンパイルが通ります。

同様に、Xにて無引数メソッドとして定義している場合は、Yでも無引数メソッドとして定義する必要があります。

class X { def go: T } class Y extends X { def go: T }

Scala 2では()が補完されていた

Scala 2では、空引数メソッドを引数リスト()なしで呼び出す際には、空の引数リスト()が自動的に付加されていました。

例えば、go()メソッドを以下のように記述すると、()が補完されます。

go

これによって、まるで無引数メソッドgoであるかのように記述しても空引数メソッドgo()と記述したことになっていました。

この機能を「自動適用」と読んでいました。

移行は3.0にアップデートしてからでOK

Scala 2.13にて既に非推奨となっていますが、3.0でも使用することができます。

3.0において引数リストの表記が揃っていない場合には、Scala 3においてはコンパイルが通らなくなります。 しかし移行オプション3.0-migrationを設定すると、以前と同様に記述することができます。

さらに、移行オプション3.0-migrationを設定している際にコンパイラオプション-rewriteを設定すると、自動でwhile文に書き換えてもらえます。

移行オプションについてはこちらの記事をご覧ください。

便利ですので、3.0へのアップデート後に行うことをおすすめします。

また、Scalafixを使用して書き換えることもできます。

なぜ「空引数」と「無引数」を区別するのか?

なぜScalaではわざわざ引数リストのない「無引数メソッド」と、空の引数リストつきの「空引数メソッド」を区別するのでしょうか?

それは、Scalaは「一様アクセス原則(uniform access principle)」に準拠しているからです。

この原則は、
「オブジェクトのメンバは、フィールドから副作用なしのメソッドへ、あるいは逆に副作用なしのメソッドからフィールドへと変更できる。
しかも、そのメンバにアクセスしているクライアントには影響を与えることなく変更できる」
というものです。

つまり使用側が「アクセスしているのはフィールドなのか副作用なしのメソッドなのか」を気にせず使えるべきだということです。

したがって、副作用をもたないメソッドをあたかもフィールドであるかのように表現できるよう、()をつけずにメソッドを定義できるようになっているというわけです。

また、これを満たすため、Scalaのコーディング規約では引数をとらないメソッドを定義する場合において、副作用がある場合は()を付け、副作用がない場合は()を付けないようにすることとして区別しています。

とはいえScala 2系では定義と呼び出しの対応関係が弱かったので、これまでは()の定義が一貫しない場合が多数ありました。

Scala 3では「自動適用」の廃止により、さらに厳密に一様アクセス原則を遵守することができるようになりました。

Javaとの互換性のため、Javaのメソッドはこの限りではない

Javaのメソッドや、Javaで定義されているメソッドをオーバーライドするScalaのメソッドは、この規則から除外されます。

なぜなら、Javaには一様アクセス原則がないので、たとえ副作用がなくてもメソッドには必ず()がついているからです。

もしJavaの副作用なしのメソッドをScalaにおいて使ったときに()がついていると「もしかして副作用があるのか?」などと混乱を生じてしまいます。

そのため、これを防ぐために()を外して書くことができるようにしてあるというわけです。

たとえば、java.lang.Stringlength()メソッドは、Scalaにおいてはlengthとして呼び出すことができます。

Scala 2で定義されているメソッドを使う場合はこの限りではない

後方互換性の維持を目的とした移行措置として、Scala 3においては今のところ、「Scala 2で定義されている空引数メソッドを使用する」あるいは「オーバーライドする」際の無引数メソッドに対しては、自動的に()を付加しています。

今後関連するライブラリの対応が進んだ暁には、この措置が外れる可能性はあります。

参考

外部リンク:Dropped: Auto-Application

サイト内検索