본문으둜 κ±΄λ„ˆλ›°κΈ°

🍭 Chapter 4: μ˜ˆμ™Έλ₯Ό μ΄μš©ν•˜μ§€ μ•Šμ€ 였λ₯˜ 처리

μ˜ˆμ™Έλ₯Ό λ˜μ§€λŠ” 것이 ν•˜λ‚˜μ˜ λΆ€μˆ˜ νš¨κ³Όμž„μ„ κ°„λ‹¨νžˆ μ–ΈκΈ‰ν–ˆλ‹€. μ‹€νŒ¨ 상황과 μ˜ˆμ™Έλ₯Ό λ³΄ν†΅μ˜ κ°’μœΌλ‘œ ν‘œν˜„ν•  수 있으며, 일반적인 였λ₯˜ 처리, 볡ꡬ νŒ¨ν„΄μ„ μΆ”μƒν™”ν™˜ κ³ μ°¨ ν•¨μˆ˜λ₯Ό μž‘μ„±ν•  수 μžˆλ‹€λŠ” 것이닀. 였λ₯˜λ₯Ό κ°’μœΌλ‘œμ„œ λŒλ €μ€€λ‹€λŠ” ν•¨μˆ˜μ  해법은 더 μ•ˆμ „ν•˜κ³  μ°Έμ‘° 투λͺ…성을 μœ μ§€ν•œλ‹€λŠ” μž₯점이 μžˆλ‹€. 계닀가 κ³ μ°¨ ν•¨μˆ˜ 덕뢄에 μ˜ˆμ™Έμ˜ 주된 이점인 였λ₯˜ 처리 λ…ΌλΌμ˜ 톡합도 μœ μ§€λœλ‹€.

πŸŽƒ μ˜ˆμ™Έμ˜ μž₯단점​

μ˜ˆμ™Έκ°€ μ™œ μ°Έμ‘° 투λͺ…성을 ν•΄μΉ κΉŒ? 그리고 그것이 μ™œ λ¬Έμ œκ°€ 될까?

// μ˜ˆμ™Έλ₯Ό λ˜μ§€κ³  λ°›κΈ°
def failingFn(i: Int): Int = {
val y: Int = throw new Exception("fail!")
try {
val x = 42 + 5
x + y
}
catch { case e: Exception => 43 }
}

// μ˜ˆμƒλŒ€λ‘œ 였λ₯˜ λ°œμƒ

yκ°€ 참쑰에 투λͺ…ν•˜μ§€ μ•ŠμŒμ„ 증λͺ…ν•  수 μžˆλ‹€. μž„μ˜μ˜ μ°Έμ‘° 투λͺ… ν‘œν˜„μ‹μ„ 그것이 μ§€μΉ­ν•˜λŠ” κ°’μœΌλ‘œ μΉ˜ν™˜ν•΄λ„ ν”„λ‘œκ·Έλž¨μ˜ μ˜λ―Έκ°€ λ³€ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 점을 κΈ°μ–΅ν•  것이닀. 만일 x + y의 yλ₯Ό throw new Exception("fail!")둜 μΉ˜ν™˜ν•˜λ©΄ κ·Έμ „κ³ΌλŠ” λ‹€λ₯Έ κ²°κ³Όκ°€ λ‚˜μ˜¨λ‹€. μ΄μ œλŠ” μ˜ˆμ™Έλ₯Ό μž‘μ•„μ„œ 43을 λŒλ €μ£ΌλŠ” try 블둝 μ•ˆμ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜κΈ° 떄문이닀.

def failingFn2(i: Int): Int = {
try {
val x = 42 + 5
x + ((throw new Exception("fail!")): Int)
}
catch { case e: Exception => 43 }
}

// 43

μ°Έμ‘° 투λͺ…μ„±μ΄λΌλŠ” 것을, 참쑰에 투λͺ…ν•œ ν‘œν˜„μ‹μ˜ μ˜λ―ΈλŠ” λ¬Έλ§₯(context)에 μ˜μ‘΄ν•˜μ§€ μ•ŠμœΌλ©° μ§€μ—­μ μœΌλ‘œ μΆ”λ‘ ν•  수 μžˆμ§€λ§Œ 참쑰에 투λͺ…ν•˜μ§€ μ•Šμ€ ν‘œν˜„μ‹μ˜ μ˜λ―ΈλŠ” λ¬Έλ§₯에 의쑴적이고 μ’€ 더 μ „μ—­μ˜ 좔둠이 ν•„μš”ν•˜λ‹€λŠ” κ²ƒμœΌλ‘œ 이해해도 될 것이닀.

