-
Notifications
You must be signed in to change notification settings - Fork 7
7.4.2
Option을 사용하는 경우 None이 왜 값이 없는지에 대한 아무 정보도 전달하지 않는다는 한계가 있음을 지적한 바 있다. 그 대신 Either를 전달하는 것도 한가지 방법이다. Either는 두 가지 중 오직 한 가지만 담을 수 있는 컨테이너다. 다른 말로 하면 Option은 원소가 있거나 없는 경우를 표현하지만, Either는 한 원소 또는 다른 한 원소를 표현한다.
- Either는 매개변수를 둘 받는 매개변수화한 타입인 Either[+A, +B]다.
- Either도 봉인된 추상 클래스로, 두 가지 서브클래스 Left[A]와 Right[B]가 있다. 이 두 서브클래스는 두 가지 가능한 원소를 구별해준다.
- Left값에는 오류를 표현하는 값을 저장하고, Right 값에 일반적인 반환값을 저장한다.
// src/main/scala/progscala2/forcomps/for-eithers-good.sc
def positive(i: Int): Either[String,Int] =
if (i > 0) Right(i) else Left(s"nonpositive number $i")
for {
i1 <- positive(5).right
i2 <- positive(10 * i1).right
i3 <- positive(25 * i2).right
i4 <- positive(2 * i3).right
} yield (i1 + i2 + i3 + i4)
// Returns: scala.util.Either[String,Int] = Right(3805)
for {
i1 <- positive(5).right
i2 <- positive(-1 * i1).right // EPIC FAIL!
i3 <- positive(25 * i2).right
i4 <- positive(-2 * i3).right // EPIC FAIL!
} yield (i1 + i2 + i3 + i4)
// Returns: scala.util.Either[String,Int] = Left(nonpositive number -5)
이 버전은 타입을 제외화면 Option을 사용한 구현과 매우 비슷하다. 하지만 첫번째 발생한 오류만 볼수 있다.
- 예제에서는 right만을 호출햇는데 왜 그런지 아래에 설명.
scala> val l: Either[String, Int] = Left("boo")
l: Either[String, Int] = Left(boo)
scala> val r: Either[String, Int] = Right(12)
r: Eitehr[String, Int] = Right(12)
두 Either 값을 생성
- 첫 Either에는 Left
- 두번째 Either에는 Right
친군한 콤비네이터인 map, fold 등은 Either 자체에는 정의되어 있지 않다. 그러므로 Either.left나 Eitehr.right를 호출해야 한다. 이런 콤비네이터들은 함수 인자를 하나만 받는 반면, 여기서는 값이 Left인 경우와 Right인 경우에 각각 호출될 수 있도록 함수를 둘 지정 할 수 있어야 하기 때문이다. 그래서 대신 left, right 메서드를 사용해서 콤비네이터 메서드를 제공하는 '프로젝션(projection)'을 만든다.
scala>l.left
res0: scala.util.Either.LeftProjection[String, Int] = \
LeftProjection(Left(boo))
scala> l.right
res1: scala.util.Either.RightProjection[String, Int] = \
RightProjection(Left(boo))
scala>r.left
res2: scala.util.Either.LeftProjection[String, Int] =\
LeftProjection(Right(12))
scala>r.right
res2: scala.util.Either.LeftProjection[String, Int] =\
LeftProjection(Right(12))
projection에 map을 호출하면서 함수를 하나씩 넘겨보자.
scala> l.left.map(_.size)
res4: Either[Int, Int] = Left(3)
scala> r.left.map(_.size)
res5: Either[Int, Int] = Right(12)
scala> l.right.map(_.toDouble)
res4: Either[Int, Int] = Left(3)
scala> r.right.map(_.toDouble)
res5: Either[Int, Int] = Right(12.0)
Left 에만 값이 있는 Eitehr l은 left.map만 반응하고 Rigthr에만 값이 있는 r은 right.map에만 반응 한다.
Either는 매력적이긴 하지만, 그냥 무언가 잘못된 경우 예외를 던지는 것이 더쉽지 않나? 하지만 예외를 던지는 것은 참조 투명성을 해친다.
def addInts(s1: String, s2: String): Int = s1.toInt + s2.toInt
for {
i <- 1 to 3
j <- 1 to i
} println(s"$i+$j = ${addInts(i.toString, j.toString)}")
1+1 = 2
2+1 = 3
2+2 = 4
2+3 = 5
3+1 = 4
3+2 = 5
3+3 = 6
addInts는 Int로 변환할 수 없는 String을 전달받으면 예외를 던진다. 따라서 이 함수 호출은 항상 값으로 대치할 수는 없다.
def addInts(s1: String, s2: String): Int = s1.toInt + s2.toInt
for {
i <- 1 to 3
j <- 1 to i
} println(s"$i+$j = ${addInts(i.toString, j.toString)}")
def addInts2(s1: String, s2: String): Either[NumberFormatException,Int] =
try {
Right(s1.toInt + s2.toInt)
} catch {
case nfe: NumberFormatException => Left(nfe)
}
println(addInts2("1", "2"))
println(addInts2("1", "x"))
println(addInts2("x", "2"))
Either를 사용하면 다양한 오류 상황에서도 호출 스택의 제얼르 보장할 수 있다.