ScalaのOption.orNullをJavaで行う

# ScalaのOption.orNullをJavaで行う

Scalaの`Some(a)`を`a`に、`None`を`null`にするやり方が出てこなかったのでメモ。

かなり長くなってしまったので、お急ぎの方は各節の見出し付近を流し読みすることをおすすめします。

### とりあえず結論

`if`文を使わず、Javaから使用できる`scala.Option`のメソッドだけを使うやり方

```java
option.fold(() -> null, some -> some)
```

## 対象

- Javaをある程度ご存知で、Scalaの[`Option`クラス][api-option]を使ったことがある方
- Java 8とScala 2.12
  - ただし、バージョンが異なっても大まかな手順は変わらないはず

## 背景

Javaで書かれたそれなりの規模のプログラムを段階的にScalaに書き換えていくにあたり、`null`を使っている箇所を`Option`に直したくなります。

<!-- なぜnullを使いたくないのかの説明をするか？ -->

例えば、ファイル名を表す文字列から拡張子を返すJavaメソッドをScalaに移植することを考えてみます。このメソッドは、ファイル名にドット(`.`)が含まれているならドットの後の文字列を返し、含まれていないなら`null`を返します。

```FileName.java
public class FileName {
    public static String getExtension(final String filename) {
        final int dotIdx = filename.lastIndexOf('.');
        if (dotIdx >= 0) {
            return filename.substring(dotIdx);
        } else {
            return null;
        }
    }
}
```

このクラスを使うプログラムの例は以下のようになります。

```Program.java
public class Program {
    public static void main(final String[] args) {
        printExtension("foo.txt");
        printExtension("hosts");
    }

    public static void printExtension(final String filename) {
        final String ext = FileName.getExtension(filename);
        if (ext != null) {
            System.out.println("\"" + filename + "\"の拡張子は\"" + ext + "\"です。");
        } else {
            System.out.println("\"" + filename + "\"に拡張子はありません。");
        }
    }
}

// 実行結果：
// "foo.txt"の拡張子は"txt"です。
// "hosts"に拡張子はありません。
```

上記の`FileName`クラスをScalaで書き直してみます。`null`は使いたくないので`getExtension`の返り値の型は`Option[String]`にします。

```FileName.scala
object FileName {
  def getExtension(filename: String): Option[String] =
    filename.lastIndexOf('.') match {
      case dotIdx if dotIdx >= 0 =>
        Some(filename.substring(dotIdx))
      case _ =>
        None
    }
}
```

`FileName.getExtension`の返り値の型が変わってしまったので、このままでは`Program.java`のコンパイルは通りません。`Program`をScalaに移植するのが一番真っ当なやり方ですが、ここではJavaのコード量が膨大であり全てをScalaに移植することが現実的ではないケースを扱いたいので、`Program`はJavaのままで何とかして`FileName.getExtension`を使うことを考えます。

## 方針

`Some(a)`を`a`に、`None`を`null`に変換する処理を**Javaで**行います。これがこの記事の趣旨です。

この手の変換処理はScala側にヘルパメソッドを作っておくのが定石ではないかと思いますが、移植するメソッドの数が多いなどの理由で現実的ではないこともあるかと思いますので、ここではあくまで**Java側で**なんとかすることにこだわってみます(Scalaのmacro-paradiseあたりを使えば`Option[A]`の代わりに`A`か`null`を返すラッパーメソッドを自動生成するアノテーションを作れるかも。誰か作ってください)。

### 無難な手: ユーティリティクラス

おそらくもっとも常識的な手。

```ScalaCompat.java
import scala.Option;

public class ScalaCompat {
    public static <A> A optionOrNull(final Option<A> o) {
        if (o.isDefined())
            return o.get();
        else
            return null;
    }
}
```

- `o.isDefined()`は`o`が`Some(a)`なら`true`を返し、`None`なら`false`を返します。
- `o.get()`は`o`が`Some(a)`なら`a`を返し、`None`なら例外を送出します。

そこで、まず`o.isDefined()`を使い`o`が値を持っているかどうか調べ、`Some(a)`であることが分かってから`o.get`で`a`を取り出し、`o`が値を持っていないなら取り出そうとせずに`null`を返す、ということをします。

無難ですがいくつか欠点があります。

#### a. クラスを新たに作らなければならない。