μ˜ˆμ™Έμ˜ 주된 문제 두 κ°€μ§€λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • 방금 λ…Όμ˜ν–ˆλ“―μ΄, μ˜ˆμ™ΈλŠ” μ°Έμ‘° 투λͺ…성을 μœ„λ°˜ν•˜κ³  λ¬Έλ§₯ μ˜μ‘΄μ„±μ„ λ„μž…ν•œλ‹€. λ”°λΌμ„œ μΉ˜ν™˜ λͺ¨ν˜•μ˜ κ°„λ‹¨ν•œ 좔둠이 λΆˆκ°€λŠ₯해지고 μ˜ˆμ™Έμ— κΈ°μ΄ˆν•œ ν˜Όλž€μŠ€λŸ¬μš΄ μ½”λ“œκ°€ λ§Œλ“€μ–΄μ§„λ‹€. 이것이 μ˜ˆμ™Έλ₯Ό 였λ₯˜ μ²˜λ¦¬μ—λ§Œ μ‚¬μš©ν•˜κ³  νλ¦„μ˜ μ œμ–΄μ—λŠ” μ‚¬μš©ν•˜μ§€ 말아야 ν•œλ‹€λŠ” μ†μ„€μ˜ 근원이닀.
  • μ˜ˆμ™ΈλŠ” ν˜•μ‹μ— μ•ˆμ „ν•˜μ§€ μ•Šλ‹€. failingFn의 ν˜•μ‹μΈ Int => Int만 λ³΄κ³ λŠ” 이 ν•¨μˆ˜κ°€ 예였λ₯Ό 던질 수 μžˆλ‹€λŠ” 사싀을 μ „ν˜€ μ•Œ 수 μ—†μœΌλ©°, κ·Έλž˜μ„œ μ»΄νŒŒμΌλŸ¬λŠ” failingFn의 ν˜ΈμΆœμžμ—κ²Œ κ·Έ μ˜ˆμ™Έλ“€μ„ μ²˜λ¦¬ν•˜λŠ” 방식을 κ²°μ •ν•˜λΌκ³  κ°•μ œν•  수 μ—†λ‹€.

이런 단점듀이 μ—†μœΌλ©΄μ„œλ„ μ˜ˆμ™Έμ˜ κ°€μž₯ μž₯점인 였λ₯˜ 처리 λ…Όλ¦¬μ˜ 톡합과 쀑앙집쀑화λ₯Ό μœ μ§€ν•˜λŠ” λŒ€μ•ˆμ΄ 있으면 쒋을 것이닀. μ§€κΈˆλΆ€ν„° μ†Œκ°œν•˜λŠ” λŒ€μ•ˆ 기법은 "μ˜ˆμ™Έλ₯Ό λ˜μ§€λŠ” λŒ€μ‹ , μ˜ˆμ™Έμ μΈ 쑰건을 λ°œμƒν–ˆμŒμ„ λœ»ν•˜λŠ” 값을 λŒλ €μ€€λ‹€"λΌλŠ” 였래된 μ°©μ•ˆμ— κΈ°μ΄ˆν•œλ‹€. 단, 이 κΈ°λ²•μ—μ„œλŠ” 였λ₯˜ λΆ€ν˜Έλ₯Ό 직접 λŒλ €μ£ΌλŠ” λŒ€μ‹  그런 '미리 μ •μ˜ν•΄ λ‘˜ 수 μžˆλŠ” κ°’λ“€'을 λŒ€ν‘œν•˜λŠ” μƒˆλ‘œμš΄ 일반적 ν˜•μ‹μ„ λ„μž…ν•˜κ³ , 였λ₯˜μ˜ μ²˜λ¦¬μ™€ μ „νŒŒμ— κ΄€ν•œ 곡톡적인 νŒ¨ν„΄λ“€μ„ κ³ μ°¨ ν•¨μˆ˜λ“€μ„ μ΄μš©ν•΄μ„œ μΊ‘μŠν™”ν•œλ‹€. μš°λ¦¬κ°€ μ‚¬μš©ν•˜λŠ” 였λ₯˜ 처리 μ „λž΅μ€ ν˜•μ‹μ— μ™„μ „νžˆ μ•ˆμ „ν•˜λ©°, μ΅œμ†Œν•œμ˜ ꡬ문적 μž‘μŒμœΌλ‘œλ„ 슀칼라의 ν˜•μ‹ 점검기λ₯Ό 도움을 λ°›μ•„μ„œ μ‹€μˆ˜λ₯Ό 미리 λ°œκ²¬ν•  수 μžˆλ‹€.

πŸŽƒ μ˜ˆμ™Έμ˜ κ°€λŠ₯ν•œ λŒ€μ•ˆλ“€β€‹

