3μ£Όμ°¨ (11 ~ 15 Chapter)

Chapter 11 슀칼라의 계측ꡬ쑰

λͺ¨λ“  ν΄λž˜μŠ€κ°€ Any 의 μ„œλΈŒν΄λž˜μŠ€μ΄κΈ° λ•Œλ¬Έμ—, Any κ°€ μ •μ˜ν•΄λ‘” λ©”μ„œλ“œλŠ” λͺ¨λ‘ '보편적인' λ©”μ„œλ“œλ‹€.

11.1 슀칼라의 클래슀 계측ꡬ쑰

슀칼라의 클래슀 계측ꡬ쑰

κ³„μΈ΅μ˜ μ΅œμƒμœ„μ—λŠ” Any ν΄λž˜μŠ€κ°€ μžˆλ‹€.

루트 (root) 클래슀 Any μ—λŠ” μ„œλΈŒν΄λž˜μŠ€κ°€ λ‘˜ μžˆλŠ”λ°, λ°”λ‘œ AnyVal κ³Ό AnyRef λ‹€.

  • AnyVal : λͺ¨λ“  μŠ€μΉΌλΌκ°’ 클래슀 (value class) 의 λΆ€λͺ¨ 클래슀
  • AnyRef : λͺ¨λ“  슀칼라의 μ°Έμ‘° 클래슀 (reference class) 의 기반 클래슀

μŠ€μΉΌλΌκ°€ 기본적으둜 μ œκ³΅ν•˜λŠ” κ°’ ν΄λž˜μŠ€λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit

예λ₯Ό λ“€λ©΄ 42 λŠ” Int 의 μΈμŠ€ν„΄μŠ€μ΄κ³ , 'x' λŠ” Char 의 μΈμŠ€ν„΄μŠ€μ΄κ³ , false λŠ” Boolean의 μΈμŠ€ν„΄μŠ€μ΄λ‹€.

이 κ°’ ν΄λž˜μŠ€λŠ” new λ₯Ό μ‚¬μš©ν•˜μ—¬ μΈμŠ€ν„΄μŠ€ν™” ν•  수 μ—†λ‹€. λͺ¨λ“  κ°’ 클래슀λ₯Ό 좔상 클래슀인 λ™μ‹œμ— νŒŒμ΄λ„ 클래슀 (final class) 둜 λ§Œλ“œλŠ” 기법을 μ‚¬μš©ν•˜μ—¬ μ œμ•½μ„ κ°€ν–ˆλ‹€.

11.2 μ—¬λŸ¬ κΈ°λ³Έ 클래슀λ₯Ό μ–΄λ–»κ²Œ κ΅¬ν˜„ν–ˆλŠ”κ°€?

boolean isEqual(Integer x, Integer y) {
  return x == y;
}

System.out.println(isEqual(421, 421));

μœ„ μžλ°” μ½”λ“œλŠ” 래퍼 (wrapper) ν΄λž˜μŠ€μ— λŒ€μž…λœ 값은 비ꡐ μ—°μ‚°μž (==, !=) λ₯Ό ν†΅ν•˜μ—¬ 비ꡐ가 λΆˆκ°€λŠ₯ν•˜λ‹€.

Integer κ°€ μ°Έμ‘° νƒ€μž…μ΄ μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— μ–Έλ°•μ‹±ν•˜μ—¬ 값을 비ꡐ해야 ν•œλ‹€.

scala> def isEqual(x: Int, y: Int) = x == y
isEqual: (x: Int, y:Int)Boolean

scala> isEqual(421, 421)
res10: Boolean = true

scala> def isEqual(x: Any, y: Any) = x == y
isEqual: (x: Any, y:Any)Boolean

scala> isEqual(421, 421)
res11: Boolean = true

μŠ€μΉΌλΌλŠ” μžλ°” μ½”λ“œμ™€λŠ” λ‹€λ₯΄κ²Œ λ§ˆλ•…νžˆ κ·Έλž˜μ•Ό ν•˜λŠ” λ°©μ‹λŒ€λ‘œ λ™μž‘λœλ‹€.

ν•˜μ§€λ§Œ μ‚¬μš©μžκ°€ μ •μ˜ν•œ 동일성 λŒ€μ‹  μ°Έμ‘° 동일성이 ν•„μš”ν•œ κ²½μš°λ„ μžˆλ‹€.

μ΄λŸ¬ν•œ κ²½μš°μ—λŠ” ν•΄μ‹œ 콘즈 (hash cons) λ₯Ό μ‚¬μš©ν•˜κ³  싢을 κ²½μš°κ°€ μžˆλ‹€. μ°Έμ‘° 동일성을 μ‚¬μš©ν•˜κ²Œ λ§Œλ“€μ–΄λ‘” eq λΌλŠ” λ©”μ„œλ“œκ°€ μžˆλ‹€.

scala> val x = new String("abc")
x: String = abc

scala> val y = new String("abc")
y: String = abc

scala> x == y
res13: Boolean = true

scala> x eq y
res13: Boolean = false

scala> x ne y
res13: Boolean = true

슀칼라의 동일성에 λŒ€ν•΄μ„œ 30μž₯μ—μ„œ μžμ„Ένžˆ 닀룰것이닀.

11.3 λ°”λ‹₯에 μžˆλŠ” νƒ€μž…

Nothing νƒ€μž…μ€ 슀칼라 클래슀 κ³„μΈ΅μ˜ 맨 λ°‘λ°”λ‹₯에 μ‘΄μž¬ν•œλ‹€.

Nothing 은 비정상적 μ’…λ£Œλ₯Ό ν‘œμ‹œν•˜λŠ”κ²ƒμ΄λ‹€.

Nothing 은 λͺ¨λ“  νƒ€μž…μ˜ μ„œλΈŒνƒ€μž…μ΄κΈ° λ•Œλ¬Έμ—, error 와 같은 λ©”μ„œλ“œλ₯Ό λ‹€μ–‘ν•œ κ³³μ—μ„œ μœ μ—°ν•˜κ²Œ μ‚¬μš©κ°€λŠ₯ν•˜λ‹€.

def divide(x: Int, y: Int): Int = 
  if (y != 0) x / y
  else sys.error("can't divide by zero")

μœ„ μ½”λ“œμ—μ„œ λ°˜ν™˜ νƒ€μž…μ΄ Int μ΄μ§€λ§Œ Nothing 의 λͺ¨λ“  νƒ€μž…μ˜ μ„œλΈŒνƒ€μž…μ΄κΈ° λ•Œλ¬Έμ— λ©”μ„œλ“œμ˜ λ°˜ν™˜νƒ€μž…μ—μ„œ μš”κ΅¬ν•˜λŠ” λŒ€λ‘œ Int κ°€ 될 수 μžˆλ‹€.

11.4 μžμ‹ λ§Œμ˜ κ°’ 클래슀 μ •μ˜

κ°’ 클래슀λ₯Ό μ •μ˜ν•˜κ³  μ‹ΆμœΌλ©΄ AnyVal 의 μ„œλΈŒν΄λž˜μŠ€λ‘œ λ§Œλ“€λ©΄ λœλ‹€.

class Dollars(val amount: Int) extends AnyVal {
  override def toString() = "$" + amount
}

ν•œ 가지 νƒ€μž…λ§Œ λ‚¨μš©ν•˜λŠ” 것을 막기

슀칼라 클래슀 계측을 κ°€μž₯ 잘 ν™œμš©ν•˜κ³  μ‹Άλ‹€λ©΄, 문제 μ˜μ—­μ— 잘 λ“€μ–΄λ§žλŠ” μƒˆλ‘œμš΄ 클래슀λ₯Ό μ •μ˜ν•˜λΌ.

class Anchor(val value: String) extends AnyVal
class Style(val value: String) extends AnyVal
class Text(val value: String) extends AnyVal
class Html(val value: String) extends AnyVal

μœ„ 같은 ν΄λž˜μŠ€κ°€ μžˆλ‹€λ©΄ μ’€ 더 μžμ„Έν•œ νƒ€μž… μ‹œκ·Έλ‹ˆμ²˜λ₯Ό κ°–λŠ” title ν•¨μˆ˜λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.

def title(title: Text, anchor: Anchor, style: Style): Html =
  new Html(
    s"<a id='${anchor.value}'>" + 
      s"<h1 class='${style.value}'>" + 
        text.value + 
        "</h1></a>"
  )

11.5 κ²°λ‘ 

λ‹€μŒ μž₯μ—μ„œ 믹슀인 (mix in) 합성을 이해할 μ€€λΉ„κ°€ λ˜μ—ˆλ‹€.

Chapter 12 트레이트

