LoginSignup
2
2

More than 5 years have passed since last update.

FP in scala 勉強メモ [PartⅠ 第4章 例外を使わないエラー処理]

Posted at

第4章 例外を使わないエラー処理

全体的な考えた方としては、失敗や例外を通常の値で表し、エラー処理とリカバリに共通するパターンを高階関数として抽出する

評価されない式をサンク(thunk)と呼ぶ。

i: => Int構文で渡す高階関数はデフォルトキャッシュされない。つまり参照されるたびに実行される。
キャッシュしたい場合はlazy valで参照することにより、明示的にキャッシュする。

Scalaの非正格関数は引数を値渡しではなく名前渡しで受け取る。

EXERCISE 4.1

// Q
リスト4ー4のすべての関数をOptionで実装せよ。
各関数を実装するときに、その関数の意味と、それを使用するであろう状況について考えること。
どの関数をいつ使用するかについては、後ほど考察する。
ヒントは以下のとおり。
 - パターンマッチングを使用しても構わないが、
   mapとgetOrElseを除く関数はすべてパターンマッチングに頼らなくても実装できるはずである。
 -  map,flatMapの実装は、過多シグネチャの情報に基づいて充分に決定できるはずである。
 -  getOrElseはOptionがNoneのバアアイは、指定されたデフォルト値を返す。
 -  orElseは、1つ目のOptionが定義されている場合はそれを返し、そうでない場合は2つ目のOptionを返す。

// answer
  def map[B](f: A => B): Option[B] = this match {
    case None => None
    case Some(x) => Some(f(x))
  }

 def getOrElse[B>:A](default: => B): B = this match {
    case None => default
    case Some(x) => x  
  }

 def flatMap[B](f: A => Option[B]): Option[B] = map(f).getOrElse(None)

 def orElse[B>:A](ob: => Option[B]): Option[B] = this match {
    case None => ob
    case Some(x) => Some(x)  
  }

  def filter(f: A => Boolean): Option[A] = this match {
    case Some(x) if f(x) => Some(x)
    case None => None
  }

// 公式のanswer
 def map[B](f: A => B): Option[B] = this match {
    case None => None
    case Some(a) => Some(f(a))
  }

  def getOrElse[B>:A](default: => B): B = this match {
    case None => default
    case Some(a) => a
  }

  def flatMap[B](f: A => Option[B]): Option[B] =
    map(f) getOrElse None

  /*
  Of course, we can also implement `flatMap` with explicit pattern matching.
  */
  def flatMap_1[B](f: A => Option[B]): Option[B] = this match {
    case None => None
    case Some(a) => f(a)
  }

  def orElse[B>:A](ob: => Option[B]) =
    this map (Some(_)) getOrElse ob

  /*
  Again, we can implement this with explicit pattern matching.
  */
  def orElse_1[B>:A](ob: => Option[B]): Option[B] = this match {
    case None => ob
    case _ => this
  }

  def filter(f: A => Boolean): Option[A] = this match {
    case Some(a) if f(a) => this
    case _ => None
  }
  /*
  This can also be defined in terms of `flatMap`.
  */
  def filter_1(f: A => Boolean): Option[A] =
    flatMap(a => if (f(a)) Some(a) else None)

orElse,filterは諦めてmatch caseで実装

EXERCISE 4.2

// Q
flatMapをベースとしてvariance関数を実装せよ。
シーケンスの平均をm、シーケンスの各要素をxとすれば、分散はmath.pow(x - m, 2)の平均となる。
def variance(xs: Seq[Double]): Option[Double]

// answer


// 公式のanswer
  def mean(xs: Seq[Double]): Option[Double] =
    if (xs.isEmpty) None
    else Some(xs.sum / xs.length)

  def variance(xs: Seq[Double]): Option[Double] =
    mean(xs) flatMap (m => mean(xs.map(x => math.pow(x - m, 2))))

ぎぶ

EXERCISE 4.3

// Q
2項関数を使って、Option型の2つの値を結合する総称関数map2を記述せよ。
どちらかのOption値がNoneの場合は、戻り値もNoneになる。
シグネチャは以下のとおり。
def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C]

// answer
  def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = (a,b) match {
    case (Some(x), Some(y)) => Some(f(x,y))
    case _ => None
  }

// 公式のanswer
def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
    a flatMap (aa => b map (bb => f(aa, bb)))

公式のflatMap & mapのほうがかっこいい

EXERCISE 4.4

// Q
Optionのリストを1つのOptionにまとめるsequence関数を記述せよ。
新しいOptionには、元のリストに含まれているすべてのSome値のリストが含まれる。
元のリストにNoneが1つでも含まれていた場合、この関数の結果はNoneになる。
それ以外の場合は、すべての値のリストを含んだSomeになる。シグネチャは以下のとおり。
def sequence[A](a: List[Option[A]]): Option[List[A]]

// answer
 def sequence[A](a: List[Option[A]]): Option[List[A]] = {
    @annotation.tailrec
    def loop(l: List[Option[A]], x: Option[List[A]]): Option[List[A]] = {
      l match {
        case Some(h) :: List() => x.map(h :: _)
        case Some(h) :: t => loop(t, x.map(h :: _))
        case None :: _ => None
        case _ => println("_"); None
      }
    }
    loop(a, Some(List())).map(_.reverse)
  }