μ˜ˆμ™Έ λŒ€μ‹  μ‚¬μš©ν•  λ§Œν•œ μ—¬λŸ¬ 가지 접근방식을 μ‘°μ‚¬ν•΄λ³΄μž. λ‹€μŒμ€ λͺ©λ‘μ˜ 평균을 κ³„μ‚°ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€. 빈 λͺ©λ‘μ— λŒ€ν•΄μ„œλŠ” 평균이 μ •μ˜λ˜μ§€ μ•ŠλŠ”λ‹€.

def mean(xs: Seq[Double]): Double = 
if (xs.isEmpty)
throw new ArithmeticException("mean of empty list!")
else xs.sum / xs.length

mean에 λŒ€ν•΄ μ˜ˆμ™Έ λŒ€μ‹  μ‚¬μš©ν•  수 μžˆλŠ” λŒ€μ•ˆ λͺ‡ 가지λ₯Ό μ‚΄νŽ΄λ³΄μž.

첫 번째 λŒ€μ•ˆμ€ Double ν˜•μ‹μ˜ κ°€μ§œ 값을 λŒλ €μ£ΌλŠ” 것이닀. λͺ¨λ“  κ²½μš°μ— κ·Έλƒ₯ xs.sum / xs.lengthλ₯Ό λŒλ €μ€€λ‹€λ©΄ 빈 λͺ©λ‘μ— λŒ€ν•΄μ„œλŠ” 0.0 / 0.0을 돌렀주게 λ˜λŠ”λ°, μ΄λŠ” Double.NaN이닀. μ•„λ‹ˆλ©΄ λ‹€λ₯Έ μ–΄λ–€ 경계 값을 λŒλ €μ€„ μˆ˜λ„ μžˆκ² λ‹€. 상황에 λ”°λΌμ„œλŠ” μ›ν•˜λŠ” ν˜•μ‹μ˜ κ°’ λŒ€μ‹  null을 λŒλ €μ€„ μˆ˜λ„ μžˆλ‹€. 이런 λΆ€λ₯˜μ˜ 접근방식은 μ˜ˆμ™Έ κΈ°λŠ₯이 μ—†λŠ” μ–Έμ–΄μ—μ„œ 였λ₯˜λ₯Ό μ²˜λ¦¬ν•˜λŠ” 데 ν”νžˆ 쓰인닀. κ·ΈλŸ¬λ‚˜ 이 μ±…μ—μ„œλŠ” 이런 접근방식을 κ±°λΆ€ν•œλ‹€. μ΄μœ λŠ” μ—¬λŸ¬ 가지이닀.

  • 였λ₯˜κ°€ μ†Œλ¦¬ 없이 μ „νŒŒλ  수 μžˆλ‹€.
  • μ‹€μˆ˜μ˜ 여지가 λ§Žλ‹€λŠ” 점 외에, ν˜ΈμΆœν•˜λŠ” μͺ½μ— ν˜ΈμΆœμžκ°€ 'μ§„μ§œ' κ²°κ³Όλ₯Ό λ°›μ•˜λŠ”μ§€ μ κ²€ν•˜λŠ” λͺ…μ‹œμ  ifλ¬Έλ“€λ‘œ κ΅¬μ„±λœ νŒμ— λ°•νžŒ μ½”λ“œκ°€ μƒλ‹Ήνžˆ λŠ˜μ–΄λ‚œλ‹€.
  • λ‹€ν˜•μ  μ½”λ“œμ—λŠ” μ μš©ν•  수 μ—†λ‹€. 좜λ ₯ ν˜•μ‹μ— λ”°λΌμ„œλŠ” κ·Έ ν˜•μ‹μ˜ 경계 값을 κ²°μ •ν•˜λŠ” 것이 λΆˆκ°€λŠ₯ν•  μˆ˜λ„ μžˆλ‹€.
  • ν˜ΈμΆœμžμ—κ²Œ νŠΉλ³„ν•œ λ°©μΉ¨μ΄λ‚˜ 호좜 κ·œμ•½μ„ μš”κ΅¬ν•œλ‹€. mean ν•¨μˆ˜λ₯Ό μ œλŒ€λ‘œ μ‚¬μš©ν•˜λ €λ©΄ ν˜ΈμΆœμžκ°€ κ·Έλƒ₯ mean을 ν˜ΈμΆœν•΄μ„œ κ·Έ κ²°κ³Όλ₯Ό μ‚¬μš©ν•˜λŠ” 것 μ΄μƒμ˜ μž‘μ—…μ„ μˆ˜ν–‰ν•΄μ•Ό ν•œλ‹€. ν•¨μˆ˜μ— 이런 νŠΉλ³„ν•œ 방침을 λΆ€μ—¬ν•˜λ©΄, λͺ¨λ“  인수λ₯Ό κ· μΌν•œ λ°©μ‹μœΌλ‘œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λŠ” κ³ μ°¨ ν•¨μˆ˜μ— μ „λ‹¬ν•˜κΈ° μ–΄λ €μ›Œμ§„λ‹€.