μŠ€μΉΌλΌμ—μ„œ νŠΈλ ˆμ΄νŠΈλŠ” μ½”λ“œ μž¬μ‚¬μš©μ˜ 근간을 μ΄λ£¨λŠ” λ‹¨μœ„λ‹€.

ν•˜λ‚˜μ˜ 슈퍼클래슀만 κ°–λŠ” 클래슀의 μƒμ†κ³ΌλŠ” 달리, 트레이트의 경우 λͺ‡κ°œλΌλ„ ν˜Όν•©ν•΄ μ‚¬μš©ν•  수 μžˆλ‹€.

12.1 트레이트의 λ™μž‘ 원리

트레이트λ₯Ό μ •μ˜ν•˜κ³  λ‚˜λ©΄ extends λ‚˜ with ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•΄ ν΄λž˜μŠ€μ— μ‘°ν•©ν•˜μ—¬ μ‚¬μš©ν•  수 μžˆλ‹€.

슀칼라 ν”„λ‘œκ·Έλž˜λ¨ΈλŠ” 트레이트λ₯Ό μ‚¬μš©ν•  λ•Œ μƒμ†λ³΄λ‹€λŠ” 믹슀인 (mix in) 을 μ‚¬μš©ν•˜λ €ν•œλ‹€.

class Frog extends Philosophical {
  override def to String = "green"
}

트레이트λ₯Ό λ―ΉμŠ€μΈν•  λ•ŒλŠ” extends ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•œλ‹€.

extends ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ 트레이트의 슈퍼클래슀λ₯Ό μ•”μ‹œμ μœΌλ‘œ μƒμ†ν•œλ‹€.

μ—¬λŸ¬ 트레이트λ₯Ό 믹슀인 ν•˜λ €λ©΄ with ꡬ문을 μΆ”κ°€ν•˜λ©΄ λœλ‹€.

class Frog extends Animal with Philosophical {
  override def toString = "green"
}
class Frog extends Animal with Philosophical with HasLegs {
  override def toString = "green"
}

ν΄λž˜μŠ€μ™€ 트레이트의 차이점은 λ‹€μŒκ³Ό κ°™λ‹€.

  • νŠΈλ ˆμ΄νŠΈλŠ” 클래슀 νŒŒλΌλ―Έν„° (클래슀의 μ£Ό μƒμ„±μžμ— 전달할 νŒŒλΌλ―Έν„°) λ₯Ό κ°€μ§ˆ 수 μ—†λ‹€.
  • ν΄λž˜μŠ€μ—μ„œλŠ” super ν˜ΈμΆœμ„ μ •μ μœΌλ‘œ λ°”μΈλ”©ν•˜μ§€λ§Œ, νŠΈλ ˆμ΄νŠΈμ—μ„œλŠ” λ™μ μœΌλ‘œ λ°”μΈλ”©ν•œλ‹€λŠ” 점이닀.

12.2 κ°„κ²°ν•œ μΈν„°νŽ˜μ΄μŠ€μ™€ ν’λΆ€ν•œ μΈν„°νŽ˜μ΄μŠ€

트레이트의 주된 μ‚¬μš©λ°©λ²•μ€ μ–΄λ–€ ν΄λž˜μŠ€μ— κ·Έ ν΄λž˜μŠ€κ°€ 이미 κ°–κ³  μžˆλŠ” λ©”μ„œλ“œλ₯Ό 기반으둜 ν•˜λŠ” μƒˆλ‘œμš΄ λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•˜λŠ” 법이닀.

κ°„κ²°ν•œ μΈν„°νŽ˜μ΄μŠ€ (thin interface) λ₯Ό ν’λΆ€ν•œ μΈν„°νŽ˜μ΄μŠ€ (rich interface) 둜 λ§Œλ“€ λ•Œ 트레이트λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

ꡬ체적인 λ©”μ„œλ“œ κ΅¬ν˜„μ„ νŠΈλ ˆμ΄νŠΈμ— 더할 수 있으면 ν’λΆ€ν•œ μΈν„°νŽ˜μ΄μŠ€ μͺ½μ˜ λΉ„μš©λŒ€λΉ„ 효용이 더 쒋아진닀.

12.3 예제: μ§μ‚¬κ°ν˜• 객체

ν’λΆ€ν•œ 트레이트λ₯Ό μ‚¬μš©ν•΄ μ½”λ“œμ˜ λ°˜λ³΅μ„ ν”Όν•  수 μžˆλ‹€.

trait Rectangular {
  def topLeft: Point
  def bottomRight: Point

  def left = topLeft.x
  def right = bottomRight.x
  def width = right - left
  // μ—¬λŸ¬ κΈ°ν•˜ κ΄€λ ¨ λ©”μ„œλ“œ...
}

12.4 Ordered 트레이트

μŠ€μΉΌλΌμ—μ„œ μ œκ³΅ν•˜λŠ” Ordered λΌλŠ” 트레이트λ₯Ό μ‚¬μš©ν•  경우 ν•˜λ‚˜μ˜ 비ꡐ μ—°μ‚°μžλ§Œ μž‘μ„±ν•˜λ©΄ λͺ¨λ“  비ꡐ μ—°μ‚°μž κ΅¬ν˜„μ„ λŒ€μ‹ ν•  수 μžˆλ‹€.

Ordered νŠΈλ ˆμ΄νŠΈκ°€ κ·Έ ν•˜λ‚˜μ˜ λ©”μ„œλ“œ κ΅¬ν˜„μ„ 기반으둜 <, >, <=, => λ₯Ό μ œκ³΅ν•œλ‹€.

compare λ©”μ„œλ“œλ§Œ κ΅¬ν˜„ν•˜λ©΄ Ordered νŠΈλ ˆμ΄νŠΈκ°€ 비ꡐ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•΄ 클래슀λ₯Ό ν’λΆ€ν•˜κ²Œ ν•΄μ€€λ‹€.

Ordered νŠΈλ ˆμ΄νŠΈκ°€ equals λ₯Ό μ •μ˜ν•˜μ§€ μ•ŠλŠ”λ‹€. μ΄λŠ” λΉ„κ΅κ΄€μ μ—μ„œ equals λ₯Ό κ΅¬ν˜„ν•˜λ €λ©΄ 전달 받을 객체의 νƒ€μž…μ„ μ•Œμ•„μ•Ό ν•œλ‹€.

ν•˜μ§€λ§Œ νƒ€μž… μ†Œκ±° (type erasure) λ•Œλ¬Έμ— Ordered νŠΈλ ˆμ΄νŠΈλŠ” μ΄λŸ¬ν•œ 검사λ₯Ό μˆ˜ν–‰ν•  수 μ—†λ‹€.

12.5 트레이트λ₯Ό μ΄μš©ν•΄ λ³€κ²½ μŒ“μ•„ 올리기

ν΄λž˜μŠ€μ— μŒ“μ„ 수 μžˆλŠ” 변경을 μ μš©ν•΄λ³΄μž.

abstract class IntQueue {
  def get(): Int
  def put(x: Int): Unit
}
import scala.collection.mutable.ArrayBuffer

class BasicIntQueue extends IntQueue {
  private val bu = new ArrayBuffer[int]
  def get() = buf.remove(0)
  def put(x: Int) = { buf += x }
}

μŒ“μ„μˆ˜ μž‡λŠ” 변경을 λ‚˜νƒ€λ‚΄λŠ” Doubling 트레이트

trait Doubling extends IntQueue {
  abstract override def put(x: Int) = { super.put(2 * x) }
}

이 선언은 Doubling νŠΈλ ˆμ΄νŠΈκ°€ IntQueue λ₯Ό μƒμ†ν•œ ν΄λž˜μŠ€μ—λ§Œ 믹슀인 될 수 μžˆλ‹€λŠ” 점이닀.

λ―ΉμŠ€μΈμ€ μˆœμ„œκ°€ μ€‘μš”ν•œλ°, λ―ΉμŠ€μΈν•œ 클래슀의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ κ°€μž₯ 였λ₯Έμͺ½μ— μžˆλŠ” 트레이트의 λ©”μ„œλ“œλ₯Ό λ¨Όμ € ν˜ΈμΆœν•œλ‹€.

12.6 μ™œ 닀쀑상속은 μ•ˆλ˜λŠ”κ°€ ?

트레이트λ₯Ό μ‚¬μš©ν•  λ•Œμ—λŠ” νŠΉμ • ν΄λž˜μŠ€μ— λ―ΉμŠ€μΈν•œ ν΄λž˜μŠ€μ™€ 트레이트λ₯Ό μ„ ν˜•ν™” (linearization) ν•΄μ„œ μ–΄λ–€ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν• μ§€ κ²°μ •ν•œλ‹€.