// 公式のanswer
  def sequence[A](a: List[Option[A]]): Option[List[A]] =
    a match {
      case Nil => Some(Nil)
      case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
    }
  /*
  It can also be implemented using `foldRight` and `map2`. The type annotation on `foldRight` is needed here; otherwise
  Scala wrongly infers the result type of the fold as `Some[Nil.type]` and reports a type error (try it!). This is an
  unfortunate consequence of Scala using subtyping to encode algebraic data types.
  */
  def sequence_1[A](a: List[Option[A]]): Option[List[A]] =
    a.foldRight[Option[List[A]]](Some(Nil))((x,y) => map2(x,y)(_ :: _))

2回走査していてイケてない。。

EXERCISE 4.5

// Q
traverse関数を実装せよ。mapとsequenceを利用すれば簡単だが、リストを1回だけ調べる、より効率のよい実装にすること。
要するに、traverseの観点からsequenceを実装すれば良い。

// answer
  def traverse[E,B,C](a: List[A])(f: A => Either[E,B]): Either[E,List[B]] =
    a match {
      case Nil => Right(Nil)
      case h :: t => (f(h) map2 traverse(t)(f))(_ :: _) 
  }

// 公式のanswer
  def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = 
    es match {
      case Nil => Right(Nil)
      case h::t => (f(h) map2 traverse(t)(f))(_ :: _)
    }

  def traverse_1[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = 
    es.foldRight[Either[E,List[B]]](Right(Nil))((a, b) => f(a).map2(b)(_ :: _))

4.4を参考にした。

EXERCISE 4.6

// Q
Right値を操作するmap,flatMap,orElse,map2をEitherに追加せよ。
trait Either[+E,+A] {
 def map[B](f: A => B): Either[E, B] = sys.error("todo")

 def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = sys.error("todo")

 def orElse[EE >: E, B >: A](b: => Either[EE, B]): Either[EE, B] = sys.error("todo")

 def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = sys.error("todo")
}
// answer
 trait Either[+E, +A] {
  def map[B](f: A => B): Either[E, B] = this match {
    case Right(x) => Right(f(x))
    case Left(e) => Left(e)
  }

  def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = this match {
    case Right(x) => f(x)
    case Left(e) => Left(e)
  }


  def orElse[EE >: E, B >: A](b: => Either[EE, B]): Either[EE, B] = this match {
    case Right(x) => this
    case Left(e) => b
  }

  def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): Either[EE, C] = for { x <- this; y <- b } yield f(x,y)

}
// 公式のanswer
sealed trait Either[+E,+A] {
 def map[B](f: A => B): Either[E, B] = 
   this match {
     case Right(a) => Right(f(a))
     case Left(e) => Left(e)
   }

 def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] =
   this match {
     case Left(e) => Left(e)
     case Right(a) => f(a)
   }
 def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] =
   this match {
     case Left(_) => b
     case Right(a) => Right(a)
   }
 def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C): 
   Either[EE, C] = for { a <- this; b1 <- b } yield f(a,b1)
}

map2が合ってたの気持ち良い^q^

EXERCISE 4.7

// Q
Eitherでsequenceとtraverseを実装せよ。
これらは、エラーが発生した場合に、最初に検出されたエラーを返すものとする。

def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]]
def traverse[E,A,B](as: List[A])(f: A => Either[E,B]): Either[E,List[B]]

// answer
  def traverse[E, A, B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
    es match {
      case Nil => Right(Nil)
      case h :: t => (f(h) map2 traverse(t)(f))(_ :: _)
    }

  def sequence[E, A](es: List[Either[E, A]]): Either[E, List[A]] =
    es match {
      case Nil => Right(Nil)
      case Left(h) :: _  => Left(h)
      case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
    }

// 公式のanswer
  def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = 
    es match {
      case Nil => Right(Nil)
      case h::t => (f(h) map2 traverse(t)(f))(_ :: _)
    }

  def traverse_1[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] = 
    es.foldRight[Either[E,List[B]]](Right(Nil))((a, b) => f(a).map2(b)(_ :: _))

  def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] = 
    traverse(es)(x => x)

sequenceかっこよい・・。

EXERCISE 4.8

// Q
リスト4-10の実装では、名前と年齢が両方とも無効であったとしても、map2はエラーを1つしか報告できない。
両方のエラーを報告するには、何を変更する必要があるか。
map2を変更するのか、それともmkPersonのシグネチャを変更するのか。
あるいは、新しい構造を追加して、この要件をEitherよりも効果的に満たす新しいデータ型を作成することは可能か。
そのデータ型では、orElse、travase、sequenceの振る舞いはどのように変化するか。

// 公式のanswer
There are a number of variations on Option and Either. If we want to accumulate multiple errors, a simple
approach is a new data type that lets us keep a list of errors in the data constructor that represents failures:
trait Partial[+A,+B]
case class Errors+A extends Partial[A,Nothing]
case class Success+B extends Partial[Nothing,B]
There is a type very similar to this called Validation in the Scalaz library. You can implement map, map2,
sequence, and so on for this type in such a way that errors are accumulated when possible (flatMap is unable to
accumulate errors--can you see why?). This idea can even be generalized further--we don't need to accumulate failing
values into a list; we can accumulate values using any user-supplied binary function.
It's also possible to use Either[List[E],_] directly to accumulate errors, using different implementations of
helper functions like map2 and sequence.


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