2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

独書会 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`文やブロックのネストに頼ることなく強力なチェックを可能にする。
特定のパラメータの利用に基づいたコードブロックを実行したいときには、ブロックを好んで使う。

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

> ```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&nbsp;2.20&nbsp;Merging&nbsp;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`型で変数に入れるのは、いけていない。値を __取り出す前__ と __取り出した後__ の変数名で悩むため。。。


2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?