# Scala School - 기초 Origin: http://twitter.github.io/scala_school/ko/basics.html ### [기초](https://hackmd.io/@2minchul/r1szGkqlr) * [Next»](https://hackmd.io/@2minchul/rk2_xJ5lr) 이번 강좌에서 다루는 내용은 다음과 같다. * [이번 강좌에 대해](#이번-강좌에-대해) * [식](#식) * [값](#값) * [함수](#함수) * [클래스](#클래스) * [상속](#상속) * [트레잇](#트레잇) * [타입](#타입) ## 이번 강좌에 대해 처음에는 기본적인 문법과 개념을 다룰 것이다. 그 후, 더 많은 연습문제를 통해 점점 범위를 넓힐 것이다. 예제 중 일부는 인터프리터에서 작성된 것을 보일 것이고, 나머지는 소스파일로 된 것을 보여줄것이다. 인터프리터가 있으면 문제가 되는 부분을 빠르게 찾아나갈 수 있을 것이다. ### 스칼라를 사용해야 하는 이유는? * 풍부한 표현력 * 1급 함수(First-class function) * 클로져(Closure) * 간결함 * 타입 추론 * 함수 리터럴(Literal) * 자바와의 혼용 가능 * 자바 라이브러리 재사용 가능 * 자바 도구를 재사용 가능 * 성능의 손실 없이 사용 가능 ### 스칼라를 어떻게 쓸 수 있나? * 자바 바이트코드로 컴파일됨 * 표준적인 자바 VM에서 잘 동작함 * 심지어는 달빅(Dalvik)과 같은 비표준 JVM에서도 동작 * 자바 컴파일러를 만든 사람이 스칼라 컴파일러도 만듦 ### 스칼라 방식으로 생각하자 스칼라는 단지 “더 나은 자바”가 아니다. 전혀 새로운 마음으로 스칼라를 배우기 바란다. 그래야 이 강좌를 넘어서는 많은 것을 배울 수 있다. ### 인터프리터 시작하기 `sbt console`로 시작하라. ``` $ sbt console [...] Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20). Type in expressions to have them evaluated. Type :help for more information. scala> ``` ## 식 ``` scala> 1 + 1 res0: Int = 2 ``` 식을 입력하면 인터프리터가 결과를 출력한다. 이때, res0는 결과 값에 인터프리터가 부여한 이름이다. 이 값의 타입은 Int이고, 값은 2라는 정수이다. 스칼라의 (거의) 모든 문장은 식이다. ## 값 식의 결과에 이름을 붙일 수 있다. ``` scala> val two = 1 + 1 two: Int = 2 ``` val로 어떤 값에 이름을 붙인 경우, 이 관계를 변경할 수 없다. ### 변수 값과 이름의 관계를 변경할 필요가 있다면, `var`를 사용해야만 한다. ``` scala> var name = "steve" name: java.lang.String = steve scala> name = "marius" name: java.lang.String = marius ``` ## 함수 def를 사용해 함수를 만들 수 있다. ``` scala> def addOne(m: Int): Int = m + 1 addOne: (m: Int)Int ``` 스칼라에서는 함수 인자의 타입을 명시해야 한다. 인터프리터는 기꺼이 타입 시그니쳐를 다시 표시해줄 것이다. ``` scala> val three = addOne(2) three: Int = 3 ``` 인자가 없는 함수의 경우 호출시 괄호를 생략할 수도 있다. ``` scala> def three() = 1 + 2 three: ()Int scala> three() res2: Int = 3 scala> three res3: Int = 3 ``` ### 이름 없는 함수 이름 없는 함수를 만들 수 있다. ``` scala> (x: Int) => x + 1 res2: (Int) => Int = <function1> ``` 위 함수는 x라는 이름의 Int에 1을 더한다. ``` scala> res2(1) res3: Int = 2 ``` 이름 없는 함수를 다른 함수나 식에 넘기거나 val에 저장할 수도 있다. ``` scala> val addOne = (x: Int) => x + 1 addOne: (Int) => Int = <function1> scala> addOne(1) res4: Int = 2 ``` 함수가 여러 식으로 이루어진 경우, {}를 사용해 이를 위한 공간을 만들 수 있다. ``` def timesTwo(i: Int): Int = { println("hello world") i * 2 } ``` 이름 없는 함수의 경우도 마찬가지이다. ``` scala> { i: Int => println("hello world") i * 2 } res0: (Int) => Int = <function1> ``` 이름 없는 함수를 넘길 때 위와 같은 형태를 자주 보게 될 것이다. ### 인자의 일부만 사용해 호출하기(부분 적용, Partial application) 함수 호출시 밑줄(_)을 사용해 일부만 적용할 수 있다. 그렇게 하면 새로운 함수를 얻는다. 스칼라에서 밑줄은 문맥에 따라 의미가 다르다. 하지만 보통 이름 없는 마법의 문자로 생각해도 된다. `{ _ + 2 }` 이라는 문맥에서 밑줄은 이름이 없는 매개변수를 가리킨다. 다음과 같이 이를 사용할 수 있다. ``` scala> def adder(m: Int, n: Int) = m + n adder: (m: Int,n: Int)Int ``` ``` scala> val add2 = adder(2, _:Int) add2: (Int) => Int = <function1> scala> add2(3) res50: Int = 5 ``` 인자 중에서 원하는 어떤 것이든 부분 적용이 가능하다. 꼭 맨 마지막 위치가 아니라도 아무 곳에서 밑줄을 넣을 수 있다. (역주: 사실은 _는 부분 적용하는 것에서 제외되는 위치가 혼동되지 않도록 표시해주는 역할을 한다. 혼동하면 안되는 것은 _이 부분적용이 아니고, _를 제외하고 호출하는 코드에 적힌 실제 인자가 부분적용의 인자값이라는 것이다. 다만 이 scala school에서는 _를 일관되게 부분 적용이라고 부르고 있지만, 역자 생각에 이는 틀린 것이다. 다만 의미의 혼동을 피하기 위해서 원저자의 표현을 그대로 사용할 것이다.) ### 커리 함수(Curried functions) 떄로 함수의 인자중 일부를 적용하고, 나머지는 나중에 적용하게 남겨두는 것이 더 쓸모있는 경우가 있다. 다음은 두 수를 곱하는 곱셈기를 만들 수 있는 함수이다. 첫 호출시 승수를 지정하고, 나중에 피승수를 지정할 수 있다. ``` scala> def multiply(m: Int)(n: Int): Int = m * n multiply: (m: Int)(n: Int)Int ``` 물론 두 인자를 한꺼번에 적용할 수도 있다. ``` scala> multiply(2)(3) res0: Int = 6 ``` 다음과 같이 첫 인자를 채워 넣고 두번째 인자를 부분적용 할 수도 있다. ``` scala> val timesTwo = multiply(2) _ timesTwo: (Int) => Int = <function1> scala> timesTwo(3) res1: Int = 6 ``` 인자가 여러개 있는 함수를 가지고 커리할 수 있다. 앞에서 본 `adder`에 적용해 보자. ``` scala> (adder _).curried res1: (Int) => (Int) => Int = <function1> ``` (연습문제(역자): 어떤 함수 f(x1,x2,….)의 커리화 된 함수를 어떻게 작성할 수 있을까? 이름없는 함수(무명함수 또는 람다식이라고도 한다)를 사용해 이를 구현해 보라.) ### 가변 길이 인자 동일한 타입의 매개변수가 반복되는 경우를 처리할 수 있는 특별한 문법이 있다. 여러 문자열에 동시에 `capitalize`를 호출하고 싶을 경우 다음과 같이 쓸 수 있다. ``` def capitalizeAll(args: String*) = { args.map { arg => arg.capitalize } } scala> capitalizeAll("rarity", "applejack") res2: Seq[String] = ArrayBuffer(Rarity, Applejack) ``` ## 클래스 ``` scala> class Calculator { | val brand: String = "HP" | def add(m: Int, n: Int): Int = m + n | } defined class Calculator scala> val calc = new Calculator calc: Calculator = Calculator@e75a11 scala> calc.add(1, 2) res1: Int = 3 scala> calc.brand res2: String = "HP" ``` 위 예와 같이 클래스 안에서 메소드는 def로, 필드는 val로 정의한다. 메소드는 단지 클래스(객체)의 상태를 억세스할 수 있는 함수에 지나지 않는다. ### 생성자 스칼라에서는 생성자가 특별한 메소드로 따로 존재하지 않는다. 클래스 몸체에서 메소드 정의 부분 밖에 있는 모든 코드가 생성자 코드가 된다. Calculator 예제를 생성자가 인자를 받아 내부 상태를 초기화하도록 변경해 보자. ``` class Calculator(brand: String) { /** * 생성자 */ val color: String = if (brand == "TI") { "blue" } else if (brand == "HP") { "black" } else { "white" } // 인스턴스 메소드 def add(m: Int, n: Int): Int = m + n } ``` 주석을 만드는 방법이 두가지 있다는 점에 유의하라. 생성자를 사용해 인스턴스를 만들어낼 수 있다. ``` scala> val calc = new Calculator("HP") calc: Calculator = Calculator@1e64cc4d scala> calc.color res0: String = black ``` ### 식 Calculator 예제를 보면 스칼라가 식 중심의 언어란 점을 잘 알 수 있다. color 값은 if/else 식에 의해 초기화되었다. 스칼라는 대부분의 구성 요소가 문(statement, 반환값이 없는 문장)이 아니고 식(expression, 결과를 반환하는 문장)이라는 점에서 식 중심의 언어이다. ### 곁다리: 함수 대 메소드 함수와 메소드는 서로 바꿔쓸 수 있는 개념이다. 함수와 메소드는 매우 유사하며, 실제 여러분이 호출 중인 어떤 _것_이 함수인지 메소드인지를 기억하지 못할 수도 있다. 실제 메소드와 함수의 차이와 마주치게 되면 혼동이 올 수도 있다. ``` scala> class C { | var acc = 0 | def minc = { acc += 1 } | val finc = { () => acc += 1 } | } defined class C scala> val c = new C c: C = C@1af1bd6 scala> c.minc // c.minc() 호출함 scala> c.finc // 함수 값(반환값이 아니라 함수 자체)을 반환함 res2: () => Unit = <function0> ``` 어떤 "함수"를 괄호 없이 호출할 수 있는데, 다른 것은 그렇게 할 수 없다면, _어라? 스칼라 함수가 어떻게 돌아가는지 잘 안다고 생각했는데, 안그런 모양이네. 때로 괄호를 꼭 써야하는 경우도 있나?_라고 생각할 지 모르겠다. 아마 함수라고 생각하고 있지만, 메소드를 사용하는 것이었을 수도 있다.(역주. 인자가 없는 메소드는 이름만으로 호출 가능하지만, 인자가 없는 함수는 이름만 사용하면 해당 함수를 의미하지, 그 함수를 적용한 값을 의미하지 않는다. ) 실제로는 메소드와 함수의 차이를 잘 모르더라도 스칼라로 훌륭한 일을 해낼 수 있다. 스칼라를 처음 접하는 독자가 [그 둘의 차이에 대한 설명](https://www.google.com/search?q=difference+scala+function+method)을 보면, 내용을 따라가기 어려울 수도 있다. 하지만 이는 스칼라 언어를 상당히 깊이 파고 들어야 그 둘의 차이를 구분할 수 있을 만큼 함수와 메소드의 차이가 미묘하다는 사실을 보여주는 것 뿐이다. 위 c.finc 부분의 결과를 보면 이를 알 수 있다.) ## 상속 ``` class ScientificCalculator(brand: String) extends Calculator(brand) { def log(m: Double, base: Double) = math.log(m) / math.log(base) } ``` **See Also** “효율적인 스칼라(effective scala)”에서는 하위클래스가 상위클래스와 실제 다르지 않을 경우 [타입 별명](https://twitter.github.com/effectivescala/#Types%20and%20Generics-Type%20aliases)이 `extends(확장)`보다 더 낫다고 말한다. 스칼라 여행(Tour of Scala)에서는 [상속하기(Subclassing)](https://www.scala-lang.org/node/125)에 대해 다루고 있다. ### 메소드 중복정의(Overloading) ``` class EvenMoreScientificCalculator(brand: String) extends ScientificCalculator(brand) { def log(m: Int): Double = log(m, math.exp(1)) } ``` ### 추상 클래스 _추상 클래스(abstract class)_는 메소드 정의는 있지만 구현은 없는 클래스이다. 대신 이를 상속한 하위클래스에서 메소드를 구현하게 된다. 추상 클래스의 인스턴스를 만들 수는 없다. ``` scala> abstract class Shape { | def getArea():Int // 하위클래스에서 이 메소드를 정의해야만 한다 | } defined class Shape scala> class Circle(r: Int) extends Shape { | def getArea():Int = { r * r * 3 } | } defined class Circle scala> val s = new Shape <console>:8: error: class Shape is abstract; cannot be instantiated val s = new Shape ^ scala> val c = new Circle(2) c: Circle = Circle@65c0035b ``` ## 트레잇(Traits, 특성이라는 뜻) `트레잇(trait)`은 다른 클래스가 확장(즉, 상속)하거나 섞어 넣을 수 있는(이를 믹스인Mix in 이라 한다) 필드와 동작의 모음이다. ``` trait Car { val brand: String } trait Shiny { val shineRefraction: Int } ``` ``` class BMW extends Car { val brand = "BMW" } ``` 클래스는 여러 트레잇를 `with` 키워드를 사용해 확장할 수 있다. ``` class BMW extends Car with Shiny { val brand = "BMW" val shineRefraction = 12 } ``` **See Also** 효율적인 스칼라도 [트레잇에](https://twitter.github.com/effectivescala/#Object oriented programming-Traits) 대해 다루고 있다. . **추상클래스 대신 트레잇를 사용해야 하는 경우는 언제인가?** 인터페이스 역할을 하는 타입을 설계할 때 트레잇과 추상클래스 중 어떤 것을 골라야 할까? 두 가지 다 어떤 동작을 하는 타입을 만들 수 있으며, 확장하는 쪽에서 일부를 구현하도록 요청한다. 중요한 규칙은 다음과 같다. * 트레잇을 사용하는 것이 낫다. 클래스는 오직 하나만 상속(extend)할 수 있지만, 트레잇은 여러 가지를 받아 사용할 수 있다. * 생성자 매개변수가 필요한 경우라면 추상 클래스를 사용하라. 추상 클래스의 생성자는 매개변수를 받을 수 있지만, 트레잇의 생성자는 그렇지 않다. 예를 들어 `trait t(i: Int) {}` 에서 `i` 매개변수는 허용되지 않는다. 당신이 이런 질문을 한 첫번째 사람은 아니다. 스택 오버플로우에서 [트레잇과 추상 클래스의 비교](https://stackoverflow.com/questions/1991042/scala-traits-vs-abstract-classes), [추상 클래스와 트레잇의 차이](https://stackoverflow.com/questions/2005681/difference-between-abstract-class-and-trait), 또는 [스칼라 프로그래밍: 트레잇냐 아니냐 그것이 문제로다?](https://www.artima.com/pins1ed/traits.html#12.7) 등을 참조하라. ## 타입 앞에서 숫자 타입중 하나인 `Int`를 인자로 받는 함수를 보았다. 모든 타입의 값을 처리할 수 있는 일반적(generic)인 함수를 만들 수도 있다. 일반적 함수를 만들 때는 각괄호([])안에 타입 매개변수를 추가한다. 아래는 키와 값을 가지는 일반적인 캐시를 보여준다. ``` trait Cache[K, V] { def get(key: K): V def put(key: K, value: V) def delete(key: K) } ``` 메소드에도 타입 매개변수를 추가할 수 있다. ``` def remove[K](key: K) ``` * [Next»](https://hackmd.io/@2minchul/rk2_xJ5lr) <hr /> 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).