🙄

RubyでOptional型っぽいことをする

に公開1

一部の言語では、Optional型(ないしOption型やMaybe型)を提供しています。

Optional型は「NULLかもしれない値」を安全に扱う型です。

RubyにはOptional型はありませんが、

Optional型のメリット

Optional型が導入される以前は、NULLチェックするのはプログラマーの責任でした、

Ticket ticket = getTicket(); // NULLを返すかもしれない関数
ticket.getTitle(); // 危険なコード。ticketがNULLだったら例外が発生するかもしれない

if (ticket != null) { // NULL かもしれないのでチェックする
  ticket.getTitle(); // NULL ではないので確実にメソッド呼び出しできる
}

Optional型で値をラップすると、以降の処理を安全に書くことができます。

Optional<Ticket> maybeTicket = Optional.ofNullable(getTicket()); // NULLかもしれない値をラップ

// maybeTicket.getTitle(); // メソッドを緑説呼び出すことはできない

// 値がNULLでなければ getTitleメソッドを呼び、値をOptionalでラップして返す
Optional<String> maybeTitle = nullableString.map(ticket -> ticket.getTitle())

// 値がNULLでなければその値を、NULLなら "no title" を返す
String title = maybeTitle.orElse("no title");

RubyにはOptional型は無いけども

RubyにはOptional型が無く、裸のnilを使わなければなりません。

しかし、RubyはJavaなどと異なりnilに対してもメソッドを呼び出せます。そのためOptional型のようなこと(if文でnilチェックをせずに処理すること)ができます。

RubyでOptional型っぽいことをする例

値があれば取得し、無ければデフォルト値を使う(orElse、orElseGet)

title = maybeTitle || "no title"

title = maybeTitle || getDefaultTitle()

値があれば取得し、無ければ例外を送出する(orElseThrow)

title = maybeTitle or throw "title is missing"

# こっちの方がRuby的かも
throw "title is missing" if maybeTitle.nil?
title = maybeTitle

値があれば処理をする(ifPresent)

これはif文を使わざるを得ないかも。そもそもJavaでもifPresentは使う機会が少ない印象。

unless maybeTicket.nil?
   process(maybeTicket)
end

値を変換する(map)

ぼっち演算子とthenを組み合わせる。

maybeTitle = maybeBook&.then { |book| book.title }

条件を満たしていれば値を残す(filter)

値が条件を満たしていれば値を残し、条件を満たさなければ nil にする。
元々 nil であれば nil のまま。

maybeSaleBook = maybeBook if maybeBook&.then { |book| book.sale? }

# こう書く方がRuby的かも
maybeSaleBook = maybeBook if maybeBook&.sale?

配列に変換(stream)

Optionalを「要素数が0または1のコレクション」とみなし、配列に変換します。

Arrayメソッドを使えば、大体のケースでこの変換ができるのですが、値がハッシュや配列など.to_ary や .to_a`を定義したオブジェクトであると意図した結果になりません。

books = Array(maybeBook)

# Array(nil) => nil
# Array(42) => [42]
# Array("abc") => ["abc"]

# Array({:it => 3}) => [[:it, 3]]
# Array([1, 2, 3]) => [1, 2, 3]

# objの型に関わらず、配列に変換するにはこうする
maybeObj&.then { |obj| [obj] }.to_a

# こうする方がRuby的かも
arr = maybeBook ? [maybeBook]: []

感想

無理にOptionalっぽい書き方をするより、Ruby的な書き方をする方が良さそうです。

&.then はイディオムとして覚えておくと、役立つこともありそうです。

Discussion

石谷太一石谷太一

値があれば処理をする(ifPresent)
これはif文を使わざるを得ないかも。そもそもJavaでもifPresentは使う機会が少ない印象。

maybeTicket&.then { process(maybeTicket) }

これはどうでしょうか?