Scalaのnull/Nothing/Nil/Noneはやりすぎなのか?
Twitterしてたら目に入ったので軽く。
この後のスライドで、
Scalaにおける「何もないもの」の分類はやり過ぎ感はある
と言われているんですが、ある程度は誤解に基づく意見だよなぁこれは、ということを言っておこうかなと。
Scalaについて
日本では説明が不要なくらいScalaって有名になってると思うんですが一応。 ScalaはJVMの上で動作する、(クラス指向の)オブジェクト指向プログラミングと関数型プログラミングを融合させた言語です。 そして、Scalaのコア機能はどちらかというとオブジェクト指向プログラミング寄りです。 オブジェクト指向プログラミングをベースに、関数型の色々なものを実現している感じです*1。
オブジェクト指向プログラミング的な機能として真っ先に思いつくのは何でしょうか? 割と上位の方に、「継承」とか「型階層」とか来るんじゃないでしょうか? Scalaは、継承とか型階層といったものと、関数型的なものを良い感じに融合させています。
そして、ScalaはJavaとの親和性をそれなりに考えて作られています。 Scalaの機能が豊富なので、どうしても親和性を犠牲にしなければならないけなかった部分もありますが、 ある程度はJavaの諸々に融和させることに成功しているように思います。
Scalaにおける「何もない」を表すものたち?
Scalaで汎用的に「何もない」を表すために使えるものはいくつかあります。 これが混乱の元になっている例をいくつか見たことがありますが、 その多くはScalaの「何もない」を表すものを本来よりも多く考えてしまうことが原因になっているように思えます。
上の資料もその一つで、以下のものを「何もない」を表すものとして挙げています。
null
Nothing
Nil
None
これらを「何もない」を表すものとして一緒くたにするのは間違っていると言い切ることはできませんが、 やめた方がいいでしょう。
Scalaにおける「何もない」を表すものたち
null
とNone
「何もない」を表すものとして考えるべきは、通常この2つだけです。
null
None
null
は「Javaとの親和性」の要求から来ており、None
は関数型から来ています。
使い分けの指針は簡単、「基本的にはNone
(というかOption
)を使い、Javaとの境界ではnull
も考慮する」です。
ScalaではInt
などの数値もオブジェクトとして扱えますが、
これらはAnyVal
という型を継承しています。
そして、JavaでのObject
に相当する型として、AnyRef
があり、
このAnyVal
とAnyRef
の共通の親としてAny
型があります。
null
はAnyRef
という型を継承している型の変数には代入できますが、AnyVal
を継承したInt
型の変数には代入できません。
しかし、Option
型はAnyVal
を継承していようがAnyRef
を継承していようが使えるため、
無理をしてnull
を使う理由はScalaではありません*2。
その他勘違いされやすいけど違うものたち
()
「何もない」ではなく「1つしかない」を表すUnit
型というのもあります。
Boolean
でさえ2つあるのに、1つしかなくていったい何に使うんだ・・・と思いますか?
であれば、null
だって実は1つしかないですよね。
null
も()
も1つか値がありませんが、null
がAnyRef
を継承したどんな型の値としても使えるのに対して、
()
はUnit
型の値としてしか使えません。
・・・ますます何のためにあるのか分からなくなるかもしれませんが、簡単です。
これは、JavaやC++やC#で言うところのvoid
と同じような使い方をします。
JavaやC++やC#のvoid
型は値を持っていませんが、なぜ持っていないのでしょうか?
たぶん歴史的な経緯があるんでしょうけど、多相性*3を入れるなら、
void
も他の型と同じように使えた方が嬉しいのです。
にもかかわらず、これらの言語はvoid
を特別扱いしています。
void
が他の型と同じように値を持ち、return
で返すものがあったなら共通化できたにも関わらず、
そうなっていないため残念なことになっている例はたくさんあります*4。
Scalaではそうなっておらず、「値に意味がないこと」をUnit
型の()
という唯一の値で表すことで、
特別扱いを不要にしているのです。
0bitの情報と考えていいでしょう。情報量ゼロ。
Nothing
さて次はNothing
です。
これは id:kmizu さんのブログに詳しいです。
簡単にまとめると、
- 例外を投げる式の型
- 空のコレクションの要素型
として使います。 最初のうちはそうそう明示することがない型ですし、 一般的なコーディングでの「何もない」を表す型ではありません。
「何もない」をコード上で表すためにnull
やNone
と言った値を使ったのとは違い、Nothing
は値すらないのです。
これをnull
やNone
とひとくくりに表すのは混乱の元となるだけですから、やめましょう。
Nothing
は型を合わせるためだけに使う、と思っておけばいいはずです。
Nil
最後にNil
です。
が、他のものがある程度汎用的で広い範囲を相手にしていたのに対して、これはとても狭い範囲のものを相手にします。
これは単に「空のリスト」を表すオブジェクトです。
歴史的経緯によって(主にRubyなどを知っている層からすると)紛らわしい名前になっていますが、
そこは間違えて使ってもScalaは静的型付き言語なので、コンパイラさんが教えてくれます。
空のリストを「値がないこと一般」を表すように使うこともできますが、 そんな設計はScalaをはじめ、多くの言語ではしないでしょう。 わざわざひとまとめにする必要はありません。
余談:object
あと、スライドではNil
型、None
型としていましたが、
Scala言語の文脈ではこれらは型ではなく(シングルトン)オブジェクトです。
「型」とは言わないほうがいい気がします。
追記:
@bleis val a: None.type = None とかできる(Nilも同様)ので、Noneが型であることは一応間違いではないですね。
確かに一般的に"None型"って言い方はあまりしないし、あのスライドの作者がそのあたり変に勘違いしてそうなのは同意ですが
— Kenji Yoshida (@xuwei_k) 2015, 4月 14
すっかり忘れていましたが、型を取ることはできますね。
まとめ
Scalaでコード上で「何もないもの」を表したい場合は基本的にはNone
を使う。
他の言語を知っていると紛らわしく思える名前が出てきても、それらは別物なので気にしない。
コンパイラを頼れば問題ない。
こんなところで。