λͺ¨λ“  μ„ ν˜•ν™”μ—μ„œ μ–΄λ–€ ν΄λž˜μŠ€λŠ” μžμ‹ μ˜ μŠˆνΌν΄λž˜μŠ€λ‚˜ λ―ΉμŠ€μΈν•΄ 넣은 νŠΈλ ˆμ΄νŠΈλ³΄λ‹€ μ•žμ— μœ„μΉ˜ ν•œλ‹€λŠ” 점이닀.

μ•„λž˜μ™€ 같이 μ—¬λŸ¬ 트레이트λ₯Ό 믹슀인 ν•œ Cat ν΄λž˜μŠ€κ°€ μžˆλ‹€κ³  κ°€μ •ν•˜μž.

class Animal
trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends Animal
class Cat extends Animal with Furry with FourLegged

Cat 클래슀의 상속 계측과 μ„ ν˜•ν™”

Cat 클래슀 μ„ ν˜•ν™”μ˜ λ§ˆμ§€λ§‰ 뢀뢄은 Cat 의 슈퍼클래슀인 Animal 을 μ„ ν˜•ν™”ν•œ 결과이닀.

12.7 νŠΈλ ˆμ΄νŠΈλƒ μ•„λ‹ˆλƒ, 이것이 λ¬Έμ œλ‘œλ‹€.

ν™•κ³ ν•œ κ·œμΉ™μ€ μ—†μ§€λ§Œ λͺ‡κ°€μ§€ κ°€μ΄λ“œλΌμΈμ„ μ œμ‹œν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

  • 클래슀λ₯Ό μ‚¬μš©ν•  λ•Œ
    • μ–΄λ–€ ν–‰μœ„λ₯Ό μž¬μ‚¬μš©ν•˜μ§€ μ•Šμ„κ±°λΌλ©΄
  • 트레이트λ₯Ό μ‚¬μš©ν•  λ•Œ
    • μ„œλ‘œ 관련이 μ—†λŠ” ν΄λž˜μŠ€μ—μ„œ μ–΄λ–€ ν–‰μœ„λ₯Ό μ—¬λŸ¬ 번 μž¬μ‚¬μš©ν•΄μ•Ό ν•œλ‹€λ©΄
  • μΆ”μƒν΄λž˜μŠ€λ₯Ό μ‚¬μš©ν•  λ•Œ
    • μŠ€μΉΌλΌμ—μ„œ μ •μ˜ν•œ λ‚΄μš©μ„ μžλ°” μ½”λ“œμ—μ„œ 상속해야 ν•œλ‹€λ©΄

12.8 κ²°λ‘ 

νŠΈλ ˆμ΄νŠΈλŠ” 상속을 톡해 μž¬μ‚¬μš©ν•  수 μž‡λŠ” κΈ°λ³Έ μ½”λ“œ λ‹¨μœ„λ‹€.

Chapter 13 νŒ¨ν‚€μ§€μ™€ μž„ν¬νŠΈ

규λͺ¨κ°€ 큰 ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•  λ•ŒλŠ” ν”„λ‘œκ·Έλž¨μ˜ μ—¬λŸ¬ 뢀뢄이 μ„œλ‘œ μ˜μ‘΄ν•˜λŠ” 정도λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μ»€ν”Œλ§ (coupling) 을 μ΅œμ†Œν™”ν•˜λŠ” 것이 μ€‘μš”ν•˜λ‹€.

13.1 νŒ¨ν‚€μ§€ μ•ˆμ— μ½”λ“œ μž‘μ„±ν•˜κΈ°

μ§€κΈˆκΉŒμ§€ 봀던 μ½”λ“œ μ˜ˆμ œλ“€μ€ 이름 μ—†λŠ” νŒ¨ν‚€μ§€ μ•ˆμ—μ„œ μž‘μ„±ν–ˆμ—ˆλ‹€.

package 절 λ‹€μŒμ— μ€‘κ΄„ν˜Έκ°€ 있으면, κ·Έ μ€‘κ΄„ν˜Έ μ•ˆμ— μžˆλŠ” μ •μ˜λŠ” λͺ¨λ“œ ꡬ νŒ¨ν‚€μ§€μ— μ†ν•œλ‹€.

이런 문법을 νŒ¨ν‚€μ§• (packaging) 이라 λΆ€λ₯Έλ‹€.

package bobsrockets.navigation {
  class Navigator
}

μ•ˆμ— μ—¬λŸ¬ νŒ¨ν‚€μ§€λ₯Ό 넣을 λ•ŒλŠ” μ•„λž˜μ™€ 같이 μ‚¬μš©ν•œλ‹€.

package bobsrockets {
  package navigation {
    class Naviator
    package tests {
      class NavigaorSuite
    }
  }
}

13.2 κ΄€λ ¨ μ½”λ“œμ— κ°„κ²°ν•˜κ²Œ μ ‘κ·Όν•˜κΈ°

μ½”λ“œλ₯Ό νŒ¨ν‚€μ§€ κ³„μΈ΅μ˜¬ λ‚˜λˆ„λŠ” μ΄μœ λŠ” μ‚¬λžŒλ“€μ΄ μ½”λ“œλ₯Ό ν›‘μ–΄λ³Ό λ•Œ 도움을 μ£ΌκΈ° μœ„ν•œ λͺ©μ λ„ μžˆμ§€λ§Œ,

μ»΄νŒŒμΌλŸ¬λ„ 같은 νŒ¨ν‚€μ§€ μ•ˆμ— μžˆλŠ” μ½”λ“œκ°€ μ„œλ‘œ κ΄€λ ¨ μžˆμŒμ„ μ•Œ 수 μžˆλ‹€.

package bobsrockets {
  package navigation {
    class Navigator {
      // bobsrockets.navigation.StarMap 을 μ“Έ ν•„μš”κ°€ μ—†λ‹€.
      val map = new StarMap
    }
    class StarMap
  }

  class Ship {
    // bobsrockets.navigation.Navigator λ₯Ό μ“Έ ν•„μš”κ°€ μ—†λ‹€.
    val nav = new navigation.Navigator
  }

  package fleets {
    class Fleet {
      // bobsrockets.Ship 을 μ“Έ ν•„μš”κ°€ μ—†λ‹€.
      def addShip() = { new Ship }
    }
  }
}

μœ„ μ½”λ“œλŠ” λ‹€μŒ 세가지 κ·œμΉ™μ„ λ”°λ₯Έλ‹€.

  1. μ–΄λ–€ ν΄λž˜μŠ€κ°€ μ†ν•œ νŒ¨ν‚€μ§€ μ•ˆμ—μ„œλŠ” 접두사가 없어도 ν•΄λ‹Ή ν΄λž˜μŠ€μ— μ ‘κ·Όν•  수 μžˆλ‹€.
  2. μ–΄λ–€ νŒ¨μΊμ§€λ₯Ό ν¬ν•¨ν•˜λŠ” (λΆ€λͺ¨) νŒ¨ν‚€μ§€ μ•ˆμ—μ„œλŠ” ν•΄λ‹Ή νŒ¨ν‚€μ§€μ— μ–΄λ–€ 접두어도 뢙이지 μ•Šκ³  μ ‘κ·Όν•  수 μžˆλ‹€.
  3. μ€‘κ΄„ν˜Έ νŒ¨ν‚€μ§€ 문법을 μ‚¬μš©ν•˜λ©΄ κ·Έ νŒ¨ν‚€μ§€ μŠ€μ½”ν”„ λ°–μ—μ„œ μ ‘κ·Ό κ°€λŠ₯ν•œ λͺ¨λ“  이름을 κ·Έ νŒ¨ν‚€μ§€ μ•ˆμ—μ„œλ„ μ“Έ 수 μžˆλ‹€.

λ˜ν•œ μΆ”κ°€μ μœΌλ‘œ μŠ€μΉΌλΌμ—μ„œ μ œκ³΅ν•˜λŠ” _root_ νŒ¨ν‚€μ§€ 문법을 톡해 ν•˜μœ„ νŒ¨ν‚€μ§€μ—μ„œ λ‹€λ₯Έ νŒ¨ν‚€μ§€λ‘œ 접근이 κ°€λŠ₯ν•˜λ‹€.

package launch {
  class Booster3
}

package bobsrockets {
  package navigation {
    package launch {
      class Booster1
    }

    class MissionControl {
      val booster1 = new launch.Booster1
      val booster2 = new bobsrockets.launch.Booster2
      val booster3 = new _root_.launch.Booster3
    }
  }
}

λͺ¨λ“  μ΅œμƒμœ„ νŒ¨ν‚€μ§€λŠ” _root_ νŒ¨ν‚€μ§€μ˜ λ©€λ²„λ‘œ μ·¨κΈ‰ν•œλ‹€.

13.3 μž„ν¬νŠΈ

package bobsdelights

