2015-12-05 第9回 関数型プログラミング勉強会(Scala)の振り返り
Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド (impress top gear)
- 作者: Paul Chiusano,Rúnar Bjarnason,株式会社クイープ
- 出版社/メーカー: インプレス
- 発売日: 2015/03/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (6件) を見る
第4章 例外を使わないエラー処理
前回の続きより
Either
一般的に左(Left)がエラー、右(Right)が正(right=正しいという意味)の結果。
注釈に
※11 Eitherは、新しいデータ型を定義するほどではないケースにおいて、 2つの可能性のうちの1つを符号化する目的でもよく使用される。 どちらかと言えば、こちらのほうが一般的である。
とあるが、意味がよくわからない。 ペアとして使うわけではないだろうし…という意見も出たが、結局はよくわからないということに… Eitherはエラーか正常な結果を返す以外に使うこともあるものなのか?
本書には、その例がいくつか含まれている。
ということなので後で具体的な例が出てくるのかもしれない。
Exercise 4.6
GitHubにあがってるもので特に問題はない
Exercise 4.7
GitHubにあがってるもので特に問題はないが、foldRightでも実装した参加者もいた。
GitHubの答えのtraverse関数を見てみる。
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))(_ :: _) }
ここで使われているmap2の定義は下記。
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)
試しに例外が発生しない場合を段階的に見ていくと
traverse(List(1,2,3))(a => Try(a *2)) = ↓ f = a => Try(a *2)とする traverse(Cons(1,Cons(2,Cons(3, Nil)))(f) ↓ f(1) map2 traverse(Cons(2,Cons(3, Nil)))(f)(_::_) ↓ f(1) map2 (f(2) map2 traverse(Cons(3, Nil))(f)(_::_))(_::_) ↓ f(1) map2 (f(2) map2 (f(3) map2 traverse(Nil)(f)(_::_))(_::_))(_::_) ↓ f(1) map2 (f(2) map2 (f(3) map2 Right(Nil)(_::_))(_::_))(_::_) ↓ f(1) map2 (f(2) map2 (Right(6) map2 Right(Nil)(_::_))(_::_))(_::_) ↓ f(1) map2 (f(2) map2 (Right(Cons(6, Nil)))(_::_))(_::_) ↓ f(1) map2 (Right(Cons(4, Cons(6, Nil))))(_::_) ↓ Right(Cons(2, Cons(4, Cons(6, Nil)))) ↓ Right(List(2,4,6))
となる。
例外が発生する場合を段階的にみると
traverse(List(0,1,2))(a => Try(10/a)) = ↓ f = a => Try(10/a) traverse(Cons(0,Cons(1,Cons(2, Nil)))(f) ↓ f(0) map2 traverse(Cons(1,Cons(2, Nil)))(f)(_::_) ↓ Left(Exception) map2 traverse(Cons(1,Cons(2, Nil)))(f)(_::_) ↓ Left(Exception) map2 Right(List(2,4))(_::_) ↓ Left(Exception)
となる。
Exercise 4.8
解答には説明だけあったので見ていく。
There are a number of variations on
Option
andEither
. 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:
Option
とEither
にはたくさんの種類がある。 もし複数のエラーをまとめたいのなら、単純な方法は 失敗を表すデータコンストラクタにエラーのリストを持つことができる 新しいデータ型をつくることである。
trait Partial[+A,+B] case class Errors[+A](get: Seq[A]) extends Partial[A,Nothing] case class Success[+B](get: B) extends Partial[Nothing,B]
There is a type very similar to this called
Validation
in the Scalaz library. You can implementmap
,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?).Scalazライブラリにほぼ同じ
Validation
という型がある。map
,map2
,sequence
, などをエラーを可能な限りまとるこの型で実装できる。 (flatMap
はエラーをまとめれない。--なぜでしょう?)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 likemap2
andsequence
.
map2
andsequence
など補助的な関数を別に実装したものを使って、Either[List[E],_]
で直接エラーをまとめることも可能だ。
Partialというのは「部分的」というような意味だと思うので、 ErrorsやSuccessなど例外処理のクラス定義をするのは抵抗があるが、どうなのだろう?
他の言語にはPartialというクラスが存在するらしい。 が、C#の場合、エラー処理としては使われてないもよう。
複数のエラーをまとめたい場合でEither[List[E],_]
の形式をとる場合、
現状のTry関数はエラーが発生した際にそのエラーをそのまま返しているのを新たに修正すれば、
確かにmap2
など関数を駆使すればEither[List[E],_]
を返す関数にすることも可能とは思える。
List[Left]
から Left[List]
にするような関数をつかって行うイメージ。