패키지와 임포트

규모가 큰 프로그램을 작성할 때는 프로그램의 여러 부분이 서로 의존하는 정도를 나타내는 커플링(coupling)을 최소화하는 것이 중요합니다.

스칼라 코드는 자바 플랫폼의 전역 패캐지 계층에 포함됩니다. 스칼라는 두 가지 방법으로 이름이 있는 패키지 안에 코드를 작성할 수 있습니다. 첫번째, 첫 줄에 package 절을 사용해 파일 전체를 패키지 안에 넣는 것 입니다.


package ch13
class Navigator

패키지 안에 코드를 작성할 수 있는 또 다른 방법은 package 절 다음에 중괄호가 있으면, 중괄호 안에 있는 정의는 모두 그 패키지에 속합니다. 이런 문법을 패키징(packaging)이라 부릅니다.


package ch13.classz {
  class Navigator
}

코드를 패키지 계층으로 나눌 수도 있습니다.

package bobsrockets {
  package navigation {
    class Navigator {
      val map = new StarMap
    }
  }

  class Ship {
    // bobsrockets.navigation.Navigation를 쓸 필요가 없다.
    val nav = new navigation.Navigator
  }

  package fleets {
    class Fleet {
      // bobsrockets.Ship을 쓸 필요가 없다.
      def addShip() = { new Ship }
    }
  }
}

스칼라는 사용자가 작성한 모든 패키지 외부에 존재하는 _root_ 패키지를 제공합니다. 따라서 중첩된 패키지를 참조할 때 _root_ 를 사용하면 됩니다.

임포트

스칼라의 improt절은 어느 곳에서 사용할 수 있으며, 패키지뿐만 아니라 객체도 참조가능하며, 다른 이름을 지정할 수도 있습니다.


import java.util.regex
import fruit._ // fruit.* 과 동일

케이스 클래스와 패턴 매치

케이스 클래스와 패턴 매치는 캡슐화되지 않은 데이터 구조를 작성할 때 쓰입니다. 케이스 클래스는 아주 많은 코드를 작성하지 않고도 객체에 대한 패턴 매치를 하게 해주는 스칼라의 구성요소 입니다. 대부분의 경우 패턴 매치에 사용할 각 클래스 앞에 case 키워드를 추가하는 것 입니다.

패턴 매치

match는 자바의 switch와 비슷합니다. 스칼라에서 select matchcase로 시작하는 여러가지 대안을 나열하면 됩니다. 개별 대안은 패턴과 셀렉터가 일치했을 때 계산하는 하나 이상의 표현식을 포함합니다.

패턴 종류

  • 와일드카드(_) 패턴; 모든 경우 매치가 가능한 디폴트 케이스나, 무시하기 위해서 와일드카드 패턴을 사용

expr match {
  case BinOp(_, _, _) => println(expr + " is a binary operation")
  case _ =>
}

  • 상수패턴; 동일한 값과 매치

def describe(x: Any) = x match {
  case 5 => "five"
  case true => "truth"
  case "hello" => "hi!"
  case Nil => "the empty list"
  case _ => "something else"
}

  • 변수패턴; 변수에 객체를 할당할 때 사용

expr match {
  case 0 => "zero"
  case somthingElse => "not zero: " + somthingElse
}

  • 생성자패턴; 이름(BinOp)이 케이스 클래스를 가리킨다면, 이 패턴의 의미는 어떤 값이 해당 케이스 클래스의 멤버인지 검사한 다음, 그 객체의 생성자가 인자로 전달받은 값들이 괄호 안의 패턴과 정확히 매치될 수 있는지 검사

expr match {
  case BinOp("+", e, Number(0)) => println("a deep match")
  case _ =>
}

  • 시퀸스패턴; 배열이나 리스트 같은 시퀸스 타입에 대해서도 매치 시킬 수 있음

expr match {
  case List(0, _, _) => println("found id")
  case _ =>
}

// 길이 관계 없이 가능
expr match {
  case List(0, _*) => println("found it")
  case _ =>
}

  • 튜플패턴; 튜플도 (당연히) 매치 가능

def tupleDemo(expr: Any) =
  expr match {
    case (a, b, c) => println("matched " + a + b + c)
    case _ =>
  }

  • 타입지정; 타입 검사나 타입 변환을 간편하게 대신하기 위한 타입 지정 패턴 가능

def generalSize(x: Any) = x match {
  case s: String => s.length
  case m: Map[_, _] => m.size
  case _ => -1
}

  • 타입소거; 타입 소거의 유일한 예외는 배열입니다. 배열은 자바뿐 아니라 스칼라에서도 특별하게 다뤄지기 때문입니다. 배열에서는 원소 타입과 값을 함께 저장하기 때문에 패턴 매칭을 할 수 있습니다. 그러나 스칼라는 자바와 마찬가지로 제네릭에서 타입소거 모델을 사용합니다. 이는 실행 시점에 타입 인자에 대한 정보를 유지하지 않는다는 뜻 입니다. 결과적으로, 실행시에는 어떤 맵 객체가 두 Int 타입을 타입 인자로 받아서 생성한 것인지, 다른 타입들을 받아서 생성한 것인지 알 방법이 없습니다. 시스템이 할 수 있는 일은 어떤 값이 임의의 타입 인자를 받아 생성한 맵이라고 결정하는 것뿐입니다.

def isIntIntMap(x: Any) = x match {
  case m: Map[Int, Int] => true
  case _ => false
}

isIntIntMap(Map(1->1)) // res0: Boolean = true
isIntIntMap(Map("abc" -> "abc")) // res1: Boolean = true

// Warning:(2, 12) non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
  case m: Map[Int, Int] => true
          ^

def isStringArray(x: Any) = x match {
  case a: Array[String] => "yes"
  case _ => "no"
}

val as = Array("abc")
isStringArray(as) // res4: String = yes

  • 변수바인딩; 변수가 하나만 있는 패턴 말고, 다른 패턴에 변수를 추가할 수 있습니다.

expr match {
  case UnOp("abs", e @ UnOp("abs", _)) => e
  case _ =>
}

패턴 가드

스칼라는 패턴을 선형 패턴으로 제한하기 때문에 어떤 패턴 변수가 한 패턴 안에 오직 한 번만 나와야 합니다. 그러나 패턴 가드를 사용하면 match 표현식을 다시 쓸 수 있습니다. 패턴 가드가 있으면, 가드가 true가 될 때만 매치에 성공합니다.


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

봉인된 클래스

봉인된 클래스(sealed class)는 그 클래스와 같은 파일이 아닌 다른 곳에서 새로운 서브클래스를 만들 수 없습니다. 이는 패턴 매치에서 아주 쓸모가 있는데, 같은 소스 파일에 정의가 있는 이미 알려진 서브클래스만 고려하면 되기 때문입니다. 봉인된 클래스를 상속한 케이스 클래스에 대해 패턴 매치를 시도하면, 컴파일러가 경고 메시지와 함께 놓친 패턴 조합을 환기해준다.


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

Option

스칼라에는 Option이라는 표준 타입이 있습니다. 이 타입은 선택적인 값을 표현하며, 두 가지 형태가 있습니다. x가 실제 값이라면 Some(x)라는 형태로 값이 있음을 표현할 수 있습니다. 반대로, 값이 없으면 None이라는 객체가 됩니다. 대조적으로, 스칼라에서는 선택적인 값을 나타내기 위해 Option을 사용하도록 권장합니다. 선택적인 값을 이런 식으로 처리하면 자바의 방식보다 여러 가지 좋은 점이 있습니다