abstract class Fruit(
  val name: String,
  val color: String
)

object Fruits {
  object Apple extends Fruit("apple", "red")
  object Orange extends Fruit("orange", "orange")
  orbject Pear extends Fruit("pear", "yellowish")

  val menu = List(Apple, Orange, Pear)
}
// Fruit 에 μ ‘κ·Ό
import bobsdelights.Fruit

// bobsdelights 의 λͺ¨λ“  멀버에 μ ‘κ·Ό
import bobsdelights._

// Fruits 의 λͺ¨λ“  멀버에 μ ‘κ·Ό
import bobsdelights.Fruit._

μž„ν¬νŠΈ μ…€λ ‰ν„° (import selector) μ‚¬μš©

// Fruits 의 Apple κ³Ό Orange λ§Œμ„ λΆˆλŸ¬μ˜¨λ‹€.
import Fruit.{Apple, Orange}

// Apple 을 McIntosh 둜 이름을 λ°”κΎΌλ‹€.
import Fruit.{Apple => McIntosh, Orange}

// Fruits 의 Pear λ₯Ό μ œμ™Έν•œ λͺ¨λ“  멀버λ₯Ό λΆˆλŸ¬μ˜¨λ‹€.
import Fruits.{Pear => _, _}

13.4 μ•”μ‹œμ  μž„ν¬νŠΈ

μŠ€μΉΌλΌλŠ” λͺ¨λ“  ν”„λ‘œκ·Έλž¨μ—μ„œ λͺ‡κ°€μ§€ μž„ν¬νŠΈλ₯Ό 항상 기본적으둜 μΆ”κ°€λœλ‹€.

import java.lang._
import scala._
import Predef._

Predef κ°μ²΄λŠ” νƒ€μž…, λ©”μ„œλ“œ 그리고 μŠ€μΉΌλΌμ—μ„œ μ‚¬μš©ν•˜λŠ” μ•”μ‹œμ  λ³€ν™˜ (implicit conversion) 을 ν¬ν•¨ν•œλ‹€.

μŠ€μΉΌλΌμ—μ„œλŠ” λ‚˜μ€‘μ— μž„ν¬νŠΈν•œ νŒ¨ν‚€μ§€κ°€ 더 μ•žμ—μ„œ μž„ν¬νŠΈν•œ 것을 κ°€λ¦°λ‹€

λ•Œλ¬Έμ— StringBuilder λ₯Ό μ‚¬μš©ν•˜λ©΄ java.lang.StringBuilder κ°€ μ•„λ‹ˆλΌ scala.StringBuilder λ₯Ό μ•”μ‹œμ μœΌλ‘œ κ°€λ₯΄ν‚¨λ‹€.

13.5 μ ‘κ·Ό μˆ˜μ‹μž

νŒ¨ν‚€μ§€, 클래슀, 객체 멀버 μ•žμ— private, protected λ“±μ˜ μ ‘κ·Ό μˆ˜μ‹μžλ₯Ό λ‘˜ 수 μžˆλ‹€.

λΉ„κ³΅κ°œ 멀버 (private)

였직 κ·Έ μ •μ˜λ₯Ό ν¬ν•¨ν•œ ν΄λž˜μŠ€λ‚˜ 객체 λ‚΄λΆ€μ—μ„œλ§Œ μ ‘κ·Όν•  수 μžˆλ‹€.

보호 멀버 (protedted)

μŠ€μΉΌλΌμ—μ„œλŠ” 보호 멀버λ₯Ό μ •μ˜ν•œ 클래슀의 μ„œλΈŒ ν΄λž˜μ„œμ—μ„œλ§Œ κ·Έ 멀버에 μ ‘κ·Ό ν•  수 μžˆλ‹€.

곡개 멀버 (public)

private λ‚˜ protected κ°€ μ—†λŠ” λ©€λ²„λŠ” 곡개 멀버이닀.

보호 μŠ€μ½”ν”„

μ ‘κ·Ό μˆ˜μ‹μžλ₯Ό μ§€μ •μžλ‘œ ν™•μž₯ ν•  수 μžˆλ‹€.

package bobsrockets

package navigation {
  private[bobsrockets] class Navigator {
    protected[navigation] def useStarChart() = {}

    class LegOfJourney {
      private[Navigator] val distance = 100
    }
    private[this] var speed = 200
  }
}

package launch {
  import navigation._
  object Vehicle {
    private[launch] val guide = new Navigator
  }
}

κ°€μ‹œμ„±κ³Ό λ™λ°˜ 객체

class Rocket {
  import Rocket.fuel
  private def canGoHomeAgain = fuel > 20
}

object Rocket {
  private def fuel = 10
  def chooseStrategy(rocket: Rocket) = {
    if (rocket.canGoHomeAgain)
      goHome()
    else 
      pickAStar()
  }

  def goHome() = {}
  def pickAStar() = {}
}

Rocket ν΄λž˜μŠ€λŠ” Rocket 객체의 fuel λΉ„κ³΅κ°œ λ©”μ„œλ“œμ— μ ‘κ·Όν•  수 μžˆλ‹€.

13.6 νŒ¨ν‚€μ§€ 객체

μŠ€μΉΌλΌμ—μ„œλŠ” νŒ¨ν‚€μ§€ 전체에 λ„μš°λ―Έ λ©”μ„œλ“œ (helper method) λ₯Ό 두고 μ‹Άλ‹€λ©΄, νŒ¨ν‚€μ§€μ˜ μ΅œμƒμœ„ μˆ˜μ€€μ— λ„£μœΌλ©΄ λœλ‹€.

package object bobsdelights {
  def showFruit(fruit: Fruit) = {
    import fruit._
    println(name + "s are " + color)
  }
}
package printmneu
import bobsdelights.Fruits
import bobsdelights.showFruit

object PrintMenu {
  def main(args: Array[String]) = {
    for (fruit <- Fruits.menu) {
      showFruit(fruit)
    }
  }
}

νŒ¨ν‚€μ§€ 객체λ₯Ό μ‚¬μš©ν•˜λŠ” λ‹€λ₯Έ μš©λ„λŠ” νƒ€μž… 별λͺ… (type alias) 와 μ•”μ‹œμ  λ³€ν™˜ (implicit conversion) 을 λ„£κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ” κ²½μš°κ°€ λ§Žλ‹€.

13.7 κ²°λ‘ 

νŒ¨ν‚€μ§€λ₯Ό μ‚¬μš©ν•˜λ©΄ 쉽고 μ“Έλͺ¨ 있게 λͺ¨λ“ˆν™”κ°€ κ°€λŠ₯ν•˜λ‹€.

Chapter 14 단언문과 ν…ŒμŠ€νŠΈ

이 μž₯μ—μ„œλŠ” μž‘μ„±ν•œ μ†Œν”„νŠΈμ›¨μ–΄κ°€ μ œλŒ€λ‘œ λ™μž‘ν•˜λŠ”μ§€ 확인할 수 μžˆλŠ” 두가지 방법을 보여쀀닀.

14.1 단언문

μŠ€μΉΌλΌμ—μ„œλŠ” assert λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” λ°©λ²•μœΌλ‘œ 단언문을 μž‘μ„±ν•œλ‹€.

λ‹¨μ–Έλ¬Έμ˜ 쑰건이 μΆ©μ‘±λ˜μ§€ μ•ŠλŠ” κ²½μš°μ—λŠ” AssertionError λ₯Ό λ°œμƒμ‹œν‚¨λ‹€.

인자λ₯Ό 2개 λ°›λŠ” 단언문도 μžˆλŠ”λ° assert(쑰건, μ„€λͺ…) 으둜 κ΅¬μ„±λœλ‹€.

이 μ„€λͺ…μ˜ 데이터 νƒ€μž…μ€ Any 이며 μ–΄λ–€ 객체라도 λ„˜κΈΈμˆ˜ μžˆλ‹€.

def above(that: Element): Element = {
  val this1 = this widen that.width
  val that1 = that widen this.width
  assert(this1.width == that1.width)
  elem(this1.contents ++ that1.contents)
}

μœ„μ™€ λ™μΌν•œ κΈ°λŠ₯으둜 ensuring 을 μ‚¬μš©ν•˜μ—¬ ν•¨μˆ˜μ˜ κ²°κ³Ό ν™•μΈν•˜κΈ°κ°€ μžˆλ‹€.

private def widen(w: Int): Element = 
  if (w <= width)
    this
  else {
    val left = elem(' ', (w - width) / 2, height)
    val right = elem(' ', w - width - left.width, height)
    left beside this beside right
  } ensuring (w <= _.width)

JVM μ—μ„œ -ea λ‚˜ -da λͺ…λ Ήν–‰ μ˜΅μ…˜μ„ μ‚¬μš©ν•˜λ©΄ assert 와 ensuring λ™μž‘μ„ μΌœκ±°λ‚˜ λŒμˆ˜κ°€ μžˆλ‹€.

