kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

Scalaアンチパターン:変更可能コレクションをvarとして宣言する

Scalaは最初から関数型プログラミングのスタイルで書くことを意識して設計されたという意味で関数型プログラミング言語と言えますが、一方で、「Better Java」な手続き型スタイルで書くことも基本的には否定されるべきではないと思います。たとえば、ビッグデータ関係のソフトウェアとして有名なApache SparkはScalaで書かれていますが、(おそらく)パフォーマンス上の理由のため、手続き型のスタイルで書かれている部分がかなり多いです。

さて、それはいいのですが、そういう「Better Java」なScalaコードによく見られるパターンに、変更可能コレクションを使っているのに、そのコレクションを格納する変数をvarとして宣言しているものが(割と有名なソフトウェアでさえ)あります。ですが、そういうコードは 書いてはいけません(かなり例外的な場合を除いて)。

「書いてはいけません」というのはかなり強い言い方ですが、きちんと考えると割と当然の話です。

各要素が"name:age"で区切られたコマンドライン引数列を元に、Personクラスのオブジェクトの列を作り出す手続き型コードを考えてみます。たとえばこんな感じになるでしょう(書きたい処理の例がかなり無理矢理ですがご容赦ください):

import scala.collection.mutable.ArrayBuffer

case class Person(name: String, age: String)
var persons = ArrayBuffer[Person]() //valにしても意味は変わらない!
for(arg <- args) {
  val Array(name, age) = arg.split(":")
  persons.append(Person(name, age))
}

/*
 * personsを使って何かする(が、personsには再代入されない)
 */

このコードにおいて、ArrayBuffer[Person]型のpersonsを変更可能な変数として宣言していますが、これはvalにしても同じです。なぜなら、personsの「指す先」であるArrayBufferそのものが変更可能だからです。

もちろん、personsに変更可能コレクションを再代入したいなら、変数自体を変更可能にする必要がありますが、かなり考えづらい事態です。コレクションを空にしたいという要求がある場合でもclearメソッドを呼び出せば済む話です。

Javaでは、変数をfinalにするのは「追加で」finalを付加する必要があり、面倒くさいのであえてfinalをつけないということは割とよくありますが、Scalaは、変数の宣言時に、valとvarの「どっちかを選択させる」タイプの言語です。つまり、varで変数を宣言した場合、その変数 そのもののの値を変更する 意図があるとみなして良いのです。これは、コードを読むときの労力を削減する役割を果たします。

しかし、変更するつもりもない変数をvarにするという無意味なことをすることがまかり通ると、せっかくの良い言語設計も台無しになってしまいます。

というわけで、そういうことをしてはいけません。

あるいは、そういうコードを書いている人は、変数自体の変更可能性変数の参照先の変更可能性 の区別がついていないのかもしれません。Javaでも「初学者」がよくはまるポイントです。ですが、その区別がついていない人はまともなJavaプログラマですらないような気がします。