또 λ‹€λ₯Έ λŒ€μ•ˆμ€ ν•¨μˆ˜κ°€ μž…λ ₯을 μ²˜λ¦¬ν•  수 μ—†λŠ” 상황에 μ²˜ν–ˆμ„ λ•Œ 무엇을 ν•΄μ•Ό ν•˜λŠ”μ§€ λ§ν•΄μ£ΌλŠ” 인수λ₯Ό ν˜ΈμΆœμžκ°€ μ§€μ •ν•˜λŠ” 것이닀.

def mean_1(xs: IndexedSeq[Double], onEmpty: Double): Double = 
if (xs.isEmpty) onEmpty
else xs.sum / xs.length

μ΄λ ‡κ²Œ ν•˜λ©΄ mean은 λΆ€λΆ„ ν•¨μˆ˜κ°€ μ•„λ‹Œ μ™„μ „ ν•¨μˆ˜κ°€ λœλ‹€. κ·ΈλŸ¬λ‚˜ μ—¬κΈ°μ—λŠ” κ²°κ³Όκ°€ μ •μ˜λ˜μ§€ μ•Šμ€ 경우의 처리 방식을 ν•¨μˆ˜μ˜ 직접적인 ν˜ΈμΆœμžκ°€ μ•Œκ³  μžˆμ–΄μ•Ό ν•˜κ³  그런 κ²½μš°μ—λ„ 항상 ν•˜λ‚˜μ˜ Double 값을 λŒλ €μ£Όμ–΄μ•Ό ν•œλ‹€λŠ” 단점이 μžˆλ‹€.

μš°λ¦¬μ—κ²Œ ν•„μš”ν•œ 것은, μ •μ˜λ˜μ§€ μ•Šμ€ κ²½μš°κ°€ κ°€μž₯ μ λ‹Ήν•œ μˆ˜μ€€μ—μ„œ μ²˜λ¦¬λ˜λ„λ‘ κ·Έ 처리 λ°©μ‹μ˜ 결정을 λ―Έλ£° 수 있게 ν•˜λŠ” 방법이닀.

πŸŽƒ Option 자료 ν˜•μ‹β€‹

해법은, ν•¨μˆ˜κ°€ 항상 닡을 내지 λͺ»ν•œλ‹€λŠ” 점을 λ°˜ν™˜ ν˜•μ‹μ„ ν†΅ν•΄μ„œ λͺ…μ‹œμ μœΌλ‘œ ν‘œν˜„ν•˜λŠ” 것이닀. 이λ₯Ό, 였λ₯˜ 처리 μ „λž΅μ„ ν˜ΈμΆœμžμ—κ²Œ λ―Έλ£¨λŠ” κ²ƒμœΌλ‘œ 생각해도 λœλ‹€. 이λ₯Ό μœ„ν•΄ Optionμ΄λΌλŠ” μƒˆλ‘œμš΄ ν˜•μ‹μ„ λ„μž…ν•œλ‹€.

sealed trait Option[+A]
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

Optionμ—λŠ” 두 개의 경우 문이 μžˆλ‹€. Option을 μ •μ˜ν•  수 μžˆλŠ” κ²½μš°μ—λŠ” Some이 되고, μ •μ˜ν•  수 μ—†λŠ” κ²½μš°μ—λŠ” None이 λœλ‹€.

이제 Option을 μ΄μš©ν•΄μ„œ mean을 κ΅¬ν˜„ν•˜λ©΄ λ‹€μŒκ³Ό 같은 μ½”λ“œκ°€ λœλ‹€.

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

μ΄μ œλŠ” 이 ν•¨μˆ˜μ˜ κ²°κ³Όκ°€ 항상 μ •μ˜λ˜μ§€ μ•ŠλŠ”λ‹€λŠ” 사싀이 ν•¨μˆ˜μ˜ λ°˜ν™˜ 방식에 λ°˜μ˜λ˜μ–΄ μžˆλ‹€. ν•¨μˆ˜κ°€ 항상 μ„ μ–Έλœ λ°˜ν™˜ ν˜•μ‹μ˜ κ²°κ³Όλ₯Ό λŒλ €μ£Όμ–΄μ•Ό ν•œλ‹€λŠ” 점은 μ—¬μ „ν•˜λ―€λ‘œ, mean은 이제 ν•˜λ‚˜μ˜ μ™„μ „ ν•¨μˆ˜μ΄λ‹€. 이 ν•¨μˆ˜λŠ” μž…λ ₯ ν˜•μ‹μ˜ λͺ¨λ“  값에 λŒ€ν•΄ μ •ν™•νžˆ ν•˜λ‚˜μ˜ 좜λ ₯ ν˜•μ‹ 값을 λŒλ €μ€€λ‹€.