14.2 μŠ€μΉΌλΌμ—μ„œ ν…ŒμŠ€νŠΈν•˜κΈ°

μŠ€μΉΌλΌμ—μ„œλŠ” μ—¬λŸ¬ ν…ŒμŠ€νŠΈ 도ꡬ가 μ‘΄μž¬ν•œλ‹€.

  • 슀칼라 ν…ŒμŠ€νŠΈ (Scala Test)
  • 슀칼라 슀팩슀2 (Scala Spec2)
  • 슀칼라 체크 (Scala Check)

슀칼라 ν…ŒμŠ€νŠΈλŠ” κ°€μž₯ μœ μ—°ν•œ ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬μ΄λ‹€.
λ‹€λ₯Έ 문제λ₯Ό ν’€κΈ° μœ„ν•΄ μ‰½κ²Œ μ»€μŠ€ν„°λ§ˆμ΄μ§• ν•  수 μžˆλ‹€.

AnyFunSuite 둜 μž‘μ„±ν•˜λ©° JUnit 을 κ²½ν—˜ν•΄λ³Έ κ°œλ°œμžλŠ” μ΅μˆ ν•  것이닀.

import org.scalatest.funsuite.AnyFunSuite
import Element.elem

class ElementSuite extends AnyFunSuite {
  test("elem result should have passed width") {
    val ele = elem('x', 2, 3)
    assert(ele.width == 2)
  }
}

슀칼라 ν…ŒμŠ€νŠΈμ—μ„œ 쀑심적인 κ°œλ…μ€ ν…ŒμŠ€νŠΈ 집합인 μŠ€μœ„νŠΈ (suite) 이닀.

ν…ŒμŠ€νŠΈλŠ” μ‹œμž‘ν•΄μ„œ 성곡 ν˜Ήμ€ μ‹€νŒ¨, λŒ€κΈ°, μ·¨μ†Œ ν•  수 μžˆλŠ” 이름이 μžˆλŠ” 어떀것듀이닀.
트레이트 μŠ€μœ„νŠΈ (trait suite) λŠ” ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜κΈ° μœ„ν•΄ 사전에 μ€€λΉ„λœ 생λͺ…μ£ΌκΈ° λ©”μ„œλ“œ (life cycle) λ₯Ό μ§€μ›ν•œλ‹€.

μ΄λŸ¬ν•œ λ©”μ„œλ“œλ“€μ€ ν…ŒμŠ€νŠΈ μž‘μ„±κ³Ό μ‹€ν–‰ 방법을 μ»€μŠ€ν„°λ§ˆμ΄μ§• ν•˜κΈ° μœ„ν•΄ μ˜€λ²„λΌμ΄λ“œ ν•  수 μžˆλ‹€.

AnyFunSuite λ₯Ό ν¬ν•¨ν•œ λͺ¨λ“  슀칼라 ν…ŒμŠ€νŠΈ μŠ€νƒ€μΌμ€ μ„œμˆ μ μΈ 이름을 κ°–λŠ” ν…ŒμŠ€νŠΈ μž‘μ„±μ„ ꢌμž₯ν•˜κ³ μž 섀계됬닀.

14.3 μΆ©λΆ„ν•œ 정보λ₯Ό μ œκ³΅ν•˜λŠ” μ‹€νŒ¨ 보고

단언문이 μ‹€νŒ¨ν•œλ‹€λ©΄ νŒŒμΌμ΄λ¦„, μ‹€νŒ¨ν•œ 단언문 쀄 번호, 정보가 λ‹΄κΈ΄ 였λ₯˜λ©”세지가 였λ₯˜ 보고에 ν¬ν•¨λ˜μ–΄μ•Ό ν•œλ‹€.

scala> val width = 3
width: Int = 3

scala> assert(width == 2)
org.scalatest.exceptions.TestFailedException: 
    3 did not equal 2

단언문 μ‹€νŒ¨μ— 더 μƒμ„Έν•œ 정보λ₯Ό μ›ν•œλ‹€λ©΄, 슀칼라 ν…ŒμŠ€νŠΈμ— Diagrams λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

scala> assert(List(1, 2, 3).contains(4))
org.scalatest.exceptions.TestFailedException:

  assert(List(1, 2, 3).contains(4))
         |    |  |  |  |        |
         |    1  2  3  false    4
         List(1, 2, 3)

μŠ€μΉΌλΌν…ŒμŠ€νŠΈμ˜ assert λ©”μ„œλ“œλŠ” 였λ₯˜ λ©”μ„Έμ§€μ—μ„œ μ‹€μ œ 결과와 κΈ°λŒ€λ˜λŠ” 결과의 차이λ₯Ό 보여주지 μ•ŠλŠ”λ‹€.

λ§Œμ•½ μ–΄λ–€ λ©”μ„œλ“œκ°€ λ°œμƒμ‹œν‚¬ 수 μžˆλŠ” μ˜ˆμ™Έλ₯Ό κ²€μ‚¬ν•˜κ³  μ‹Άλ‹€λ©΄, μŠ€μΉΌλΌν…ŒμŠ½νŠΈμ˜ assertThrows λ©”μ„œλ“œλ₯Ό λ‹€μŒκ³Ό 같이 μ‚¬μš©ν•  수 μžˆλ‹€.

assertThrows[IllegalArgumentException] {
  elem('x', -2, 3)
}

μ€‘κ΄„ν˜Έ λ‚΄μ˜ μ½”λ“œκ°€ λ‹€λ₯Έ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚€κ±°λ‚˜, μ–΄λ–€ μ˜ˆμ™Έλ„ λ°œμƒμ‹œν‚€μ§€ μ•ŠλŠ”λ‹€λ©΄, assertThrows λŠ” TestFailedException κ³Ό ν•¨κ»˜ μ¦‰μ‹œ 끝날것이닀.

14.4 λͺ…μ„Έλ‘œ ν…ŒμŠ€νŠΈν•˜κΈ°

λ™μž‘ 주도 개발 (BDD: Behavior Driven Development) ν…ŒμŠ€νŠΈλŠ” μ½”λ“œμ˜ λ™μž‘μ΄ μ‚¬λžŒμ΄ 읽을 수 μžˆλŠ” λͺ…μ„Έλ‘œ μž‘μ„±ν•˜κ³ , μ½”λ“œκ°€ κ·Έ λͺ…세에 따라 μž₯λ™ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜λŠ”λ° 쀑점을 λ‘”λ‹€.

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class ElementSpec extends AnyFlatSpec with Matchers {
  "A UniformElement" should
    "have a width equal to the passed value" in {
      val ele = elem ('x', 2, 3)
      elem.width should be (2)
    }

    it should "have a height equal to the passed value" in {
      val ele = elem('x', 2, 3)
      ele.height should be (3)
    }

    it should "throw an IAE if passed a negative width" in {
      an [IllegalArgumentException] should be thrownBy {
        elem('x', -2, 3)
      }
    }
}

AnyFlatSpec μ—μ„œλŠ” λͺ…μ„Έ 절 (specifier clause) 을 μ‚¬μš©ν•΄ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•œλ‹€.

λ¨Όμ € ν…ŒμŠ€νŠΈ ν•  μ£Όμ œμ— λŒ€ν•΄ 이름을 λ¬Έμžμ—΄λ‘œ λΆ™μ΄λŠ” 것뢀터 μ‹œμž‘ν•œλ‹€.
κ·Έ 뒀에 should λ₯Ό λ„£κ³ , κ·Έ 뒀에 주제의 μž‘λ™μ„ μ„€λͺ…ν•˜λŠ” λ¬Έμžμ—΄μ΄ 였고, κ·Έ λ‹€μŒμ— in 이 λ”°λΌμ˜¨λ‹€.

in λ‹€μŒμ—λŠ” μ€‘κ΄„ν˜Έ μ•ˆμ— μ§€μ •ν•œ λ™μž‘μ„ ν…ŒμŠ€νŠΈν•˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€.

에릭 ν† λ ˆλ³΄λ₯΄ (Eric Torreborrre) κ°€ 슀칼라둜 μž‘μ„±ν•œ μ˜€ν”ˆμ†ŒμŠ€ 도ꡬ인 μŠ€νŽ™μŠ€2 (specs2) ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬λ„ BDD λ₯Ό μ§€μ›ν•œλ‹€.

