独書会 Scala IN DEPTH @大人のバーガー屋 その3で記述した、第2章 The core rules の続き。
要約
Nullの代わりにNoneを使う
-
null
を阻止するプログラミングをするには、Scalaはベストな選択である。 -
null
を阻止するために、scala.Option
を使うことができる。 -
Option
は あるか・ないか のコンテナと考えることができる。 -
Option
には、Some
とNone
の2つのサブクラスがある。
null
を許す言語は、null
は戻り値として致命的でないエラーを示す。
また、 初期化されていないこと を示すプレースホルダとして使われる。
Scalaでの表現
-
Option
のサブクラスNone
で、null
を表せる。 -
Option
のサブクラスSome
で、 初期化された変数 と 致命的でない変数 の状態を表せる。
Option
の生成
- 値を含まない
Option
は、None
オブジェクトを通して生成される。 - 値を含む
Option
は、Some
のファクトリメソッドを通して生成される。
Option
の内部の値の取得
get
-
Option
に格納した値にアクセスする - 存在しない場合は、
Exception
をスローする。 - 他の言語で
null
値を許容するのに似ている。
getOrElse
- 値が存在する場合は、
Option
に格納された値にアクセスしようとする。 - 値が存在しない場合は、メソッドに指定した値を返す。
get
よりもgetOrElse
を常に選ぶべきである。
Option
のファクトリメソッド
Option
のコンパニオンオブジェクトでは、ファクトリメソッドを提供している。
- 入力が
null
ならNone
を作る。 - 入力が初期化されているなら
Some
を作る。
Option
にラップすることは、信頼出来ないソース(別のJVM言語とか)からの入力を受け取ることを簡単にする。
コードでnull
チェックする箇所がシンプルになる。
Option
は、null
を使ったりif
チェックするよりも高度な機能を提供する。
Option
のテクニック
-
Option
の最大の特徴は、 collection のように扱えること。 -
map
,flatMap
,foreach
メソッドを使うことができるし、内部で式を利用することもできる。 - これにより簡潔なシンタックスで書けるし、初期化されていない値を処理する様々なメソッドを使える。
オブジェクトを生成するかデフォルトを返すか
Listing 2.17 Creating an object or returning a default
def getTemporaryDirectory(tmpArg: Option[String]): java.io.File = {

tmpArg.map(name => new java.io.File(name)).
filter(_.isDirectory).
getOrElse(new java.io.File(
System.getProperty("java.io.tmpdir")))
}
> (Joshua D. Suereth, Scala in Depth, p.35)
ここでは、以下の場合に適切なテンポラリディレクトリを返したい。
- ユーザが新しいファイルを指定しない場合
- ユーザが与えた引数が存在しないディレクトリの場合
- ディレクトリを提供できなかった場合
`if`文やブロックのネストに頼ることなく強力なチェックを可能にする。
特定のパラメータの利用に基づいたコードブロックを実行したいときには、ブロックを好んで使う。
### 変数が初期化されていれば、ブロックを実行する
> ```scala:Listing 2.18 Executing code if option is defined
val username: Option[String] = ...
for(uname <- username) {
println("User: " + uname)
}
(Joshua D. Suereth, Scala in Depth, p.36)
-
Option
は値を含む場合に、コードブロックを実行することができる。 -
foreach
メソッドで実現できる。つまり、Option
内の全ての要素を反復処理する。 -
Option
は、0 or 1つの値だけを含む。これは、ブロックが 実行される か 無視される かを意味する。 - このシンタックスは特に式に適している。
Listing 2.19 Executing code if several options are defined
def authenticateSession(session: HttpSession,
username: Option[String],
password: Option[Array[Char]]) = {
for(u <- username;
p <- password;
if canAuthenticate(u, p)) {
val privileges = privilegesFor(u)
injectPrivilegesIntoSession(session, privileges)
} }
> (Joshua D. Suereth, Scala in Depth, p.36)
ユーザ認証をして、セキュリティトークンをHTTPSessionにインジェクションする例。
こうしておけば、後でフィルタとサーブレットがアクセス権限をチェックできる。
- この例では、式で条件ロジックを組み込んでいる。少ないネストで論理ブロックを保持するのに役に立つ。
- 全てのヘルパーメソッドが`Option`クラスを使う必要ないことは、重要なことである。
- `Option`は、初期化していない変数の最前線の防御として機能する。`Option`がコードの残りを汚染する必要はない。
- 引数としての`Option`は、 __初期化されていない・初期化されている可能性__ があることを意味する。
- メソッドが`Option`として受け取らないなら、値を`null` or __初期化されていないパラメータ__ で渡すべきではない。
### 別の変数を生成するために、潜在的に初期化されていない変数を使う
> ```scala:Listing 2.20 Merging options
def createConnection(conn_url: Option[String],
conn_user: Option[String],
conn_pw: Option[String]) : Option[Connection] =
for {
url <- conn_url
user <- conn_user
pw <- conn_pw
} yield DriverManager.getConnection(url, user, pw)
(Joshua D. Suereth, Scala in Depth, p.37)
初期化されていない可能性がある変数の集合を変換したい場合がある。
この時はfor
式を使い、同時にyield
も使う。
lift
関数
Listing 2.21 Generically converting functions
scala> def lift3[A,B,C,D](
| f: Function3[A,B,C,D]): Function3[Option[A], Option[B],
| Option[C], Option[D]] = {
| (oa : Option[A], ob : Option[B], oc : Option[C]) =>
| for(a <- oa; b <- ob; c <- oc) yield f(a,b,c)
| }
lift3: [A,B,C,D](f: (A, B, C) => D)(Option[A],
Option[B],
Option[C]) => Option[D]
scala> lift3(DriverManager.getConnection)
res4: (Option[java.lang.String],
Option[java.lang.String],
Option[java.lang.String]) => Option[java.sql.Connection] =
> (Joshua D. Suereth, Scala in Depth, p.37)
- `lift3`メソッドは、唯一のパラメータとして関数を受け取ることを除いて、先ほどの`createConnection`メソッドに似ている。
- `Function3`traitは、3つの引数を受け取り結果を返す関数である。
- `lift3`関数は、入力として3つの引数の関数を受け取り、3つの引数の関数を出力する。
- REPLの出力からわかるように、`Option`フレンドリーな関数を作るために、既存の関数に対して使うことができる。
直接`DriverManager.getConnection`メソッドを受け取り、初期の`createConnection`メソッドと意味的に等価な関数になる。
__初期化されていない変数のカプセル化で使われる時、このテクニックはうまく動作する。__
初期化されていると仮定して、ユーティリティメソッドも含めてコードのほとんどで書くことができる。
そして必要な時に、これらの関数を`Option`フレンドリーの変種にする。
重要な点は、`Option`に含まれるものからequalityとhashCodeを導出することである。
__Scalaでは、`equality`と`hashCode`理解することは、特に多態では重要である。__
# Rule
## 4. `null`の代わりに`None`を使う
- Javaでは値を`null`で初期化するのが習慣であったが、Scalaでは同じ目的で`Option`が用意されている。
- `None`を使うと、意図しない __null pointer__ を防げる。
所感
--
- `Option`はどこで使うかを見極めるのが重要。なんでもかんでも使うと、逆に簡潔性が失われる。
- 複数の`Option`型の変数を扱うのに __for-comprehension__ を使うのは有用。
- 同一ブロック内で、ある`Option`型の変数を2回以上評価させたら負けかな。
- つまり、関数の戻り値を`Option`型で変数に入れるのは、いけていない。値を __取り出す前__ と __取り出した後__ の変数名で悩むため。。。