読者です 読者をやめる 読者になる 読者になる

Scalaのnull/Nothing/Nil/Noneはやりすぎなのか?

Twitterしてたら目に入ったので軽く。

f:id:bleis-tift:20150414225326p:plain
Javaにおけるnull。これまでとこれから

この後のスライドで、

Scalaにおける「何もないもの」の分類はやり過ぎ感はある

と言われているんですが、ある程度は誤解に基づく意見だよなぁこれは、ということを言っておこうかなと。

Scalaについて

日本では説明が不要なくらいScalaって有名になってると思うんですが一応。 ScalaJVMの上で動作する、(クラス指向の)オブジェクト指向プログラミングと関数型プログラミングを融合させた言語です。 そして、Scalaのコア機能はどちらかというとオブジェクト指向プログラミング寄りです。 オブジェクト指向プログラミングをベースに、関数型の色々なものを実現している感じです*1

オブジェクト指向プログラミング的な機能として真っ先に思いつくのは何でしょうか? 割と上位の方に、「継承」とか「型階層」とか来るんじゃないでしょうか? Scalaは、継承とか型階層といったものと、関数型的なものを良い感じに融合させています。

そして、ScalaJavaとの親和性をそれなりに考えて作られています。 Scalaの機能が豊富なので、どうしても親和性を犠牲にしなければならないけなかった部分もありますが、 ある程度はJavaの諸々に融和させることに成功しているように思います。

Scalaにおける「何もない」を表すものたち?

Scalaで汎用的に「何もない」を表すために使えるものはいくつかあります。 これが混乱の元になっている例をいくつか見たことがありますが、 その多くはScalaの「何もない」を表すものを本来よりも多く考えてしまうことが原因になっているように思えます。

上の資料もその一つで、以下のものを「何もない」を表すものとして挙げています。

  • null
  • Nothing
  • Nil
  • None

これらを「何もない」を表すものとして一緒くたにするのは間違っていると言い切ることはできませんが、 やめた方がいいでしょう。

Scalaにおける「何もない」を表すものたち

nullNone

「何もない」を表すものとして考えるべきは、通常この2つだけです。

  • null
  • None

nullは「Javaとの親和性」の要求から来ており、Noneは関数型から来ています。 使い分けの指針は簡単、「基本的にはNone(というかOption)を使い、Javaとの境界ではnullも考慮する」です。

ScalaではIntなどの数値もオブジェクトとして扱えますが、 これらはAnyValという型を継承しています。 そして、JavaでのObjectに相当する型として、AnyRefがあり、 このAnyValAnyRefの共通の親としてAny型があります。

nullAnyRefという型を継承している型の変数には代入できますが、AnyValを継承したInt型の変数には代入できません。 しかし、Option型はAnyValを継承していようがAnyRefを継承していようが使えるため、 無理をしてnullを使う理由はScalaではありません*2

その他勘違いされやすいけど違うものたち

()

「何もない」ではなく「1つしかない」を表すUnit型というのもあります。 Booleanでさえ2つあるのに、1つしかなくていったい何に使うんだ・・・と思いますか? であれば、nullだって実は1つしかないですよね。

null()も1つか値がありませんが、nullAnyRefを継承したどんな型の値としても使えるのに対して、 ()Unit型の値としてしか使えません。

・・・ますます何のためにあるのか分からなくなるかもしれませんが、簡単です。 これは、JavaC++C#で言うところのvoidと同じような使い方をします。 JavaC++C#void型は値を持っていませんが、なぜ持っていないのでしょうか?

たぶん歴史的な経緯があるんでしょうけど、多相性*3を入れるなら、 voidも他の型と同じように使えた方が嬉しいのです。 にもかかわらず、これらの言語はvoidを特別扱いしています。 voidが他の型と同じように値を持ち、returnで返すものがあったなら共通化できたにも関わらず、 そうなっていないため残念なことになっている例はたくさんあります*4

Scalaではそうなっておらず、「値に意味がないこと」をUnit型の()という唯一の値で表すことで、 特別扱いを不要にしているのです。 0bitの情報と考えていいでしょう。情報量ゼロ。

Nothing

さて次はNothingです。 これは id:kmizu さんのブログに詳しいです。

scala.Nothingは何のためにあるのか

簡単にまとめると、

  • 例外を投げる式の型
  • 空のコレクションの要素型

として使います。 最初のうちはそうそう明示することがない型ですし、 一般的なコーディングでの「何もない」を表す型ではありません。

「何もない」をコード上で表すためにnullNoneと言った値を使ったのとは違い、Nothingは値すらないのです。 これをnullNoneとひとくくりに表すのは混乱の元となるだけですから、やめましょう。 Nothingは型を合わせるためだけに使う、と思っておけばいいはずです。

Nil

最後にNilです。 が、他のものがある程度汎用的で広い範囲を相手にしていたのに対して、これはとても狭い範囲のものを相手にします。 これは単に「空のリスト」を表すオブジェクトです。 歴史的経緯によって(主にRubyなどを知っている層からすると)紛らわしい名前になっていますが、 そこは間違えて使ってもScalaは静的型付き言語なので、コンパイラさんが教えてくれます。

空のリストを「値がないこと一般」を表すように使うこともできますが、 そんな設計はScalaをはじめ、多くの言語ではしないでしょう。 わざわざひとまとめにする必要はありません。

余談:object

あと、スライドではNil型、None型としていましたが、 Scala言語の文脈ではこれらは型ではなく(シングルトン)オブジェクトです。 「型」とは言わないほうがいい気がします。

追記:

すっかり忘れていましたが、型を取ることはできますね。

まとめ

Scalaでコード上で「何もないもの」を表したい場合は基本的にはNoneを使う。 他の言語を知っていると紛らわしく思える名前が出てきても、それらは別物なので気にしない。 コンパイラを頼れば問題ない。

こんなところで。

*1:F#もオブジェクト指向プログラミングと関数型プログラミングできますが、F#は関数型に軸を置き、両者を融合させるのではなくある程度独立したものとして扱っています

*2:とここでは断言していますが、もちろんパフォーマンスが重大な問題になるような場合には使うこともあるでしょう。が、そういうのはあくまで例外です。基本はOptionを使います

*3:ジェネリックやテンプレート

*4:C#で言うならSystem.FuncとSystem.Actionの2系統のデリゲート型など