BDD 의 κ°€μž₯ 큰 아이디어 쀑 ν•˜λ‚˜λŠ” μ–΄λ–€ μ†Œν”„νŠΈμ›¨μ–΄ μ‹œμŠ€ν…œμ„ λ§Œλ“€μ§€ κ²½μ •ν•˜λŠ” μ‚¬λžŒ, κ·Έ μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό κ΅¬ν˜„ν•˜λŠ” μ‚¬λžŒ, 그리고 κ·Έ μ†Œν”„νŠΈμ›¨μ–΄κ°€ 잘 마무리 λ˜μ–΄ λ™μž‘ν•˜λŠ”μ§€λ₯Ό κ²°μ •ν•˜λŠ” μ‚¬λžŒ μ‚¬μ΄μ˜ μ˜μ‚¬μ†Œν†΅μ„ ν…ŒμŠ€νŠΈκ°€ 도와쀄 수 μžˆλ‹€λŠ” 것이닀.

import org.scalatest._
import org.scalatest.featurespec.AnyFeatureSpec

class TVSetSpec extends AnyFeatureSpec with GivenWhenThen{
  Feature("TV power button")
    Scenario("User presses power button when TV is off") {
      Given("a TV set that is switched off")
      When("the power button is pressed")
      Then("the TV should switch on)
      pending
    }
}

AnyFeatureSpec 은 μ†Œν”„νŠΈμ›¨μ–΄ μš”κ΅¬μ‚¬ν•­μ— λŒ€ν•œ λŒ€ν™”λ₯Ό 돕기 μœ„ν•΄ μ„€κ³„λ˜μ—ˆλ‹€.

μ΄λŠ” ꡬ체적인 νŠΉμ§• (feature) 은 λ°ν˜€μ•Ό ν•˜κ³ , 그런 νŠΉμ§•μ„ μ‹œλ‚˜λ¦¬μ˜€ (scenario) 에 λͺ…μ‹œν•΄μ•Ό ν•œλ‹€.
Given, When, Then 은 ꡬ체적인 κ°œλ³„ μ‹œλ‚˜λ¦¬μ˜€μ— λŒ€ν•œ λŒ€ν™”μ˜ μ΄ˆμ μ„ λ§žμΆ”λŠ”λ° 도움을 쀄 수 μžˆλ‹€.

pending ν˜ΈμΆœμ€ ν…ŒμŠ€νŠΈλ‚˜ μ‹€μ œ λ™μž‘μ΄ 아직 κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜λ‹€λŠ” 사싀을 λͺ…μ‹œν•œλ‹€.

14.5 ν”„λ‘œνΌν‹° 기반 ν…ŒμŠ€νŠΈ

λ¦¬μ»€λ“œ λ‹μŠ¨ (Rickard Nilsson) 이 μž‘μ„±ν•œ μ˜€ν”ˆμ†ŒμŠ€ ν”„λ ˆμž„μ›Œν¬μΈ 슀칼라체크 (Scala Check) λŠ” 슀칼라둜 λ§Œλ“€μ–΄μ§„ 또 λ‹€λ₯Έ μœ μš©ν•œ ν…ŒμŠ€νŠΈ 도ꡬ이닀.

슀칼라 μ²΄ν¬λŠ” μ½”λ“œκ°€ μ€€μˆ˜ν•΄μ•Ό ν•˜λŠ” ν”„λ‘œνΌν‹°λ₯Ό λͺ…μ‹œν•˜κ²Œ ν•΄μ€€λ‹€.

import org.scalatest.wordspec.AnyWordSpec
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalatest.matchers.must.Matchers._

class ElementSpec extends AnyWorldSpec with ScalaCheckPropertyChecks {
  "elem result" must {
    "have passed width" in {
      forAll { (w: Int) =>
        whenever (x > 0) {
          elem('x', w % 100, 3).width must equal (w % 100)
        }
      }
    }
  }
}

forAll λ©”μ„œλ“œ 내뢀에 elem νŒ©ν† λ¦¬κ°€ μ§€μΌœμ•Ό ν•˜λŠ” ν”„λ‘œνΌν‹°λ₯Ό κ²€μ‚¬ν•œλ‹€.

whenever (w > 0) {
  elem('x', w % 100, 3).width must equal (w % 100)
}

whenever μ ˆμ€ μ™Όμͺ½ νŽΈμ— μžˆλŠ” 식이 true 일 λ•Œλ§ˆλ‹€ 였λ₯Έμͺ½μ— μžˆλŠ” 식이 true κ°€ λ˜μ–΄μ•Ό 함을 λͺ…μ‹œν•œλ‹€.

슀칼라 μ²΄ν¬λŠ” ν”„λ‘œνΌν‹°μ— λ§žμ§€ μ•ŠλŠ” 값을 μ°ΎκΈ° μœ„ν•΄ w 에 λ“€μ–΄κ°ˆ 수 μžˆλŠ” 값을 μ—¬λŸ¬κ°œ μƒμ„±ν•˜κ³  각각을 ν…ŒμŠ€νŠΈν•œλ‹€.
슀칼라 체크가 μ‹œλ„ν•˜λŠ” λͺ¨λ“  값을 ν”„λ‘œν‹°κ°€ λ§Œμ‘±ν•˜λŠ” 경우 ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όν•˜κ³ , 그렇지 μ•ŠμœΌλ©΄ μ‹€νŒ¨μ˜ 원인이 된 값이 λ“€μ–΄μžˆλŠ” TestFailedException 을 뿜으며 ν…ŒμŠ€νŠΈκ°€ μ’…λ£Œλœλ‹€.

abstract class Element {
  def contents: Array[String]
  def height: Int = contents.length
  def width: Int = if (height == 0) 0 else contents(0).length
}

Element ν΄λž˜μŠ€λŠ” 10.3 예제λ₯Ό λ”°λ₯Έλ‹€.

참고자료

ν”„λ‘œνΌν‹° 기반 ν…ŒμŠ€νŠΈλ‘œ μΆ”μ²œ 받은 Hedgehog

14.6 ν…ŒμŠ€νŠΈ 쑰직과 μ‹€ν–‰

슀칼라 ν…ŒμŠ€νŠΈμ—μ„œλŠ” μŠ€μœ„νŠΈ (suite) μ•ˆμ— μŠ€μœ„νŠΈλ₯Ό 포함 μ‹œν‚΄μœΌλ‘œμ¨ 큰 ν…ŒμŠ€νŠΈλ₯Ό 쑰직화 ν•œλ‹€.

μ΄λŠ” μ–΄λ–€ μŠ€μœ„νŠΈκ°€ μ‹€ν–‰λ˜λ©΄ κ·Έ μ•ˆμ— ν…ŒμŠ€νŠΈ 뿐만이 μ•„λ‹ˆλΌ 내뢀에 μžˆλŠ” μŠ€μœ„νŠΈμ˜ ν…ŒμŠ€νŠΈλ„ μ‹€ν–‰ν•¨μœΌλ‘œμ¨ λ‚΄ν¬λœ λͺ¨λ“  μŠ€μœ„νŠΈλ₯Ό μ‹€ν–‰ν•œλ‹€.

큰 μŠ€μœ„νŠΈλŠ” Suite 객체 트리둜 ν‘œν˜„ν•  수 μžˆλ‹€.

μˆ˜λ™ ν˜Ήμ€ μžλ™μœΌλ‘œ μŠ€μœ„νŠΈλ₯Ό 포함 μ‹œν‚¬μˆ˜ μžˆλ‹€.

μˆ˜λ™

  • nestedSuites λ©”μŠ€λ“œλ₯Ό μ˜€λ²„λΌμ΄λ“œ ν•˜κ±°λ‚˜ ν¬ν•¨μ‹œν‚€κ³  싢은 μƒμ„±μžμ— Suite 클래슀의 μƒμ„±μžμ— 전달
  • μŠ€μœ„νŠΈκ°„μ˜ 포함관계λ₯Ό μœ„ν•΄μ„œ μΆ”κ°€ μƒμ„±μžλ₯Ό 제곡

μžλ™

