ScalaのOptionの話

ScalaのOptionの話

こんにちは!ワタナベ@札幌です。今回のテーマはScalaです!
Null安全の立役者、Optionについて書きました。
Scalaには「値が存在するかもしれないし、しないかもしれない」というモノを扱うためのOptionというクラスがあります。

プログラムを書く上で存在が不確かなモノを扱う機会は多く、存在すると思い込んでコードを書いてしまうと思わぬ事故に遭遇することがあります。
Optionは、このような事故を防ぎやすくするとともに、存在が不確かであるモノをどう扱っているかをコードに物語らせることができる、素敵なクラスです!

今回は、Optionクラスのソースコードを眺めながら理解を深めていこうと思います!なお、Optionの具体的な使い方(メソッドの使い方)については、諸先輩方が書かれている記事が散見されましたので、本稿ではあまり触れていませんのでご了承ください。

それでは、早速定義を見てみましょう!(於 v2.13.1)

sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable

sealed

sealedが付いているので、同一ファイル内からのみ継承可能です。

そして、同一ファイル内に存在するサブクラスというのは、SomeNoneです。
存在しているモノを表すクラスがSome、存在していないモノを表すクラスがNoneです。

final case class Some[+A](value: A) extends Option[A]
... 略 ...
case object None extends Option[Nothing]<br>

sealedと定義されていることで、matchの際に↓のように書くと

  op match {// opはOptionのインスタンス
    case Some(_) => ???
  }

コンパイラが↓のように「OptionのサブクラスにはNoneもあるけど、忘れていませんか?」と警告してくれます。

Warning: match may not be exhaustive.

It would fail on the following input: None

変位指定

[+A](=共変)なので、↓のような関係性のクラスがあった時に

class Fruit
class Apple extends Fruit

↓はもちろんOKで、

val maybeFruit: Option[Fruit] = Some(new Fruit())

↓こちらもOKとなります!

val maybeFruit: Option[Fruit] = Some(new Apple())

余談

少し前に登場したNoneですが、定義を見るとOption[Nothing]を継承しています。

case object None extends Option[Nothing]

Nothingはすべての型のサブタイプなので、NoneはOptionの型パラメータに依らず、あらゆるOptionのインスタンスに入れることができます。

このようにScalaでは、ジェネリクスクラスの型パラメータの変位をクラスの宣言時に指定することができます。

仮に、Option[A]という定義がされていたら、

val maybeFruit: Option[Fruit] = Some(Apple())

という使い方をすることができません。もちろん、OptionのインスタンスにNoneを入れることもできなくなってしまいます。

Mix-in

IterableOnceをミックスインしているので、イテレータによって走査可能です。また、case classに自動的にくっついてくることでお馴染みのProductSerializableをミックスインしています。パターンマッチ可能、オブジェクトの書き出し&読み込みが可能、という点を押さえておけばOKでしょうか。

コンパニオンオブジェクト

暗黙の型変換(Option -> Iterable)が定義されているので、Iterableのメソッドも使うことができます!(型変換の実装が2.12->2.13で若干変わっています)

  /** An implicit conversion that converts an option to an iterable value */
  implicit def option2Iterable[A](xo: Option[A]): Iterable[A] =
    if (xo.isEmpty) Iterable.empty else Iterable.single(xo.get)

コンパニオンオブジェクトにはwhenunlessというメソッドも実装されています。(v2.13から新たに追加されたメソッドのようです)

Optionのインスタンス生成時に、Optionで包もうとしているオブジェクトとは直接関係の無い条件(=引数のcondition)によって、Someを生成するかNoneを生成するかを分岐させる、という処理ですね。

どのような使い方がフィットするか、具体例はすぐには思いつきませんが、文脈がわかりやすいので使い所はありそうですよね!

 /** When a given condition is true, evaluates the `a` argument and returns
   *  Some(a). When the condition is false, `a` is not evaluated and None is
   *  returned.
   */
  def when[A](cond: Boolean)(a: => A): Option[A] =
    if (cond) Some(a) else None

  /** Unless a given condition is true, this will evaluate the `a` argument and
   *  return Some(a). Otherwise, `a` is not evaluated and None is returned.
   */
  @inline def unless[A](cond: Boolean)(a: => A): Option[A] =
    when(!cond)(a)

いかがでしたでしょうか?
具体的な使い方に関する理解は進まなかったと思いますが、Optionの構造に関して深く知るきっかけになればと思います。
また、Scalaの魅力も感じていただけていれば幸甚の極みです。

採用情報

メンバーズエッジで最高のチームで最高のプロダクトを作りませんか?

最高のプロダクトをつくる 最高のチームで働く

在宅でも、地方でも、首都圏でも。多様な働き方で最高のチームをつくり、お客様のプロダクトパートナーを目指します。アジャイル開発を通じ、開発現場の第一線で活躍し続けたいエンジニアを募集しています。