web/ko/basics2.textile (235 lines of code) (raw):
---
prev: basics.textile
next: collections.textile
title: 기초(계속)
layout: post
---
이번 강좌에서 다루는 내용은 다음과 같다.
* "apply 메소드":#apply
* "객체":#object
* "함수도 객체다":#fnobj
* "패키지":#package
* "패턴 매치":#match
* "케이스 클래스":#caseclass
* "try-catch-finally 예외 처리":#exception
h2(#apply). apply 메소드
apply 메소드를 사용하면 클래스나 객체의 용도가 주로 하나만 있는 경우를 아주 멋지게 표현할 수 있다.
<pre>
scala> class Foo {}
defined class Foo
scala> object FooMaker {
| def apply() = new Foo
| }
defined module FooMaker
scala> val newFoo = FooMaker()
newFoo: Foo = Foo@5b83f762
</pre>
위와 같이 사용하거나, 다음과 같이 쓸 수 있다.
<pre>
scala> class Bar {
| def apply() = 0
| }
defined class Bar
scala> val bar = new Bar
bar: Bar = Bar@47711479
scala> bar()
res8: Int = 0
</pre>
apply를 정의하면 메소드를 호출하듯 객체 인스턴스를 호출할 수 있다. 객체 인스턴스를 호출하면 그 객체(클래스)에 정의된 apply()가 호출된다. 자세한 것은 나중에 살펴볼 것이다.
h2(#object). 객체
객체(여기서는 object 키워드로 선언하는 객체를 말함)는 클래스의 유일한 인스턴스를 넣기 위해 사용한다. 보통 팩토리에 사용된다.
<pre>
object Timer {
var count = 0
def currentCount(): Long = {
count += 1
count
}
}
</pre>
위와 같이 정의하면 다음과 같이 사용할 수 있다.
<pre>
scala> Timer.currentCount()
res0: Long = 1
</pre>
클래스와 객체가 같은 이름을 가질 수도 있다. 이런 객체는 '짝 객체(Companion Object)'라 한다. 보통 팩토리를 만들 때 짝 객체를 사용한다.
다음 예는 'new'를 사용하지 않고 새 객체를 만들 수 있음을 보여준다.
<pre>
class Bar(foo: String)
object Bar {
def apply(foo: String) = new Bar(foo)
}
</pre>
h2(#fnobj). 함수는 객체이다
스칼라에 대해 이야기할 떄, 객체-함수형 프로그래밍이라는 말을 하고는 한다. 그 말이 무슨 뜻일까? 함수란 실제로 무엇일까?
함수는 트레잇의 집합이다. 구체적으로 말하자면, 인자를 하나만 받는 함수는 <code>Function1</code> 트레잇의 인스턴스이다. 이 트레잇에는 앞에서 설명했던 <code>apply()</code>가 정의되어 있다. 따라서 함수를 호출하듯 객체를 호출할 수 있다.
<pre>
scala> object addOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined module addOne
scala> addOne(1)
res2: Int = 2
</pre>
스칼라에는 Function이 1부터 22까지 준비되어 있다. 22인 이유는? 그냥 그렇게 정한 것이다. 저자는 인자가 22개 보다 더 많이 필요한 함수를 본 적이 없다. 22개면 충분하리라 본다.
apply를 통한 편리 문법(syntactic sugar)을 통해 객체와 함수 프로그래밍 양쪽을 잘 통합할 수 있다. 여러분은 클래스를 여기저기 넘기면서 함수 처럼 호출해 사용할 수 있고, 함수는 한꺼풀 벗겨보면 단지 클래스의 인스턴스일 뿐이다.
그렇다면 클래스의 메소드를 정의할 때마다 실제로 Function*의 인스턴스가 만들어지는 걸까? 아니다. 클래스 내부의 메소드는 메소드이다. repl(read eval print loop. 입력을 받아 값을 계산하고 결과를 출력하는 루프. 스칼라 인터프리터라 생각하면 대략 맞다)에서 정의한 개별 메소드는 Function*의 인스턴스이다.
Function*을 확장한 클래스를 정의할 수도 있다. 물론 이런 클래스도 ()로 호출할 수 있다.
<pre>
scala> class AddOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined class AddOne
scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>
scala> plusOne(1)
res0: Int = 2
</pre>
<code>extends Function1[Int, Int]</code>는 <code>extends (Int => Int)</code>라고 더 알아보기 쉽게 쓸 수 있다.
<pre>
class AddOne extends (Int => Int) {
def apply(m: Int): Int = m + 1
}
</pre>
h2(#package). 패키지
코드를 패키지로 구성할 수 있다.
<pre>
package com.twitter.example
</pre>
위와 같이 파일의 맨 앞에서 선언하면 파일 내의 모든 것이 위 패키지 안에 포함된다.
값이나 함수는 클래스나 객체 바깥에 존재할 수 없다. 객체(여기서도 object로 선언한 객체를 의미함)를 사용하면 정적인(자바의 정적 함수와 동일) 함수를 관리하기 쉽다.
<pre>
package com.twitter.example
object colorHolder {
val BLUE = "Blue"
val RED = "Red"
}
</pre>
이제 직접 객체의 멤버를 사용할 수 있다.
<pre>
println("the color is: " + com.twitter.example.colorHolder.BLUE)
</pre>
여러분이 이렇게 객체를 정의하면 스칼라 repl은 다음과 같이 표시해준다.
<pre>
scala> object colorHolder {
| val Blue = "Blue"
| val Red = "Red"
| }
defined module colorHolder
</pre>
모듈이라고 repl이 응답하는 것에 유의하라. 이는 스칼라 언어를 설계시 객체를 모듈 시스템의 일부로 생각하고 설계했음을 보여준다.
h2(#match). 패턴 매칭
패턴 매치는 스칼라에서 가장 유용한 기능 중 하나이다.
값에 대해 매칭할 수 있다.
<pre>
val times = 1
times match {
case 1 => "one"
case 2 => "two"
case _ => "some other number"
}
</pre>
가드(조건문)를 사용해 매칭할 수 있다.
<pre>
times match {
case i if i == 1 => "one"
case i if i == 2 => "two"
case _ => "some other number"
}
</pre>
변수 'i'에 어떻게 값을 잡아 넣었는지 주의깊게 살펴보라.
마지막 경우의 <code>_</code>는 와일드카드이다. 즉, 모든 경우를 처리한다. 만약 이 부분이 없다면 매치되지 않는 값이 들어온 경우 런타임 에러가 발생할 것이다. 이에 대해서는 나중에 살펴보겠다.
*See Also* 효율적인 스칼라에서 <a href="https://twitter.github.com/effectivescala/#Functional programming-Pattern matching">패턴매치를 사용해야 하는 경우</a>와 <a href="https://twitter.github.com/effectivescala/#Formatting-Pattern matching">패턴 매칭을 어떤 형식으로 할지</a>에 대해 설명한다. 스칼라 여행에서도 <a href="https://www.scala-lang.org/node/120">패턴매칭</a>을 다룬다.
h3. 타입에 대해 매치시키기
<code>match</code>를 사용해 타입이 다른 값을 서로 다른 방식으로 처리 가능하다.
<pre>
def bigger(o: Any): Any = {
o match {
case i: Int if i < 0 => i - 1
case i: Int => i + 1
case d: Double if d < 0.0 => d - 0.1
case d: Double => d + 0.1
case text: String => text + "s"
}
}
</pre>
h3. 클래스 멤버에 대해 매치시키기
앞에서 봤던 계산기 예제를 다시 떠올려보자.
타입(계산기의 유형)에 따라 계산기를 구분하자.
<pre>
def calcType(calc: Calculator) = calc match {
case calc.brand == "HP" && calc.model == "20B" => "financial"
case calc.brand == "HP" && calc.model == "48G" => "scientific"
case calc.brand == "HP" && calc.model == "30B" => "business"
case _ => "unknown"
}
</pre>
아이구, 힘들어 죽겄다. 스칼라는 이런 처리를 쉽게 할 수 있는 도구를 제공한다.
h2(#caseclass). 케이스 클래스(case class)
케이스 클래스는 손쉽게 내용을 어떤 클래스에 저장하고, 그에 따라 매치를 하고 싶은 경우 사용한다. new를 사용하지 않고도 케이스 클래스의 인스턴스 생성이 가능하다.
<pre>
scala> case class Calculator(brand: String, model: String)
defined class Calculator
scala> val hp20b = Calculator("HP", "20b")
hp20b: Calculator = Calculator(hp,20b)
</pre>
케이스 클래스는 자동으로 생성자 인자에 따른 동등성 검사를 제공하며, 또한 보기 좋은 toString 메소드도 제공한다.
<pre>
scala> val hp20b = Calculator("HP", "20b")
hp20b: Calculator = Calculator(hp,20b)
scala> val hp20B = Calculator("HP", "20b")
hp20B: Calculator = Calculator(hp,20b)
scala> hp20b == hp20B
res6: Boolean = true
</pre>
케이스 클래스 안에도 일반 클래스와 똑같이 메소드를 정의할 수 있다.
h6. 케이스 클래스와 패턴 매칭
케이스 클래스는 패턴 매치와 사용하기 위해 설계된 것이다. 앞의 계산기 분류 예제를 간략하게 만들어보자.
<pre>
val hp20b = Calculator("HP", "20B")
val hp30b = Calculator("HP", "30B")
def calcType(calc: Calculator) = calc match {
case Calculator("HP", "20B") => "financial"
case Calculator("HP", "48G") => "scientific"
case Calculator("HP", "30B") => "business"
case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}
</pre>
마지막 매치는 다음과 같이 쓸 수도 있다.
<pre>
case Calculator(_, _) => "Calculator of unknown type"
</pre>
혹은, 그냥 calc가 계산기인지 아닌지도 명시하지 않아도 된다.
<pre>
case _ => "Calculator of unknown type"
</pre>
아니면, 매치된 값에 다른 이름을 붙일 수도 있다.
<pre>
case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
</pre>
h2(#exception). 예외
스칼라에서는 예외 처리시 try-catch-finally 문법에 패턴 매치를 사용할 수 있다.
<pre>
try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
} finally {
remoteCalculatorService.close()
}
</pre>
<code>try</code> 역시 식 중심의 구문이다.
<pre>
val result: Int = try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => {
log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
0
}
} finally {
remoteCalculatorService.close()
}
</pre>
이렇게 하는게 좋은 프로그램 스타일은 아니다. 위 내용은 단지 다른 대부분의 스칼라 구성 요소와 마찬가지로 try-catch-finally도 결과값을 내는 식임을 보여주기 위한 예일 뿐이다.
finally는 예외가 처리(catch)된 다음에 실행될 것이다. 이 부분은 전체 식의 일부가 아니다. 예외가 발생하지 않으면 try {} 안의 마지막 식의 값이 try-catch-finally 전체의 값이 되고, 예외가 발생하는 경우에는 catch 안의 식의 값이 전체 식의 최종 값이 된다.