  • 슀칼라 ν…ŒμŠ€νŠΈμ˜ Runner 에 νŒ¨ν‚€μ§€ 이름을 전달

14.7 κ²°λ‘ 

μžλ°”μ—μ„œμ˜ μ΅μˆ™ν•œ ν…ŒμŠ€νŠΈ λ„κ΅¬μ˜ μž₯점을 μ‚΄λ¦΄μˆ˜λ„ 있고, μŠ€μΉΌλΌν…ŒμŠ€νŠΈ, 슀칼라체크, 슀팩슀2 λ“±μ˜ μŠ€μΉΌλΌλ§Œμ„ μœ„ν•΄ μ„€κ³„ν•œ μƒˆλ‘œμš΄ λ„κ΅¬μ˜ 이점도 μ·¨ν•  수 μžˆλ‹€.

Chapter 15 μΌ€μ΄μŠ€ ν΄λž˜μŠ€μ™€ νŒ¨ν„΄ 맀치

μΌ€μ΄μŠ€ 클래슀 (Case Class) 와 νŒ¨ν„΄ 맀치 (Pattern Match) λŠ” 일반적이고 μΊ‘μŠν™”λ˜μ§€ μ•ŠλŠ” 데이터 ꡬ쑰λ₯Ό μž‘μ„±ν•  λ•Œ 쓰인닀.

15.1 κ°„λ‹¨ν•œ 예

μΌ€μ΄μŠ€ 클래슀의 μ •μ˜

abstract class Expr 
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

μΌ€μ΄μŠ€ 클래슀

클래슀 μ„ μ–Έμ—μ„œ 각 μ„œλΈŒ 클래슀 μ•žμ— case λΌλŠ” μˆ˜μ‹μžκ°€ μžˆμŒμ„ μ£Όμ˜ν•œλ‹€.

case μˆ˜μ‹μžλŠ” 슀칼라 μ»΄νŒŒμΌλŸ¬μ—κ²Œ ν•΄λ‹Ή ν΄λž˜μŠ€μ— λ¬Έλ²•μ μœΌλ‘œ νŽΈλ¦¬ν•œ κΈ°λŠ₯ λͺ‡κ°€μ§€λ₯Ό μΆ”κ°€ν•˜λΌκ³  μ§€μ‹œν•œλ‹€.

  1. μ»΄νŒŒμΌλŸ¬λŠ” 클래슀 이름과 같은 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•œλ‹€.
scala> val v = Var("x")
v: Var = Var(x)

νŒ©ν† λ¦¬ λ©”μ„œλ“œλŠ” μ€‘μ²©ν•΄μ„œ 객체λ₯Ό 생성할 λ•Œ μ’‹λ‹€.

  1. μΌ€μ΄μŠ€ 클래슀의 νŒŒλΌλ―Έν„° λͺ©λ‘μ— μžˆλŠ” λͺ¨λ“  인자의 μ•”μ‹œμ μœΌλ‘œ val 접두사λ₯Ό 뢙인닀.
scala> v.name
res0: String = x

scala> op.left
res1: Expr = Number(1.0)

각 νŒŒλΌλ―Έν„°κ°€ 클래슀의 ν•„λ“œλ„ λœλ‹€.

  1. μ»΄νŒŒμΌλŸ¬λŠ” μΌ€μ΄μŠ€ ν΄λž˜μŠ€μ— toString, hashCode, equals λ©”μ„œλ“œμ˜ 일반적인 κ΅¬ν˜„μ„ μΆ”κ°€ν•œλ‹€.
scala> println(op)
BinOp(+,Number(1.0),Var)

scala> op.right == Var("x")
res3: Boolean = true

μŠ€μΉΌλΌμ—μ„œλŠ” == 을 μ‚¬μš©ν•œ 비ꡐλ₯Ό 항상 equals 에 μœ„μž„ν•œλ‹€.

  1. μ»΄νŒŒμΌλŸ¬λŠ” μ–΄λ–€ μΌ€μ΄μŠ€ ν΄λž˜μŠ€μ—μ„œ 일뢀λ₯Ό λ³€ν˜•ν•œ 볡사본을 μƒμ„±ν•˜λŠ” copy λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•œλ‹€.

이 copy λ©”μ„œλ“œλŠ” λ””ν΄νŠΈ νŒŒλΌλ―Έν„°μ™€ 이름을 뢙인 νŒŒλΌλ―Έν„°λ₯Ό μ œκ³΅ν•œλ‹€.

scala> op.copy(operator = "-")
BinOp(-,Number(1.0),Var)

ν•˜μ§€λ§Œ μΌ€μ΄μŠ€ 클래슀의 κ°€μž₯ 큰 μž₯점은 νŒ¨ν„΄ 맀치λ₯Ό μ§€μ›ν•œλ‹€λŠ” 점이닀.

νŒ¨ν„΄ 맀치

νŒ¨ν„΄ λ§€μΉ˜λŠ” μŠ€μΉΌλΌμ—μ„œ ν•¨μˆ˜λ₯Ό λ‹¨μˆœν™” μ‹œν‚€λŠ”κ²ƒμ΄ 핡심

 def simplifyTop(expr: Expr): Expr = expr match { 
  case UnOp("-", UnOp("-", e))  => e
  case BinOp("+", e, Number(0)) => e
  case BinOp("*", e, Number(1)) => e
  case _ => expr 
}

simplifyTop(UnOp("-", UnOp("-", Var("x")))) 
// Var(x)

simplifyTop(BinOp("+", UnOp("-", Var("x")), Number(3))) 
// BinOp(+,UnOp(-,Var(x)),Number(3.0))  ???

TODO

방문자 νŒ¨ν„΄ 을 μ‚¬μš©ν•˜μ—¬ λ™μΌν•œ κΈ°λŠ₯을 κ΅¬ν˜„ν•΄λ³΄μž

switch 와 match 의 비ꡐ

match 식은 μžλ°” μŠ€νƒ€μΌμ˜ switch λ₯Ό μΌλ°˜ν™”ν•œ 것이닀.

슀칼라의 match 와 μžλ°” switch 차이점은 λ‹€μŒκ³Ό κ°™λ‹€.