🎈 Option의 μ‚¬μš© νŒ¨ν„΄β€‹

λΆ€λΆ„ ν•¨μˆ˜λŠ” ν”„λ‘œκ·Έλž˜λ°μ—μ„œ ν”νžˆ λ³Ό 수 있으며, FPμ—μ„œλŠ” 그런 뢀뢄성을 ν”νžˆ Option 같은 자료 ν˜•μ‹μœΌλ‘œ μ²˜λ¦¬ν•œλ‹€.

Option이 νŽΈλ¦¬ν•œ μ΄μœ λŠ”, 였λ₯˜ 처리의 곡톡 νŒ¨ν„΄μ„ κ³ μ°¨ ν•¨μˆ˜λ“€μ„ μ΄μš©ν•΄μ„œ μΆ”μΆœν•¨μœΌλ‘œμ¨ μ˜ˆμ™Έ 처리 μ½”λ“œμ— ν”νžˆ μˆ˜λ°˜λ˜λŠ” νŒμ— λ°•νžŒ μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€λŠ” 점이닀.

Option에 λŒ€ν•œ 기본적인 ν•¨μˆ˜λ“€β€‹

Option은 μ΅œλŒ€ ν•˜λ‚˜μ˜ μ›μ†Œλ₯Ό 담을 수 μžˆλ‹€λŠ” 점을 μ œμ™Έν•˜λ©΄ List와 λΉ„μŠ·ν•˜λ‹€.

// Option 자료 ν˜•μ‹
trait Option[+A] {
// 만일 Option이 None이 μ•„λ‹ˆλ©΄ fλ₯Ό 적용
def map[B](f: A => B): Option[B]
// 만일 Option이 None이 μ•„λ‹ˆλ©΄ f(μ‹€νŒ¨ν•  수 있음)λ₯Ό μ μš©ν•œλ‹€.
def flatMap[B](f: A => Option[B]): Option[B]
// B >: AλŠ” B ν˜•μ‹ λ§€κ°œλ³€μˆ˜κ°€ λ°˜λ“œμ‹œ A의 μƒμœ„ν˜•μ‹μ΄μ–΄μ•Ό 함을 μ˜λ―Έν•œλ‹€.
def getOrElse[B >: A](default: => B): B
// obλŠ” ν•„μš”ν•œ κ²½μš°μ—λ§Œ ν‰κ°€ν•œλ‹€.
def orElse[B >: A](ob: => Option[B]): Option[B]
// 값이 fλ₯Ό λ§Œμ‘±ν•˜μ§€ μ•ŠμœΌλ©΄ Some을 None으둜 λ³€ν™˜ν•œλ‹€.
def filter(f: A => Boolean): Option[A]
}

기본적인 Option ν•¨μˆ˜λ“€μ˜ μš©λ‘€β€‹

ν•˜λ‚˜μ˜ Option에 λŒ€ν•΄ λͺ…μ‹œμ μΈ νŒ¨ν„΄ 뢀함을 μ μš©ν•  μˆ˜λ„ μžˆμ§€λ§Œ, 거의 λͺ¨λ“  κ²½μš°μ— μœ„μ— λ§ν•œ κ³ μ°¨ ν•¨μˆ˜λ“€μ„ μ‚¬μš©ν•˜κ²Œ λœλ‹€.

map ν•¨μˆ˜λŠ” Option μ•ˆμ˜ κ²°κ³Όλ₯Ό λ³€ν™˜ν•˜λŠ” 데 μ‚¬μš©ν•  수 μžˆλ‹€. 이λ₯Ό 였λ₯˜κ°€ λ°œμƒν•˜μ§€ μ•Šμ•˜λ‹€λŠ” κ°€μ •ν•˜μ—μ„œ 계산을 μ§„ν–‰ν•˜λŠ” κ²ƒμœΌλ‘œ 생각해도 될 것이닀. λ˜ν•œ, μ΄λŠ” 였λ₯˜ 처리λ₯Ό λ‚˜μ€‘μ˜ μ½”λ“œμ— λ―Έλ£¨λŠ” μˆ˜λ‹¨μ΄κΈ°λ„ ν•˜λ‹€.

case class Employee(name: String, department: String)

def lookupByName(name: String): Option[Employee] = ...

val joeDepartment: Option[String] =
lookupByName("Joe").map(_.department)

