Posted at

独書会 Scala IN DEPTH @夜のイタリアンカフェ その4

More than 5 years have passed since last update.

独書会 Scala IN DEPTH @大人のバーガー屋 その3で記述した、第2章 The core rules の続き。


要約


Nullの代わりにNoneを使う



  • nullを阻止するプログラミングをするには、Scalaはベストな選択である。


  • nullを阻止するために、scala.Optionを使うことができる。


  • Optionあるか・ないか のコンテナと考えることができる。


  • Optionには、SomeNoneの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文やブロックのネストに頼ることなく強力なチェックを可能にする。

特定のパラメータの利用に基づいたコードブロックを実行したいときには、ブロックを好んで使う。


変数が初期化されていれば、ブロックを実行する



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 初期化されていないパラメータ で渡すべきではない。


別の変数を生成するために、潜在的に初期化されていない変数を使う



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] =
<function3>

(Joshua D. Suereth, Scala in Depth, p.37)




  • lift3メソッドは、唯一のパラメータとして関数を受け取ることを除いて、先ほどのcreateConnectionメソッドに似ている。


  • Function3traitは、3つの引数を受け取り結果を返す関数である。


  • lift3関数は、入力として3つの引数の関数を受け取り、3つの引数の関数を出力する。

  • REPLの出力からわかるように、Optionフレンドリーな関数を作るために、既存の関数に対して使うことができる。

直接DriverManager.getConnectionメソッドを受け取り、初期のcreateConnectionメソッドと意味的に等価な関数になる。

初期化されていない変数のカプセル化で使われる時、このテクニックはうまく動作する。

初期化されていると仮定して、ユーティリティメソッドも含めてコードのほとんどで書くことができる。

そして必要な時に、これらの関数をOptionフレンドリーの変種にする。

重要な点は、Optionに含まれるものからequalityとhashCodeを導出することである。

Scalaでは、equalityhashCode理解することは、特に多態では重要である。


Rule


4. nullの代わりにNoneを使う


  • Javaでは値をnullで初期化するのが習慣であったが、Scalaでは同じ目的でOptionが用意されている。


  • Noneを使うと、意図しない null pointer を防げる。


所感



  • Optionはどこで使うかを見極めるのが重要。なんでもかんでも使うと、逆に簡潔性が失われる。

  • 複数のOption型の変数を扱うのに for-comprehension を使うのは有用。

  • 同一ブロック内で、あるOption型の変数を2回以上評価させたら負けかな。


    • つまり、関数の戻り値をOption型で変数に入れるのは、いけていない。値を 取り出す前取り出した後 の変数名で悩むため。。。