  • 슀칼라의 match λŠ” ν‘œν˜„μ‹μ΄λ‹€. λ”°λΌμ„œ 결괏값을 λ‚΄λ†“λŠ”λ‹€.
  • 슀칼라의 λŒ€μ•ˆ ν‘œν˜„μ‹μ€ λ‹€μŒ μΌ€μ΄μŠ€λ‘œ 빠지지 μ•ŠλŠ”λ‹€.
  • match 에 μ„±κ³΅ν•˜μ§€ λͺ»ν•œ 경우 MatchError μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€.

15.2 νŒ¨ν„΄μ˜ μ’…λ₯˜

μ™€μΌλ“œ μΉ΄λ“œ νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

μƒμˆ˜ νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

λ³€μˆ˜ νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

λ³€μˆ˜ λ˜λŠ” μƒμˆ˜?

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

μƒμ„±μž νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

μ‹œν€€μŠ€ νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

νŠœν”Œ νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

νƒ€μž… 지정 νŒ¨ν„΄

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

νƒ€μž… μ†Œκ±°

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

λ³€μˆ˜ 바인딩

μ±… 예제 및 μ„€λͺ… μ°Έκ³ 

15.3 νŒ¨ν„΄ κ°€λ“œ

ν•œ νŒ¨ν„΄ μ•ˆμ— 였직 ν•œλ²ˆλ§Œ λ‚˜μ™€μ•Ό ν•œλ‹€. μ΄λŠ” μŠ€μΉΌλΌκ°€ μ„ ν˜• νŒ¨ν„΄μœΌλ‘œ μ œν•œν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

scala> def simplifyAdd(e: Expr) = e match { 
          case BinOp("+", x, x) => BinOp("*", x, Number(2)) 
          case _ => e 
      } 

// error: x is already defined as value x

ν•˜μ§€λ§Œ νŒ¨ν„΄ κ°€λ“œ (pattern guard) λ₯Ό μ‚¬μš©ν•˜λ©΄ match ν‘œν˜„μ‹μ„ λ‹€μ‹œ μ“Έ 수 μžˆλ‹€.

scala> def simplifyAdd(e: Expr) = e match { 
        case BinOp("+", x, y) if x == y => 
        BinOp("*", x, Number(2)) 
        case _ => e 
      } 

νŒ¨ν„΄ κ°€λ“œλŠ” νŒ¨ν„΄ 뒀에 였고 if 둜 μ‹œμž‘ν•œλ‹€.

15.4 νŒ¨ν„΄ κ²ΉμΉ¨

νŒ¨ν„΄ λ§€μΉ˜λŠ” μ½”λ“œμ— μžˆλŠ” μˆœμ„œλ₯Ό λ”°λ₯Έλ‹€.

λ”°λΌμ„œ λͺ¨λ“  경우λ₯Ό μ²˜λ¦¬ν•˜λŠ” case 문이 더 ꡬ체적인 κ·œμΉ™ λ‹€μŒμ— 와야 ν•œλ‹€. (더 쒁은 λ²”μœ„λ₯Ό μ²˜λ¦¬ν•˜λŠ” κ·œμΉ™μ΄ μ„ ν–‰λ˜μ–΄μ•Ό ν•œλ‹€.)

def simplifyBad(expr: Expr): Expr = expr match { 
  case UnOp(op, e) => UnOp(op, simplifyBad(e)) 
  case UnOp("-", UnOp("-", e)) => e 
} 

// case UnOp("-", UnOp("-", e)) => e μ½”λ“œλŠ” 도달할 수 μ—†λ‹€.

15.5 λ΄‰μΈλœ 클래슀

컴파일러의 도움을 μ–»μ–΄ λͺ¨λ“  μΌ€μ΄μŠ€μ˜ 맀치λ₯Ό κ°€λŠ₯ν•˜λ„λ‘ λ†“μΉœ νŒ¨ν„΄ 쑰합이 있으면 μ•Œλ €μ€€λ‹€.

μΌ€μ΄μŠ€ 클래슀λ₯Ό λ΄‰μΈλœ 클래슀 (sealed class) 둜 λ§Œλ“€λ©΄ κ·Έ ν΄λž˜μŠ€μ™€ 같은 파일이 μ•„λ‹Œ λ‹€λ₯Έ κ³³μ—μ„œ μƒˆλ‘œμš΄ μ„œλΈŒ 클래슀λ₯Ό λ§Œλ“€ 수 μ—†λ‹€.

λ΄‰μΈλœ 클래슀λ₯Ό λ§Œλ“œλ €λŠ” 클래슀 μ•žμ— sealed λΌλŠ” ν‚€μ›Œλ“œλ₯Ό λ„£μœΌλ©΄ λœλ‹€.

sealed abstract class Expr 
case class Var(name: String) extends Expr 
case class Number(num: Double) extends Expr 
case class UnOp(operator: String, arg: Expr) extends Expr 
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr

μœ„μ™€ 같이 λ΄‰μΈλœ 클래슀λ₯Ό μ •μ˜ν•΄λ†“κ³  μ•„λž˜μ™€ 같이 맀치 μΌ€μ΄μŠ€λ₯Ό μž‘μ„±ν•œλ‹€.

def describe(e: Expr): String = e match { 
    case Number(_) => "a number" 
    case Var(_)    => "a variable" 
} 

λ‹€μŒκ³Ό 같은 κ²½κ³ κ°€ λ…ΈμΆœ λœλ‹€.

warning: match is not exhaustive!
missing combination         UnOp 
missing combination         BinOp

λ§Œμ•½ κ²½κ³ λ₯Ό μ—†μ• κ³  μ‹Άλ‹€λ©΄ λ‚˜λ¨Έμ§€ μΌ€μ΄μŠ€λ₯Ό λ‹€ λ§€μΉ˜μ‹œμΌœλ†“λŠ”λ‹€.

def describe(e: Expr): String = e match { 
  case Number(_) => "a number" 
  case Var(_) => "a variable" 
  case _ => throw new RuntimeException
}

더 μ„Έλ ¨λœ 방법은 @unchecked μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•˜λŠ” 것이닀.

이 @unchecked μ–΄λ…Έν…Œμ΄μ…˜μ€ νŒ¨ν„΄ λ§€μΉ˜μ‹œ λͺ¨λ“  νŒ¨ν„΄μ„ λ‹€ λ‹€λ£¨λŠ”μ§€ κ²€μ‚¬ν•˜λŠ” 일을 μƒλž΅ν•œλ‹€.

15.6 Options νƒ€μž…

μŠ€μΉΌλΌμ—λŠ” Option μ΄λΌλŠ” ν‘œμ€€ νƒ€μž…μ΄ μžˆλ‹€. 이 νƒ€μž…μ€ 선택적인 값을 ν‘œν˜„ν•˜λ©° 두가지 ν˜•νƒœκ°€ μžˆλ‹€.

λ§Œμ•½ x κ°€ μ‹€μ œ 값이라면 Some(x) λΌλŠ” ν˜•νƒœλ‘œ 값이 μžˆμŒμ„ ν‘œν˜„ν•  수 μžˆλ‹€. λ°˜λŒ€λ‘œ 값이 μ—†μœΌλ©΄ None μ΄λΌλŠ” 객체가 λœλ‹€.

scala> def show(x: Option[String]) = x match { 
        case Some(s) => s 
        case None => "?" 
      } 

μŠ€μΉΌλΌμ—μ„œλŠ” 선택적인 값을 λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ Option 을 μ‚¬μš©ν•˜λ„λ‘ ꢌμž₯ν•œλ‹€.

15.7 νŒ¨ν„΄μ€ μ–΄λ””μ—μ„œλ‚˜

독립적인 match ν‘œν˜„μ‹λΏ μ•„λ‹ˆλΌ, 슀칼라의 μ—¬λŸ¬κ³³μ—μ„œ νŒ¨ν„΄μ„ μ‚¬μš©ν•  수 μžˆλ‹€.

λ³€μˆ˜ μ •μ˜μ—μ„œ νŒ¨ν„΄ μ‚¬μš©ν•˜κΈ°

val μ΄λ‚˜ var λ₯Ό μ •μ˜ν•  λ•Œ μ‹λ³„μž λŒ€μ‹ μ— νŒ¨ν„΄μ„ μ‚¬μš©ν•  수 μžˆλ‹€.

scala> val myTuple = (123, "abc") 
myTuple: (Int, java.lang.String) = (123,abc) 

scala> val (number, string) = myTuple 
number: Int = 123 
string: java.lang.String = abc

λ‹€μŒκ³Ό 같이 ꡬ쑰해체 ꡬ문과 같이(?) μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.

scala> val exp = new BinOp("*", Number(5), Number(1)) 
exp: BinOp = BinOp(*,Number(5.0),Number(1.0)) 

scala> val BinOp(op, left, right) = exp 
op: String = * 
left: Expr = Number(5.0) 
right: Expr = Number(1.0)

case λ‚˜μ—΄ν•΄μ„œ λΆ€λΆ„ ν•¨μˆ˜ λ§Œλ“€κΈ°

val withDefault: Option[Int] => Int = {
  case Some(x) => x
  case None => 0
}

μ•„λž˜ case 문은 λΆ€λΆ„ ν•¨μˆ˜μ²˜λŸΌ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.

scala> withDefault(Some(10)) 
// Int = 10 

scala> withDefault(None) 
// Int = 0

μ΄λŸ¬ν•œ κΈ°λŠ₯은 μ•„μΉ΄ μ•‘ν„° (akka actors) λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μœ μš©ν•œλ°, case λ₯Ό λ‚˜μ—΄ν•˜μ—¬ receive λ©”μ„œλ“œ μ •μ˜λ₯Ό κ°€λŠ₯μΌ€ ν•œλ‹€.

μœ μ˜ν•  점은 case λ‚˜μ—΄μ€ λΆ€λΆ„ ν•¨μˆ˜ (partial function) 이 μ²˜λ¦¬ν•˜μ§€ μ•ŠλŠ” 값을 μ „λ‹¬ν•΄μ„œ ν˜ΈμΆœν•˜λ©΄ μ‹€ν–‰ μ‹œμ μ— μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€.

val second: List[Int] => Int = {
  case x :: y :: _ => y 
}

// warning: match is not exhaustive!
// missing combination      Nil

μœ„ μ½”λ“œλŠ” μ›μ†Œλ₯Ό 3개λ₯Ό λ„˜κΈ°λ©΄ ν†΅κ³Όν•˜μ§€λ§Œ 빈 리슀트λ₯Ό λ„˜κΈ°λ©΄ μ‹€νŒ¨ν•œλ‹€.

μ΄λŸ¬ν•œ 이슈 해결을 μœ„ν•΄ isDefinedAt λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λŠ”λ°, isDefinedAt λ©”μ„œλ“œλŠ” λΆ€λΆ„ ν•¨μˆ˜κ°€ μ–΄λ–€ 값에 λŒ€ν•΄ 결괏값을 μ •μ˜ν•˜κ³  μžˆλŠ”μ§€ μ•Œλ €μ€€λ‹€.

second.isDefinedAt(List(5,6,7))
// Boolean = true                             

second.isDefinedAt(List()) 
// Boolean = false

for ν‘œν˜„μ‹μ—μ„œ νŒ¨ν„΄ μ‚¬μš©ν•˜κΈ°

for ((country, city) <- capitals) 
  println("The capital of "+ country +" is "+ city) 

// The capital of France is Paris 
// The capital of Japan is Tokyo

μœ„μ™€ 같이 for ν‘œν˜„μ‹ μ•ˆμ— νŒ¨ν„΄μ„ μ‚¬μš©ν•  수 μžˆλ‹€.

15.8 λ³΅μž‘ν•œ 예제

예제 μ½”λ“œ μ„€λͺ…

15.9 κ²°λ‘ 

μΌ€μ΄μŠ€ ν΄λž˜μŠ€μ™€ νŒ¨ν„΄λ§€μΉ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ 객체지ν–₯μ—μ„œ μ§€μ›ν•˜μ§€ μ•ŠλŠ” κ°„κ²°ν•œ ν‘œν˜„λ²•μ˜ 이점을 λˆ„λ¦΄μˆ˜ μžˆλ‹€.