이 μ˜ˆμ—μ„œ lookupByName("Joe")λŠ” Option[Employee]λ₯Ό λŒλ €μ€€λ‹€. 그것을 map으둜 λ³€ν™˜ν•˜λ©΄ Joeκ°€ μ†ν•œ λΆ€μ„œμ˜ 이름을 λœ»ν•˜λŠ” Option[String]이 λ‚˜μ˜¨λ‹€. μ—¬κΈ°μ„œ lookupBtName("Joe")의 κ²°κ³Όλ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ κ²€ν•˜μ§€ μ•ŠμŒμ„ μ£Όλͺ©ν•˜κΈ° λ°”λž€λ‹€. κ·Έλƒ₯ 였λ₯˜κ°€ μ „ν˜€ λ°œμƒν•˜μ§€ μ•Šμ•˜λ‹€λŠ” 듯이 map의 인수 μ•ˆμ—μ„œ 계산을 계속 μ§„ν–‰ν•œλ‹€. 만일 lockupByName("Joe")κ°€ None을 λŒλ €μ£Όμ—ˆλ‹€λ©΄ κ³„μ‚°μ˜ λ‚˜λ¨Έμ§€ 뢀뢄이 μ·¨μ†Œλ˜μ–΄μ„œ map은 _.department ν•¨μˆ˜λ₯Ό μ „ν˜€ ν˜ΈμΆœν•˜μ§€ μ•ŠλŠ”λ‹€.

flatMap을 μ΄μš©ν•˜λ©΄ μ—¬λŸ¬ λ‹¨κ³„λ‘œ 이루어진 계산을 μˆ˜ν–‰ν•˜λ˜ μ–΄λ–€ 단계라도 μ‹€νŒ¨ν•˜λ©΄ κ·Έ μ¦‰μ‹œ λ‚˜λ¨Έμ§€ λͺ¨λ“  과정이 μ·¨μ†Œλ˜λŠ” λ°©μ‹μœΌλ‘œ μˆ˜ν–‰ν•  수 μžˆλ‹€. μ΄λŠ” None.flatMap(f)κ°€ fλ₯Ό μ‹€ν–‰ν•˜μ§€ μ•Šκ³  μ¦‰μ‹œ None을 돌렀주기 λ•Œλ¬Έμ΄λ‹€.

filterλŠ” 성곡적인 값이 주어진 μˆ μ–΄μ™€ λΆ€ν•©ν•˜μ§€ μ•Šμ„ λ•Œ 성곡을 μ‹€νŒ¨λ‘œ λ³€ν™˜ν•˜λŠ”λ° μ‚¬μš©ν•  수 μžˆλ‹€. ν”ν•œ μ‚¬μš© νŒ¨ν„΄μ€ map, flatMap, filter의 μž„μ˜μ˜ 쑰합을 μ΄μš©ν•΄μ„œ Option을 λ³€ν™˜ν•˜κ³  제일 λμ—μ„œ getOrElseλ₯Ό μ΄μš©ν•΄μ„œ 였λ₯˜ 처리λ₯Ό μˆ˜ν–‰ν•˜λŠ” 것이닀.

val dept: String =
lookupByName("Joe").
map(_.dept).
filter(_ != "Accounting").
getOrElse("Default Dept")

μ—¬κΈ°μ„œ getOrElseλŠ” Option[String]을 String으둜 λ³€ν™˜ν•˜λ˜ "Joe"λΌλŠ” ν‚€κ°€ Map에 μ‘΄μž¬ν•˜μ§€ μ•Šκ±°λ‚˜ Joe의 λΆ€μ„œκ°€ "Accounting"인 κ²½μš°μ—λŠ” κΈ°λ³Έ λΆ€μ„œ 이름을 λŒλ €μ£ΌλŠ” 역할을 ν•œλ‹€.

였λ₯˜λ₯Ό λ³΄ν†΅μ˜ κ°’μœΌλ‘œ 돌렀주면 μ½”λ“œλ₯Ό μ§œκΈ°κ°€ νŽΈν•΄μ§€λ©°, κ³ μ°¨ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ μ˜ˆμ™Έμ˜ 주된 μž₯점인 였λ₯˜ 처리 λ…Όλ¦¬μ˜ 톡합과 격리도 μœ μ§€ν•  수 μžˆλ‹€. κ³„μ‹Όμ˜ 맀 λ‹¨κ³„λ§ˆλ‹€ None을 점검할 ν•„μš”κ°€ μ—†μŒμ„ μ£Όλͺ©ν•˜κΈ° λ°”λž€λ‹€.

🎈 μ˜ˆμ™Έ 지ν–₯적 API의 Option ν•©μ„±κ³Ό μŠΉκΈ‰, 감싸기​

