# Scala School - 기초 2 Origin: http://twitter.github.io/scala_school/ko/basics2.html ### [기초(계속)](https://hackmd.io/@2minchul/r1szGkqlr) * [«Previous](https://hackmd.io/@2minchul/ryPMp0teH) * [Next»](http://twitter.github.io/scala_school/ko/collections.html) 이번 강좌에서 다루는 내용은 다음과 같다. * [apply 메소드](#apply-메소드) * [객체](#객체) * [함수도 객체다](#함수도-객체다) * [패키지](#패키지) * [패턴 매치](#패턴-매치) * [케이스 클래스](#케이스-클래스) * [try-catch-finally 예외 처리](#try-catch-finally-예외-처리) ## apply 메소드 apply 메소드를 사용하면 클래스나 객체의 용도가 주로 하나만 있는 경우를 아주 멋지게 표현할 수 있다. ``` scala> class Foo {} defined class Foo scala> object FooMaker { | def apply() = new Foo | } defined module FooMaker scala> val newFoo = FooMaker() newFoo: Foo = Foo@5b83f762 ``` 위와 같이 사용하거나, 다음과 같이 쓸 수 있다. ``` scala> class Bar { | def apply() = 0 | } defined class Bar scala> val bar = new Bar bar: Bar = Bar@47711479 scala> bar() res8: Int = 0 ``` apply를 정의하면 메소드를 호출하듯 객체 인스턴스를 호출할 수 있다. 객체 인스턴스를 호출하면 그 객체(클래스)에 정의된 apply()가 호출된다. 자세한 것은 나중에 살펴볼 것이다. ## 객체 객체(여기서는 object 키워드로 선언하는 객체를 말함)는 클래스의 유일한 인스턴스를 넣기 위해 사용한다. 보통 팩토리에 사용된다. ``` object Timer { var count = 0 def currentCount(): Long = { count += 1 count } } ``` 위와 같이 정의하면 다음과 같이 사용할 수 있다. ``` scala> Timer.currentCount() res0: Long = 1 ``` 클래스와 객체가 같은 이름을 가질 수도 있다. 이런 객체는 ‘짝 객체(Companion Object)’라 한다. 보통 팩토리를 만들 때 짝 객체를 사용한다. 다음 예는 ’new’를 사용하지 않고 새 객체를 만들 수 있음을 보여준다. ``` class Bar(foo: String) object Bar { def apply(foo: String) = new Bar(foo) } ``` ## 함수는 객체이다 스칼라에 대해 이야기할 떄, 객체-함수형 프로그래밍이라는 말을 하고는 한다. 그 말이 무슨 뜻일까? 함수란 실제로 무엇일까? 함수는 트레잇의 집합이다. 구체적으로 말하자면, 인자를 하나만 받는 함수는 `Function1` 트레잇의 인스턴스이다. 이 트레잇에는 앞에서 설명했던 `apply()`가 정의되어 있다. 따라서 함수를 호출하듯 객체를 호출할 수 있다. ``` scala> object addOne extends Function1[Int, Int] { | def apply(m: Int): Int = m + 1 | } defined module addOne scala> addOne(1) res2: Int = 2 ``` 스칼라에는 Function이 1부터 22까지 준비되어 있다. 22인 이유는? 그냥 그렇게 정한 것이다. 저자는 인자가 22개 보다 더 많이 필요한 함수를 본 적이 없다. 22개면 충분하리라 본다. apply를 통한 편리 문법(syntactic sugar)을 통해 객체와 함수 프로그래밍 양쪽을 잘 통합할 수 있다. 여러분은 클래스를 여기저기 넘기면서 함수 처럼 호출해 사용할 수 있고, 함수는 한꺼풀 벗겨보면 단지 클래스의 인스턴스일 뿐이다. 그렇다면 클래스의 메소드를 정의할 때마다 실제로 Function*의 인스턴스가 만들어지는 걸까? 아니다. 클래스 내부의 메소드는 메소드이다. repl(read eval print loop. 입력을 받아 값을 계산하고 결과를 출력하는 루프. 스칼라 인터프리터라 생각하면 대략 맞다)에서 정의한 개별 메소드는 Function*의 인스턴스이다. Function*을 확장한 클래스를 정의할 수도 있다. 물론 이런 클래스도 ()로 호출할 수 있다. ``` 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 ``` `extends Function1[Int, Int]`는 `extends (Int => Int)`라고 더 알아보기 쉽게 쓸 수 있다. ``` class AddOne extends (Int => Int) { def apply(m: Int): Int = m + 1 } ``` ## 패키지 코드를 패키지로 구성할 수 있다. ``` package com.twitter.example ``` 위와 같이 파일의 맨 앞에서 선언하면 파일 내의 모든 것이 위 패키지 안에 포함된다. 값이나 함수는 클래스나 객체 바깥에 존재할 수 없다. 객체(여기서도 object로 선언한 객체를 의미함)를 사용하면 정적인(자바의 정적 함수와 동일) 함수를 관리하기 쉽다. ``` package com.twitter.example object colorHolder { val BLUE = "Blue" val RED = "Red" } ``` 이제 직접 객체의 멤버를 사용할 수 있다. ``` println("the color is: " + com.twitter.example.colorHolder.BLUE) ``` 여러분이 이렇게 객체를 정의하면 스칼라 repl은 다음과 같이 표시해준다. ``` scala> object colorHolder { | val Blue = "Blue" | val Red = "Red" | } defined module colorHolder ``` 모듈이라고 repl이 응답하는 것에 유의하라. 이는 스칼라 언어를 설계시 객체를 모듈 시스템의 일부로 생각하고 설계했음을 보여준다. ## 패턴 매칭 패턴 매치는 스칼라에서 가장 유용한 기능 중 하나이다. 값에 대해 매칭할 수 있다. ``` val times = 1 times match { case 1 => "one" case 2 => "two" case _ => "some other number" } ``` 가드(조건문)를 사용해 매칭할 수 있다. ``` times match { case i if i == 1 => "one" case i if i == 2 => "two" case _ => "some other number" } ``` 변수 ’i’에 어떻게 값을 잡아 넣었는지 주의깊게 살펴보라. 마지막 경우의 `_`는 와일드카드이다. 즉, 모든 경우를 처리한다. 만약 이 부분이 없다면 매치되지 않는 값이 들어온 경우 런타임 에러가 발생할 것이다. 이에 대해서는 나중에 살펴보겠다. **See Also** 효율적인 스칼라에서 [패턴매치를 사용해야 하는 경우](https://twitter.github.com/effectivescala/#Functional programming-Pattern matching)와 [패턴 매칭을 어떤 형식으로 할지](https://twitter.github.com/effectivescala/#Formatting-Pattern matching)에 대해 설명한다. 스칼라 여행에서도 [패턴매칭](https://www.scala-lang.org/node/120)을 다룬다. ### 타입에 대해 매치시키기 `match`를 사용해 타입이 다른 값을 서로 다른 방식으로 처리 가능하다. ``` 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" } } ``` ### 클래스 멤버에 대해 매치시키기 앞에서 봤던 계산기 예제를 다시 떠올려보자. 타입(계산기의 유형)에 따라 계산기를 구분하자. ``` 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" } ``` 아이구, 힘들어 죽겄다. 스칼라는 이런 처리를 쉽게 할 수 있는 도구를 제공한다. ## 케이스 클래스(case class) 케이스 클래스는 손쉽게 내용을 어떤 클래스에 저장하고, 그에 따라 매치를 하고 싶은 경우 사용한다. new를 사용하지 않고도 케이스 클래스의 인스턴스 생성이 가능하다. ``` scala> case class Calculator(brand: String, model: String) defined class Calculator scala> val hp20b = Calculator("HP", "20b") hp20b: Calculator = Calculator(hp,20b) ``` 케이스 클래스는 자동으로 생성자 인자에 따른 동등성 검사를 제공하며, 또한 보기 좋은 toString 메소드도 제공한다. ``` 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 ``` 케이스 클래스 안에도 일반 클래스와 똑같이 메소드를 정의할 수 있다. ###### 케이스 클래스와 패턴 매칭 케이스 클래스는 패턴 매치와 사용하기 위해 설계된 것이다. 앞의 계산기 분류 예제를 간략하게 만들어보자. ``` 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) } ``` 마지막 매치는 다음과 같이 쓸 수도 있다. ``` case Calculator(_, _) => "Calculator of unknown type" ``` 혹은, 그냥 calc가 계산기인지 아닌지도 명시하지 않아도 된다. ``` case _ => "Calculator of unknown type" ``` 아니면, 매치된 값에 다른 이름을 붙일 수도 있다. ``` case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c) ``` ## 예외 스칼라에서는 예외 처리시 try-catch-finally 문법에 패턴 매치를 사용할 수 있다. ``` 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() } ``` `try` 역시 식 중심의 구문이다. ``` 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() } ``` 이렇게 하는게 좋은 프로그램 스타일은 아니다. 위 내용은 단지 다른 대부분의 스칼라 구성 요소와 마찬가지로 try-catch-finally도 결과값을 내는 식임을 보여주기 위한 예일 뿐이다. finally는 예외가 처리(catch)된 다음에 실행될 것이다. 이 부분은 전체 식의 일부가 아니다. 예외가 발생하지 않으면 try {} 안의 마지막 식의 값이 try-catch-finally 전체의 값이 되고, 예외가 발생하는 경우에는 catch 안의 식의 값이 전체 식의 최종 값이 된다. <hr /> * [Next»](collections.html) Built at [@twitter](https://twitter.com/twitter) by [@stevej](https://twitter.com/stevej), [@marius](https://twitter.com/marius), and [@lahosken](https://twitter.com/lahosken) with much help from [@evanm](https://twitter.com/evanm), [@sprsquish](https://twitter.com/sprsquish), [@kevino](https://twitter.com/kevino), [@zuercher](https://twitter.com/zuercher), [@timtrueman](https://twitter.com/timtrueman), [@wickman](https://twitter.com/wickman), [@mccv](https://twitter.com/mccv) and [@garciparedes](https://github.com/garciparedes); Russian translation by [appigram](https://github.com/appigram); Chinese simple translation by [jasonqu](https://github.com/jasonqu); Korean translation by [enshahar](https://github.com/enshahar); Licensed under the [Apache License v2.0](https://www.apache.org/licenses/LICENSE-2.0).