クラスを新たに作らなければならないということは、以下の手間が発生します。

- パッケージを選ぶ
- クラス名を考える
- 今後、このクラスを使い続けなければならない。さもなくば、コードの重複が発生する

くだらない話だとは思うんですが、コードを保守する観点では意外とこういう点に悩まされます。

また、自作しなくともおそらく同様のクラスを持つライブラリがすでに存在すると思われますが、これだけのために依存を増やすのは躊躇します。

#### b. インスタンスメソッドとして呼ぶことができない。

`null`を乱用(正確には、無視)しているコードでメソッドチェーンをしている場合、上記のようなユーティリティクラスを導入すると可読性が大幅に落ちます。

例えば、以下のコードを考えます。`get`メソッドはすべて`null`を返しうるとします。(`java.util.Map<String, *>`みたいなイメージ)

```java
person.get("account").get("balance").get("USD")
```

この`get`メソッドを`Option`を返すメソッドに置き換え、ユーティリティクラスを使用してみます。既存の動作を維持するため、本来は必要なnullチェックを敢えて省きます。

```java
ScalaCompat.optionOrNull(ScalaCompat.optionOrNull(ScalaCompat.optionOrNull(person.get("account")).get("balance")).get("USD"))
```

とんでもなく読み書きしづらくなるのがお分かりいただけますでしょうか。「`null`チェックしろ！」「コードの構造を変えろ！」「そこもScalaに移植しろ！」等ツッコミが絶えないかと思いますが、時間の制約がある現実においては同クラスの他のメソッドが大量にあるなどの状況が原因で「Javaのままで」「あえて`null`チェックしないで(=NPEが発生する条件を変えないで」リファクタする必要があるものでして、そういう状況下でメソッドチェーンとユーティリティクラスの`static`メソッドは相性が最悪です。

#### 亜種1: `if`文

難しいことはない、単に`if`文を使えば済む話ではあります。

```java
final Option<Account> account = person.get("account");
if (account.isDefined()) {
  final Option<Balance> balance = account.get().get("balance");
  if (balance.isDefined()) {
    final Option<Double> usd = balance.get().get("USD");
    if (usd.isDefined()) {
      return usd;
    } else {
      return null;
    }
  }
}
throw new NullPointerException();
```

もともと1行だったコードが13行に膨れ上がってしまいました。行数が増えたことに加え、いったんローカル変数に代入するため型を明記する必要もあります。Scalaではこういう時のためにfor comprehensionが存在するのですが、残念ながらJavaではそのようなぜいたくはできません。

#### 亜種2: `try`文

`Option.get`は`None`については`NoSuchElementException`を送出するので、それを捕まえて代わりにNPEを送出しちゃおう、という手です。

```java
try {
  person.get("account").get().get("balance").get().get("USD").get();
} catch (final NoSuchElementException e) {
  throw new NullPointerException();
}
```

比較的簡潔に書くことができます。1行が4行に増えることを許容できるなら問題ないように見えます。しかし、このアプローチは地味ながらやっかいな問題があります。`Option.get()`ではなく、`person.get("account")`自体が`NoSuchElementException`を送出したらどうなるでしょうか？もともとのコードではその例外がそのまま送出されますが、このコードではそれもNPEに置き換えてしまいます。キャッチするべきではない例外をキャッチしてしまうため、密かにバグの原因になるおそれがあります。

ケースによるとは思いますが…この記事ではこれらはすべて実用に耐えないとみなします。以下、「`scala.Option`のインスタンスメソッドを使って」`Option`を`null`にする方法を考えてみます。

### 失敗1: `Option.orNull`

`Option`には`orNull`というメソッドがあります。これは`Option`自身が`Some(a)`なら`a`を、`None`を`null`を返す、まさにおあつらえ向きのメソッドですが、残念ながらJavaから使うのは困難です。その理由は`Option.orNull`の宣言を見ると分かります。[Scala APIレファレンス][api-option-ornull]によるとシグネチャは

```scala
final def orNull[A1 >: A](implicit: ev <:<[Null, A1]): A1
```

問題はこの`implicit`引数です。一言でいうとこれは型引数`A`が`Int`など`null`になれない型の場合に使用できないようにするために使われています(詳細な説明: [StackOverflow][so-option-ornull])。Scalaで使用する分には`implicit`引数をコンパイラが自動で埋めてくれるので気にする必要はないのですが、Javaでは手で埋めなければならないので非常に面倒な作業になります。

実際に手で埋めなければならないとしたらどうすればいいでしょうか？せっかくなので考えてみましょう。

`javap`によると、Scala-2.12.6時点で`Option.orNull`は以下のようなメソッドにコンパイルされているようです:

```
$ javap scala/Option.class | grep orNull
  public final <A1> A1 orNull(scala.Predef$$less$colon$less<scala.runtime.Null$, A1>)
...
```

`implicit`引数がただの引数にコンパイルされていることがわかります。型はScalaの標準ライブラリ、scala.Predef$のメンバクラスである`<:<`であり、`Function1`のサブクラスです。

[Scala標準ライブラリのscala/Predef.scala][api-predef-<:<]より要点のみ引用

```scala/Predef.scala
object Predef {
  // 謎パラメータの型(1)
  sealed abstract class <:<[-From, +To] extends (From => To) with Serializable
  // <:<の唯一のインスタンス(2)
  private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x }
  // implicit値の源(3)
  implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
```

ポイントごとに解説します。

- `(1)`: 謎パラメータ(implicit引数の正体)の型の定義です。`Serializable`をmixinしていること以外は`Function1[From, To]`と何ら変わりません。
- `(2)`: `(1)`が`sealed`クラスなので、これが唯一のインスタンスです。`def apply`の中身をよく見ると、実質的には`identity`と同じです。
- `(3)`: 値パラメータを取らない`implicit def`関数です。そのため、`implicit val`値と同様、`implicit`パラメータを暗黙的に渡す際にこの値が使用されます。

これらを総合すると、`Option.orNull`を呼ぶときにScalaコンパイラによって暗黙的に渡されるimplicitパラメータは`(3)`であり、その中身は`(2)`(のキャスト)であり、それは実質的には`identity`である、ということになります。

もう少し具体的に考えるため、任意の型`X`を持つ`None: Option[X]`に対して`#orNull`を呼ぶプログラムをトレースしてみます。

```scala
val o = Option.empty[X]

val x: X
  = o.orNull
  // 式の型がXなので、orNullの型引数A1にXが選択される。
  // <:<は第一型引数について反変で、Null <: Xなので、(Null <:< X) >: (X <:< X)である。
  // (X <:< XがNull <:< Xのサブクラスとなる。)
  // よってPredef.$conforms[X]: X <:< Xがimplicit引数に選択される。 (☆)
  = o.orNull(Predef.$conforms[X])
  = o.orNull(singleton_<:<.asInstanceOf[X <:< X])
  // singleton_<:<が実質的にはidentityであることに注意すると、
  ~= o.orNull(identity: X => X)
```

ただし、`singleton_<:<`は`private[this]`なので、明示的に指定したいなら`Predef.$conforms[X]`を指定すればいいことになります。これをJavaで書くと

```java
scala.Predef.<X>$conforms()
```

ということになります。

以上をまとめると、`Option.orNull`をJavaで呼び出す場合は以下のようになります。

```java
final scala.Option<A> option = ...;
final A aOrNull = option.orNull(scala.Predef.<A>$conforms());
```

なるほど。しかしこれはコンパイルが通りません。`A <:< B`が`A`について反変なのはScalaの中だけで、Javaでは非変として扱われるためです。`scala.Predef.<A>$conforms()`の型はJavaで書くと`scala.Predef$$less$common$less<A, A>`ですが、implicit引数の型は`<A1>scala.Predef$$less$common$less<scala.runtime.Null$, A1>`なので、`A`と`A1`の両方が`scala.runtime.Null$`でなければならないのです。

したがって正しくは以下のようになります。

```java
final scala.Option<A> option = ...;
final A aOrNull = (A)(Object) option.orNull(scala.Predef.<scala.runtime.Null$>$conforms());
```

- `$conforms`のジェネリクス引数に`scala.runtime.Null$`を指定することで非変性を潜り抜けます(ハック)。
- 結果、`#orNull`の返り値の型が`scala.runtime.Null$`になってしまうので`A`にキャストしなおします(ハック)。
  - 無関係な型同士のキャストなので、いったん`Object`へキャストする二重キャストテクニックを使います(超ハック)。

これを使って`Program.java`を書き直すと以下のようになります。

```Program.java
public class Program {
    public static void main(final String[] args) {
        printExtension("foo.txt");
        printExtension("hosts");
    }

    public static void printExtension(final String filename) {
        final String ext = (String)(Object) FileName.getExtension(filename).orNull(scala.Predef.<scala.runtime.Null$>$conforms());
        if (ext != null) {
            System.out.println("\"" + filename + "\"の拡張子は\"" + ext + "\"です。");
        } else {
            System.out.println("\"" + filename + "\"に拡張子はありません。");
        }
    }
}

// 実行結果：
// "foo.txt"の拡張子は"txt"です。
// "hosts"に拡張子はありません。
```

冗談半分で試してみましたが、予想以上にhackyな結果になってしまいました。このプログラムが意図通りに動作する(してしまう)理由は`Predef.singleton_<:<`が実質的に`identity`であり、コンパイル時・実行時の型に一切関知しないためです。

そもそも、`Option.orNull`のimplicit引数はその値そのものには特に意味がなく(ただの`identity`だし)、「値が存在するかどうか」に意味があります。例えば反例として、`Option[Int].orNull`を呼び出そうとすると、コンパイラは`Null <:< Int`の型を持つ値を探すことになります。しかし、唯一のimplicit値たる`implicit def $conforms[A]: A <:< A`の`A`に`Int`を代入すると`Int <:< Int`となりますが、`Null <: Int`が成り立たないため`(Null <:< Int) >: (Int <:< Int)`が成り立ちません(前述の`(☆)`が失敗する)。したがって`Option[Int].orNull`に対応するimplicit引数が見つからないことになり、**コンパイル時**エラーにすることができるわけです。

Javaでは(説明していませんがScalaでも)、キャストを悪用すれば無理やり手動でimplicit引数を渡してしまうことができますが、せっかくのコンパイル時チェックが機能しなくなってしまいます。

何らかの理由により、どうしてもどうしても`Option.orNull`をJavaから呼ばなければならない場合の、最後の最後の最終手段に試してみるのはいいかもしれません。私は全くお勧めしません。

### 失敗2: `Option.getOrElse(null)`

型が`Option[A] => A`かそれに近いメソッドはいくつかありますが(`orNull`もその一つ)、代表的なものは`getOrElse`でしょう。

`Option.getOrElse`の宣言を[APIレファレンス][api-option-getorelse]で見てみると:

```scala
final def getOrElse[B >: A](default: => B): B
```

`orNull`と違ってimplicit引数がないです。引数の`default`が`Option`が`None`の場合に代わりに返される値なので、`null`を指定すればいいように思えます。

```TooBad_NPE.java
public class TooBad_NPE {
    public static void main(final String[] args) {
        final scala.Option<String> some = scala.Option.apply("foo");  // Some("foo")
        final String foo = some.getOrElse(null);
        System.out.println(foo);                                      // foo
        final scala.Option<String> none = scala.Option.apply(null);   // None
        final String shouldBeNull = none.getOrElse(null);             // !! throw NullPointerException !!
        System.out.println(shouldBeNull);
    }
}
```

コンパイルは通ります。また、`Option`が`Some`の場合も期待通りの動作をします。しかし、`Option`が`None`だった場合NPEが送出されてしまいます。

原因はやはり`getOrElse`のシグネチャにあります。宣言をよく見ると`default: => B`とあります。`default`の型は`B`ですが名前渡し(`=>`)になっています。名前渡しの引数は特殊な引数で、引数の評価(実行)が呼び出し元ではなく呼び出されたメソッド内で行われます。こんな機能はJavaにはないので何か特別な型にコンパイルされているに違いありません！`javap`で見てみましょう:

```
$ javap scala/Option.class | grep getOrElse
  public final <B> B getOrElse(scala.Function0<B>)
```

というわけで、名前渡し引数`=> B`は`scala.Function0<B>`(つまり、ゼロ引数関数`() => B`)にコンパイルされていることが分かりました。ということは、`getOrElse[B](default: =>B)`の引数`default`に`null`を渡すということは、**`null`になるのは`B`ではなく、`Function0[B]`だ**ということです。`B`も`Function0<B>`もJavaにとっては`Object`のサブクラスなのでコンパイルは通ってしまいます。「`None`の場合に何を返せばいいか教えてくれるはずのオブジェクト」が`null`なので、`None`の場合にNPEが発生してしまうというわけです。

…ということは、`() => null: Function0[B]`に相当するオブジェクトをJavaで書ければ良い、というふうに見えます。

### 失敗3: `Option.getOrElse(() -> null)`

Java8で導入された「SAM(Single Abstract Method)変換」のおかげで、Javaのラムダ式はダイレクトに`scala.Function1`に変換されます。そのため、Javaでこう書けます:

```java
final scala.Option<String> some = scala.Option.apply("foo");  // Some("foo")
System.out.println(some.getOrElse(() -> null));               // foo
final scala.Option<String> none = scala.Option.apply(null);   // None
System.out.println(none.getOrElse(() -> null));               // null
```

ほとんどScalaと同じように`getOrElse`を呼ぶことができます。ありがたいことですね。これでようやく期待通りの動作を実現できた…ように思えますが、残念ながらまだ落とし穴が残っています。実は、`Option.getOrElse`は*Javaでは*型安全ではありません。

```TooBad_CCE.java
import java.util.Date;

public class TooBad_CCE {
    public static void main(final String[] args) {
        final scala.Option<String> some = scala.Option.apply("foo");  // Some("foo")
        final String foo = some.getOrElse(() -> null);
        System.out.println(foo);                                      // foo
        final scala.Option<String> none = scala.Option.apply(null);   // None
        final String shouldBeNull = none.getOrElse(() -> null);
        System.out.println(shouldBeNull);                             // null
        // ここまではOK

        // Optionの中身がDateなのに、受け手がString
        final scala.Option<Date> now = scala.Option.apply(new Date());
        final String nowstr = now.getOrElse(() -> null);              // !! throw ClassCastException !!
    }
}
```

`now`が`Option<Date>`型なので、`now.getOrElse(...)`の型は`Date`であるべきですが、上記のように別の型で受けてもコンパイルが通ってしまいます。実行時に`ClassCastException`が送出されてしまいます。

どうしてこんなことが起こってしまうのでしょうか？ここで、ScalaのAPIレファレンスとjavapの出力結果をもう一度よく確認してみましょう。

```apidoc.scala
final def getOrElse[B >: A](default: => B): B
```

```javap.java
public final <B> B getOrElse(scala.Function0<B>)
```

`B`に課せられた制約に差があります。Scalaでは`[B >: A]`、つまり`B`は`A`のスーパークラスでなければなりませんが、Javaではただの`<B>`、つまり`B`は`A`とは無関係な型を選べてしまいます。`TooBad_CCE.java`の例では、返り値の型から`B`に`String`が選択されますが、Javaコンパイラにとっては何の問題もありません。したがってコンパイルが通り、実際には型が違うので実行時に例外が出る、というわけです。

一応、ジェネリクス引数を明示的に指定することでコンパイルエラーを発生させることはできます。

```java
final scala.Option<Date> now = scala.Option.apply(new Date());
final String nowstr_ct = now.<Date>getOrElse(() -> null);    // compilation error
final String nowstr_rt = now.<String>getOrElse(() -> null);  // !! throw ClassCastException !!
```

とはいえ大変な手間ですし、`Option`オブジェクトの型ではなく誤って受け手の型を指定してしまうとやはり実行時エラーとなるため、ケアレスミスの原因となります。

`getOrElse`は期待通りに動作しますが、型安全性を失ってしまうことが分かりました。発見が難しいバグの原因となってしまうため、できれば避けたいところです。

### 正解: `Option.fold(() -> null, some -> some)`

`Option`には`fold`というメソッドがあります。[APIレファレンス][api-option-fold]より:

```scala
final def fold[B](ifEmpty: => B)(f: (A) => B): B
```

`Option`が`None`なら`ifEmpty`を評価した値を、`Some(a)`なら`f(a)`を返します。`f`に`identity`を指定すれば`getOrElse`と同じ振る舞いをします。肝は第2パラメータセクションに`A`と`B`の両方が現れる点で、`A => A`の型を持つ関数を与えれば、型パラメータを明示的に指定することなく`B`を`A`に固定することができます。

このメソッドはただの2引数メソッドにコンパイルされます。`javap`より:

```
$ javap scala/Option.class | grep fold
  public final <B> B fold(scala.Function0<B>, scala.Function1<A, B>);
```

というわけで、第1引数に`null`を返す関数を、第2引数に引数をそのまま返す関数を指定すれば良さそうです。やってみましょう。

```WorksAsIntended.java
import java.util.Date;

public class WorksAsIntended {
    public static void main(final String[] args) {
        final scala.Option<Date> now = scala.Option.apply(new Date());
        // final String nowstr = now.fold(() -> null, some -> some);  // compilation error
        final Date nowdate  = now.fold(() -> null, some -> some);  // ok, now
        final scala.Option<Date> none = scala.Option.apply(null);
        final Date nulldate = none.fold(() -> null, some -> some); // ok, null
        System.out.println(nowdate);   // 現在時刻
        System.out.println(nulldate);  // null
    }
}
```

(`some`の代わりにどんな名前でも構いませんが、`Some`のケースを扱うことを明示するためにこの名前にしてみました。)

少々長いですが、「`scala.Option`のインスタンスメソッドを使って」、さらに型安全な方法を実現することができました。

#### 余談: 型推論に失敗する場合

オーバーロードされているメソッドの引数として渡す場合など、Javaコンパイラが型推論をできない場合はジェネリクス引数を明示的に指定する必要があります。

しかしその場合も、間違ったジェネリクス引数を指定した場合はコンパイルエラーになるため、`Option.getOrElse(() -> null)`の場合と異なり、型安全は保たれます。

```ExplicitGenParam.java
import java.util.Date;

public class ExplicitGenParam {
    public static void main(final String[] args) {
        final scala.Option<Date> now = scala.Option.apply(new Date());
        println(now.fold(() -> null, some -> some));          // compilation error, ambiguous
        println(now.<Date>fold(() -> null, some -> some));    // OK, 現在時刻
        println(now.<String>fold(() -> null, some -> some));  // compilation error, incompatible types
    }

    public static void println(final Date x) {
        System.out.println(x);
    }

    public static void println(final String x) {
        System.out.println(x);
    }
}
```

## 結論

JavaでScalaの`Option[A]`を`A`もしくは`null`にするやり方は

```java
option.fold(() -> null, some -> some)
```

がおすすめです。

これを使って「背景」節の`Program.java`を`FileName.scala`向けに書き直してみましょう。

```Program.java
public class Program {
    public static void main(final String[] args) {
        printExtension("foo.txt");
        printExtension("hosts");
    }

    public static void printExtension(final String filename) {
        final String ext = FileName.getExtension(filename).fold(() -> null, some -> some); // ここが変わった
        if (ext != null) {
            System.out.println("\"" + filename + "\"の拡張子は\"" + ext + "\"です。");
        } else {
            System.out.println("\"" + filename + "\"に拡張子はありません。");
        }
    }
}

// 実行結果：
// "foo.txt"の拡張子は"txt"です。
// "hosts"に拡張子はありません。
```

めでたしめでたし。

## 教訓

この件でいくつか教訓が得られたのでまとめておきます。

- `Option.orNull`は事実上Javaからは使えない。
- 名前渡し引数(`=> B`)は`scala.Function0<B>`にコンパイルされるので、`null`を渡すときはラムダ式`() -> null`を渡す
- Scalaの上下限付き型パラメータ(`[B >: A]`)を持つメソッドをJavaから呼ぶときは、型安全性が保たれない恐れがある


とはいえ、プログラムを全部Scalaで書いてしまえば以上の手間はすべて不要になりますので、皆様、新しく開発する際は是非Scalaをご検討ください。


[api-option]: https://www.scala-lang.org/api/current/scala/Option.html
[api-option-fold]: https://www.scala-lang.org/api/current/scala/Option.html#orNull[A1%3E:A](implicitev:Null%3C:%3CA1):A1
[api-option-getorelse]: https://www.scala-lang.org/api/current/scala/Option.html#orNull[A1%3E:A](implicitev:Null%3C:%3CA1):A1
[api-option-ornull]: https://www.scala-lang.org/api/current/scala/Option.html#orNull[A1%3E:A](implicitev:Null%3C:%3CA1):A1
[api-predef-<:<]: https://github.com/scala/scala/blob/v2.12.6/src/library/scala/Predef.scala#L499
[so-option-ornull]: https://stackoverflow.com/questions/4467601/please-explain-use-of-options-ornull-method