일단 Option을 μ‚¬μš©ν•˜κΈ° μ‹œμž‘ν•˜λ©΄ μ½”λ“œ 기반 전체에 Option이 λ²ˆμ§€κ²Œ λ˜λ¦¬λΌλŠ” μ„±κΈ‰ν•œ 결둠을 λ‚΄λ¦¬λŠ” λ…μžλ„ μžˆμ„ 것이닀. 즉, Option을 λ°›κ±°λ‚˜ λŒλ €μ£ΌλŠ” λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” λͺ¨λ“  μ½”λ“œλ₯Ό Someμ΄λ‚˜ None을 μ²˜λ¦¬ν•˜λ„λ‘ μˆ˜μ •ν•΄μ•Ό ν•œλ‹€κ³  μΆ”μΈ‘ν•  수 μžˆλ‹€. κ·ΈλŸ¬λ‚˜ μ‹€μ œλ‘œλŠ” 그런 뢀담을 질 ν•„μš”κ°€ μ—†λ‹€. λ³΄ν†΅μ˜ ν•¨μˆ˜λ₯Ό Option에 λŒ€ν•΄ μž‘μš©ν•˜λŠ” ν•¨μˆ˜λ‘œ μŠΉκΈ‰μ‹œν‚¬ 수 있기 λ•Œλ¬Έμ΄λ‹€.

def lift[A, B](f: A => B): Option[A] => Option[B] = _ map f

μ΄λŸ¬ν•œ liftκ°€ 있으면 μ§€κΈˆκΉŒμ§€ λ‚˜μ˜¨ κ·Έ μ–΄λ–€ ν•¨μˆ˜λΌλ„ ν•œ Option κ°’μ˜ λ¬Έλ§₯ μ•ˆμ—μ„œ μž‘μš©ν•˜λ„λ‘ λ³€ν™˜ν•  수 μžˆλ‹€.

val abs0: Option[Double] => Option[Double] = lift(math.abs)

μœ„ μ˜ˆμ—μ„œ 보듯이, 선택적 값에 μž‘μš©ν•˜λŠ” math.abs ν•¨μˆ˜λ₯Ό 직접 μž‘μ„±ν•  ν•„μš”κ°€ μ—†λ‹€. κ·Έλƒ₯ κ·Έ ν•¨μˆ˜λ₯Ό Option λ¬Έλ§₯으둜 μŠΉκ²©μ‹œν‚€λ©΄ λœλ‹€. μ΄λŸ¬ν•œ μŠΉκΈ‰μ€ λͺ¨λ“  ν•¨μˆ˜μ— κ°€λŠ₯ν•˜λ‹€.

πŸŽƒ Either 자료 ν˜•μ‹β€‹

Option이 μ˜ˆμ™Έμ μΈ 쑰건이 λ°œμƒν–ˆμ„ λ•Œ 무엇이 잘λͺ»λ˜μ—ˆλŠ”지에 λŒ€ν•œ 정보λ₯Ό μ œκ³΅ν•˜μ§€ λͺ»ν•œλ‹€λŠ” 단점이 μžˆλ‹€. μ‹€νŒ¨ μ‹œ 이 ν˜•μ‹μ€ κ·Έλƒ₯ μœ νš¨ν•œ 값이 μ—†μŒμ„ λœ»ν•˜λŠ” None을 λŒλ €μ€„ 뿐이닀. κ·ΈλŸ¬λ‚˜ κ·Έ μ™Έμ˜ 것이 ν•„μš”ν•  λ•Œλ„ μžˆλ‹€.

μ‹€νŒ¨μ— κ΄€ν•΄ μ•Œκ³  싢은 정보가 μ–΄λ–€ 것이든 그것을 λΆ€ν˜Έν™”ν•˜λŠ” 자료 ν˜•μ‹μ„ λ§Œλ“œλŠ” 것은 λ¬Όλ‘  κ°€λŠ₯ν•˜λ‹€. κ·Έλƒ₯ μ‹€νŒ¨κ°€ λ°œμƒν–ˆμŒμ„ μ•Œλ©΄ μΆ©λΆ„ν•œ λ•Œμ—λŠ” Option을 μ‚¬μš©ν•˜λ©΄ λœλ‹€. κ·Έ μ™Έμ˜ κ²½μš°μ—λŠ” μ’€ 더 λ§Žμ€ 정보가 ν•„μš”ν•˜λ‹€.

sealed trait Either[+E, +A]
case class Left[+E](value: E) extends Either[E, Nothing]
case class Right[+A](value: A) extends Either[Nothing, A]

