Scala の for-comprehension

## Option

* http://www.scala-lang.org/api/current/#scala.Option

以下の仕様を満たす `Map[String, String]` から `Option[User]` に変換する `optUser` 関数を作るとします。

```scala
object Main extends App {
  case class User(email: String, name: String)

  /** Converts Map[String, String] to Option[User].
   * @param params
   * @return
   */
  def optUser(params: Map[String, String]): Option[User] = None

  assert(optUser(Map.empty[String, String]) ==
    None, "empty parameters")

  assert(optUser(Map("email" -> "foo@example.net")) ==
    None, "invalid keys")

  assert(optUser(Map("email" -> "", "name" -> "Foo")) ==
    None, "email is empty")

  assert(optUser(Map("email" -> "foo@example.net", "name" -> "")) ==
    None, "name is empty")

  assert(optUser(Map("email" -> "foo@example.net", "name" -> "Foo")) ==
    Some(User("foo@example.net", "Foo")), "valid parameters")
}
```

for-comprehension を使うと、以下のように簡潔に書けます。

```scala
def optUser(params: Map[String, String]): Option[User] = 
  for {
    email <- params.get("email") if !email.isEmpty
    name <- params.get("name") if !name.isEmpty
  } yield User(email, name)
```

for-comprehension は

* 先頭を `flatMap[B](f: (A) ⇒ Option[B]): Option[B]`
* 以降は `map[B](f: (A) ⇒ B): Option[B]`
* `if` は `withFilter(p: (A) ⇒ Boolean): WithFilter`

に置き換えてくれますので、以下のコードと同じです。

```scala
def optUser(params: Map[String, String]): Option[User] = 
  params.get("email").withFilter(!_.isEmpty) flatMap { email =>
    params.get("name").withFilter(!_.isEmpty) map { name =>
      User(email, name)
    }
  }
```

無理してパターンマッチで書くと、以下のような冗長なコードになってしまいます。

```scala
def optUser(params: Map[String, String]): Option[User] =
  params.get("email") match {
    case Some(email) => 
      if (!email.isEmpty) {
        params.get("name") match {
          case Some(name) =>
            if (!name.isEmpty) Some(User(email, name))
            else None
          case None => None
        }
      } else None
    case None => None
  }
```

Java 風なコードだと以下のようにも書けます。これはこれで良いような気もします（笑）

```scala
def optUser(params: Map[String, String]): Option[User] = {
  val email = params.getOrElse("email", "")
  val name = params.getOrElse("name", "")
  if (!email.isEmpty && !name.isEmpty) Some(User(email, name))
  else None
}
```

## Try

* http://www.scala-lang.org/api/current/#scala.util.Try

`Try` でも `flatMap` `map` を持っているので for-comprehension が使えます。

```scala:BankAccountApp.scala
import scala.util.{Try, Success, Failure}

object BankAccountApp {
  def withdraw(balance: Int, amount: Int): Try[Int] = {
    val x = balance - amount
    if (x >= 0) Success(x)
    else Failure(new Error("balance is less than " + amount))
  }

  def main(args: Array[String]) {
    val result: Try[String] = for {
      a <- Try(args(0).toInt)
      b <- Try(args(1).toInt)
      balance <- withdraw(a, b)
    } yield s"balance: ${balance}"
    println(result)
  }
}
```

上記のプログラムは

* `args(0)`: 残高
* `args(1)`: 引出額

を指定して引出後の残高を表示します。

* 正常終了なら `Success(message: String)`
* 途中で引数や残高の例外が発生した場合は `Failure(e: Throwable)`

が得られます。

```
$ sbt
> run-main BankAccountApp
Failure(java.lang.ArrayIndexOutOfBoundsException: 0)

> run-main BankAccountApp 100
Failure(java.lang.ArrayIndexOutOfBoundsException: 1)

> run-main BankAccountApp 100 foo
Failure(java.lang.NumberFormatException: For input string: "foo")

> run-main BankAccountApp 100 101
Failure(java.lang.Error: balance is less than 101)

> run-main BankAccountApp 100 100
Success(balance: 0)

> run-main BankAccountApp 100 50
Success(balance: 50)
```