Option 처럼 Either도 caseκ°€ 두 κ°œλΏμ΄λ‹€. Option과의 본질적인 μ°¨μ΄λŠ”, 두 경우 λͺ¨λ‘ 값을 κ°€μ§„λ‹€λŠ” 것이닀. μ•„μ£Ό κ°œκ΄„μ μœΌλ‘œ λ§ν•˜μžλ©΄, Either 자료 ν˜•μ‹μ€ λ‘˜ 쀑 ν•˜λ‚˜μΌ 수 μžˆλŠ” 값듀을 λŒ€ν‘œν•œλ‹€. 이 ν˜•μ‹μ€ 두 ν˜•μ‹μ˜ 뢄리합집합(μ„œλ‘œ μ†Œ 합집합)이라 ν•  수 μžˆλ‹€. 이 ν˜•μ‹μ„ 성곡 λ˜λŠ” μ‹€νŒ¨λ₯Ό λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©ν•  λ•Œμ—λŠ”, Right μƒμ„±μžλ₯Ό 성곡을 λ‚˜νƒ€λ‚΄λŠ” 데 μ‚¬μš©ν•˜κ³ (였λ₯Έμͺ½μ΄ μ˜³μ€ μͺ½) LeftλŠ” μ‹€νŒ¨μ— μ‚¬μš©ν•œλ‹€. μ™Όμͺ½ ν˜•μ‹ λ§€κ°œλ³€μˆ˜μ˜ μ΄λ¦„μœΌλ‘œλŠ” error(였λ₯˜)λ₯Ό μ˜λ―Έν•˜λŠ” Eλ₯Ό μ‚¬μš©ν•œλ‹€.

그럼 mean 예제λ₯Ό λ‹€μ‹œ 보자. μ΄λ²ˆμ—λŠ” μ‹€νŒ¨μ˜ κ²½μš°μ— String을 λŒλ €μ€€λ‹€.

def mean(xs: IndexedSeq[Double]): Either[String, Double] =
if (xs.isEmpty)
Left("mean of empty list!")
else
Right(xs.sum / xs.length)

였λ₯˜μ— λŒ€ν•œ μΆ”κ°€ 정보, 이λ₯Όν…Œλ©΄ μ†ŒμŠ€ μ½”λ“œμ—μ„œ 였λ₯˜κ°€ λ°œμƒν•œ μœ„μΉ˜λ₯Ό μ•Œ 수 μžˆλŠ” μŠ€νƒ 좔적 정보가 있으면 νŽΈλ¦¬ν•œ κ²½μš°κ°€ μ’…μ’… μžˆλ‹€. 그런 경우 Either의 Leftμͺ½μ—μ„œ κ·Έλƒ₯ μ˜ˆμ™Έλ₯Ό 돌렀주면 λœλ‹€.

def safeDiv(x: Int, y: Int): Either[Exception, Int] =
try Right(x / y)
catch { case e: Exception => Left(e) }

Optionμ—μ„œ ν–ˆλ“―μ΄, λ˜μ Έμ§„ μ˜ˆμ™Έλ₯Ό κ°’μœΌλ‘œ λ³€ν™˜ν•œλ‹€λŠ” μ΄λŸ¬ν•œ κ³΅ν†΅μ˜ νŒ¨ν„΄μ„ μΆ”μΆœν•œ ν•¨μˆ˜ Tryλ₯Ό μž‘μ„±ν•΄ 보자.

def Try[A](a: => A): Either[Exception, A] =
try Right(a)
catch { case e: Exception => Left(e) }

μ΄λŸ¬ν•œ μ •μ˜λ“€μ΄ 있으면 Eitherλ₯Ό for-함좕에 μ‚¬μš©ν•  수 μžˆμŒμ„ μ£Όλ¬™ν•˜μž.

def parseInsuranceRateQuote(
age: String,
numberOfSpeedTickets: String
): Either[Exception. Double] =
for {
a <- Try { age.toInt }
tickets <- Try { numberOfSpeedingTickets.toInt }
} yield insuranceRateQuote(a, tickets)

μ΄μ œλŠ” μ‹€νŒ¨ μ‹œ κ·Έλƒ₯ None이 μ•„λ‹ˆλΌ λ°œμƒν•œ μ‹€μ œ μ˜ˆμ™Έμ— λŒ€ν•œ 정보λ₯Ό μ–»κ²Œ λ˜μ—ˆλ‹€.

λ§ˆμ§€λ§‰ 예둜, λ‹€μŒμ€ map2λ₯Ό μ μš©ν•œ μ˜ˆλ‹€. ν•¨μˆ˜ mkPerson은 주어진 이름과 λ‚˜μ΄μ˜ μœ νš¨μ„±μ„ μ κ²€ν•œ ν›„ μœ νš¨ν•œ Person을 μƒμ„±ν•œλ‹€.

// Eitherλ₯Ό 자료 μœ νš¨μ„± 점검에 ν™œμš©
case class Person(name: Name, age: Age)
sealed class Name(val value: String)
sealed class Age(val value: Int)

def mkName(name: String): Either[String, Name] =
if (name == "" || name == null) Left("Name is empty.")
else Right(new Name(name))

def mkAge(age: Int): Either[String, Age] =
if (age < 0) Left("Age is out of range.")
else Right(new Age(age))

def mkPerson(name: String, age: Int): Either[String, Person] =
mkName(name).map2(mkAge(age))(Person(_, _))