Skip to content

kmh review2

minhee0405 edited this page May 6, 2016 · 20 revisions

1장. 확장 가능한 언어

객체지향 + 함수형 프로그래밍 + 정적타입 언어

스칼라는 자바와 JVM이라는 든든한 기반 위에서 함수 프로그래밍뿐 아니라 객체지향과 절차적 프로그래밍을 잘 버무린 언어.

  1. 함수 언어를 사용하면 타입 추론이 주는 간결한 표현과 타입 오류 예방 능력의 도움을 받을수 있다.
  2. 패턴매치를 활용하면 다양한 여러 경우를 중복 없고 누락없이 간편하게 기술할 수 있다.
  3. 컬렉션과 고차 함수, 클로저, 다양한 연산자를 활용하면 도메인 특화언어DSL를 함수 언어를 통해 정의할 수 있고, 이렇게 만든 DSL을 기존 시스템이나 다른 모듈들과 매끄럽게 통합해 활용할 수 있다.

함수형 프로그래밍 구성요소 -> 관심 대상을 간단한 부품으로부터 빠르고 쉽게 만들 수 있다.

객체지향 구성요소 -> 더큰 시스템을 구조화하고 새로운 요구에 맞춰 구조를 쉽게 변형할 수 있다.

위 두가지 스타일을 조합하여 새로운 유형의 프로그래밍 패턴과 컴포넌트 추상화를 표현할수 있다.

스칼라의 확장성

스칼라에서는 함수 값도 객체다. 함수 타입은 서브클래스가 상속할 수 있는 클래스다. 스칼라는 순수한 형태의 객체지향 언어이다. 모든 값이 객체이며, 모든 연산은 메소드 호출이다. 스칼라는 객체를 조합함에 있어서 뛰어나다 -> 스칼라의 트레이트(trait) (자바의 인터페이스와 비슷하다. 그러나 트레이트 안에서 메소드를 정의할 수있고, 필드도 정의할 수 있다.)

객체는 믹스인 조합을 통해 만들 수 있다. 믹스인은 한 클래스의 멤버에 다른 트레이트에서 가져온 멤버들을 추가하는것.

트레이트는 새로운 기능을 아무 서브클래스에나 섞어넣을 수있다. 따라서 끼워넣기 좋은 구성요소이다.(트레이트를 사용하면 다중 상속의 고전적 문제인 다이아몬드 상속을 피할수 있다. 다이아몬드 상속은 동일한 클래스를 여러 다른 경로를 통해 여러번 상속하는 경우 생긴다.)

스칼라는 함수형

  1. 함수가 1급 계층값이다. 함수형 언어에서 함수는 정수나 문자열과 동일한 자격을 갖는값이다. (일반적인 값하고 큰 차이없이 자유자재로 다룰 수 있는 값)
  2. 프로그램은 입력 값을 출력 값으로 변환해야 하며, 데이터를 그 자리에서 변경하지 말아야 한다. == 메소드에는 부수효과(side effect)가 없어야 한다. (스칼라에서 변경불가능한 데이터 타입 예: 리스트, 튜플, 맵, 집합)

메소드 부수효과(side effect): 주어진 입력에 대해, 프로그램 의미에 전혀 영향을 주지 않고 어떤 메소드 호출 부분을 그 메소드를 호출해 얻은 결과 값으로 치환할수 있다는말.

왜 스칼라인가?

호환성, 간결성, 고수준 추상화, 고급 타이핑

호환성: 스칼라 프로그램은 JVM 바이트코드로 컴파일된다. 실행성능은 자바 프로그램과 대등하다. 스칼라코드가 자바 메소드를 호출, 자바필드 접근, 자바 클래스 상속, 자바 인터페이스 구현등이 가능하다. 간결성: 동일한 프로그램을 자바로 작성했을 때에 비해 전형적인 스칼라 프로그램은 절반 정도 코드 줄 수가 줄어든다.

자바코드

class MyClass {
  private int index;
  private String name;
  public MyClass(int index, String name) {
    this.index= index;
    this.name = name;
  }
}

스칼라 코드

class MyClass(index: Int, name: String)

간결성: 스칼라의 타입추론(type inference)은 간결성을 가능하게 하는 요소다.

고수준 추상화: 스칼라를 사용하면 설계하고 사용하는 인터페이스의 추상화 수준을 높여서 복잡성을 관리 할 수 있다. -> 술어를 사용하여 높은 수준의 문자 시퀀스를 다룬다. _.isUpper는 스칼라의 함수 리터럴의 한 예다. 추상화를 사용하면 코드 중복을 피할 수 있고, 코드를 더 짧고 간결하게 만들수 있다.

고급 타이핑: 스칼라는 아주 진보적인 정적 타입 시스템을 가진 언어다. 장황함은 타입추론을 통해 피하며, 패턴매치와 타입을 쓰고 합성하는 새로운 방법들을 통해 유연성을 확보한다.

2장. 스칼라 첫걸음

1. 스칼라를 시작하는 가장 쉬운 방법은 스칼라 인터프리터

scala> 1+2
res0: Int =3 
  • 계산 결과 값을 나타낼 때 사용할 수 있는, 자동으로 만들어졌거나 사용자가 정의한 이름(여기서는 res0이다. 이는 0번째 결과 라는 뜻이다.)
  • 콜론(:) 과 결과의 타입(여기서는 Int)
  • 등호(=)
  • 사용자가 입력한 표현식을 계산에 얻은 결과 값(여기서는 3)

res번호 식별자는 나중에 사용할수있다.

scala> res0*3
res1: Int =9

2. 변수를 정의해보자

스칼라에는 두종류의 변수가 있다. val, var

  • val은 자바의 final 변수와 비슷하다. 일단 초기화 하고 나면 val을 결코 다시 할당할 수는 없다.
  • 반면 var는 자바의 변수와 비슷하다. var는 없어질때 까지 재할당이 가능하다.

3. 함수를 정의해보자.

함수 정의는 def로 시작한다.

스칼라 컴파일러는 파라미터 타입을 추론하지 않기 때문에 모든 파라미터에는 콜론과 함께 타입 지정을 해줘야한다.

def max (x: Int, y: Int) : Int = {
   if(x > y)
     x
   else
     y
} 
  • def는 함수 정의를 시작한다.
  • max는 함수 이름이다.
  • 괄호안에 파라미터 목록이 온다.
  • Int는 함수결과 타입
  • 중괄호 안에 함수 본문

함수가 재귀적이라면 함수 결과 타입을 반드시 명시해야한다.

함수 본문에 문장이 하나밖에 안들어 있다면, 중괄호를 생략할수 있다.

아무 파라미터도 받지 않고 관심이 있을만한 어떤 결과도 돌려주지 않는 함수의 결과 타입은 ()Unit (Unit 타입은 자바의 void 타입과 비슷)

결과타입이 Unit인 메소드는 부수 효과를 위해서만 실행하는 함수이다.

4. 스칼라 스크립트를 작성해보자.

스크립트는 파일에 스칼라 문장들을 넣는것을 말한다.

스칼라 스크립트는 args라는 스칼라 배열에 명령행 인자를 받는다. 스칼라 배열은0 부터 시작하며, 인덱스를 에 넣어서 배열의 원소에 접근 할 수 있다.

5. while문으로 돌고 if로 결정해보자.

-> 명령형 스타일(작동을 지시하는 명령을 한번에 하나씩 사용하고 루프로 이터레이션하면서, 여러 다른 함수 사이에 공유하는 상태를 변경한다.)

6. foreach와 for를 사용해 이터레이션을 해보자.

args.foreach(arg => println(arg))

arg에 있는 foreach메소드를 호출하고 인자로 함수를 넘긴다. 여기서 arg라는 파라미터를 받는 함수 리터럴을 사용했다. 이 함수의 본문은 println(arg)이다. 정상적인 함수 리터럴 문법은 다음과 같이 타입을 명시한다.

args.foreach((arg: String) => println(arg))

축약형으로 쓰면 다음과 같다. (함수 리터럴이 인자를 하나만 받는 문장인 경우 가능)

args.foreach(println)

스칼라 함수 리터럴의 문법

(x: Int, y: Int) => x+y

for(arg <- args)
   println(arg)

arg는 val의 이름이다. for루프를 돌때마다 arg에 새값이 들어가기 때문에, arg가 변수처럼 보일지 몰라고 실제로는 val이다. 그래서 for표현식의 본문에서 arg를 재할당 할 수 없다.

3장. 스칼라 두 번째 걸음

7. 배열타입 파라미터를 지정해보자.

스칼라에서는 new를 사용해 객체를 인스턴스화 할수 있다. 즉 클래스의 인스턴스를 만들 수 있다. 스칼라에서 객체를 인스턴스화 할 때, 값과 타입을 파라미터로 넘길 수 있다.

파라미터화 라는 말은 인스턴스를 생성할때 그 인스턴스를 설정한다는 뜻

새로운 java.math.BigInteger 인스턴스를 만들고 값 "12345"로 그 인스턴스를 파라미터화 한다.

val big = new java.math.BigInteger("12345")

: Array[String]형으로 타입 지정하여 명시적으로 나타낼수 있다.

val greetStrings: Array[String] = new Array[String] (3)
greetStrings(0) = "Hello"
greetStrings(1) = ","
greetStrings(2) = "world!\n"

위의 코드는 val의 의미와 관련해 중요한 의미를 갖는다. 어떤 변수를 val로 지정하면 그 변수를 재할당할 수 없다. 하지만 그 변수가 나타내는 객체는 잠재적으로 여전히 변경 가능하다. 따라서 greetStrings에 다른 변수를 넣을수는 없으며 항상 초기화 시 설정한것과 같은 Array[String] 타입의 배열을 가리킨다. 하지만 Array[String]의 원소는 언제나 변경가능하다. (배열 자체는 변경가능하다. -> mutable)

scala> 1+2 

1이라는 값의 Int객체, 1에대해 +라는 이름의 메소드를 호출한다, Int객체 2를 +메소드에 전달한다. (1).+(2)로 쓸 수도 있다.

어떤 변수 뒤에 괄호로 둘러싼 인자들이 있는 표현식에 할당을 하면 컴파일러는 괄호 안에 있는 인자와 등호 오른쪽의 값을 모두 인자로 넣어서 update 메소드를 호출한다.

greetStrings(0) = "Hello"   =>   greetStrings.update(0, "Hello")

스칼라에서 배열을 초기화 할수있는 더 간편한 방법은 다음과 같다.

val numNames = Array("zero", "one", "two")

apply라는 이름의 팩토리 메소드를 호출한다. 이 메소드는 새로운 배열을 만들어서 반환한다. apply메소드는 임의 개수의 인자를 받을 수 있다.

8. 리스트를 사용해보자.

메소드의 유일한 동작은 계산을 해서 값을 반환하는 것뿐이어야 한다. 이런 접근 방법을 택할 때 얻을 수 있는 이점은 메소드가 덜 얽히므로 더 많이 신뢰할수 있으며 재사용할수 있다는 점.

스칼라 배열: 같은 타입의 객체로 이뤄진 변경 가능 한 시퀀스(순서를 정해 원소를 나열 한것)

스칼라 리스트: 같은 타입의 객체로 이뤄진 변경 불가능한 시퀀스(순서를 정해 원소를 나열 한것)

스칼라 리스트 예

val oneTwoThree = List(1, 2, 3)

::: 라는 메소드는 두 리스트를 이어붙인다.

::(콘즈) 는 새 원소를 기존 리스트의 맨 앞에 추가한 새 리스트를 반환한다.

Nil 은 빈리스트를 의미한다.

왜 리스트 뒤에 추가하지 않을까? List 뒤에 원소를 추가하는 연산은 리스트의 길이에 비례한 시간이 걸리기 때문이다. 반면 ::를 사용해 맨 앞에 추가하는 것은 상수 시간이 걸린다.

리스트 끝에 Nil을 필요로 하는 이유는 ::가 List 클래스의 멤버이기 때문이다. 만약 1::2::3만을 사용했다면, 3은 Int라서 :: 메소드가 없기 때문에 컴파일 실패한다.

9. 튜플을 사용해보자.

튜플은 또 다른 유용한 컨테이너 객체이다. 리스트와 마찬가지로 변경 불가능하다. 하지만 튜플에는 각기 다른 타입의 원소를 넣을 수 있다. 튜플의 인덱스는 1부터 시작한다.

튜플을 만들고 사용해보자.

val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

10. 집합과 맵을 써보자.

집합과 맵에 대해서는 변경 가능한 것과 변경 불가능한 것을 모두 제공한다.

[집합]

변경 불가능한 집합을 만들고, 초기화하고, 사용하기

var jetSet = Set("Boeing", "Airbus")
jetSet +="Lear"
println(jetSet .contains("Cessna"))

+는 원소를 추가한 새로운 집합을 반환한다. 변경 불가능한 집합에서는 재할당 해야하므로 var로 선언

변경 가능한 집합을 만들고, 초기화하고, 사용하기

import scala.collection.mutable.Set
val jetSet2 = Set("Boeing", "Airbus")
jetSet2 +="Lear"
println(jetSet2)

mutable.set을 임포트하여 사용했기때문에 변경가능한 집합으로 초기화한다. 변경가능한 집합이므로 += 메소드가 있으며 jetSet2 +="Lear" 은 jetSet2.+=("Lear")라고 쓸수도 있다.

[맵]

변경 불가능한 맵을 만들고, 초기화하고, 사용하기

val romanNumeral = Map (1 -> "I", 2-> "II", 3->"III", 4->"IV", 5->"V" )
println(romanNumeral (4))

변경 가능 맵을 만들고, 초기화하고, 사용하기

import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap  += (1-> "Go to island.")
treasureMap  += (2-> "Find big X on ground.")
treasureMap  += (3-> "Dig.")
println(treasureMap(2))

11. 함수형 스타일을 인식하는 법을 배우자

코드에 var가 있다면 명령형 코드에 val가 있다면 함수형 스타일

12. 파일의 내용을 줄 단위로 읽자

4장. 클래스와 객체

클래스, 필드, 메소드, 세미콜론 추론, 싱글톤 객체에 대해서 배워보자.

클래스, 필드, 메소드

클래스는 객체에 대한 청사진. 클래스를 정의하고 나면, 그 클래스 청사진으로부터 new를 사용해 객체를 만들 수 있다. 클래스 정의 안에는 필드와 메소드를 넣을 수 있다. 이둘은 멤버라고 부른다.

필드는 val 이나 var로 정의하며 객체를 나타내는 변수이다.

-> 객체가 사용할 상태 또는 데이터를 담는다.

메소드는 def로 정의하며 실행할 코드를 담는다.

-> 데이터를 사용해 객체의 계산 업무를 담당한다.

클래스를 인스턴스화 할때 스칼라 런타임은 해당 객체의를 담아둘 메모리를 확보한다.

필드는 다른 말로 인스턴스 변수 라고도 한다.

객체의 강건성(robustness)을 추구하는 한 가지 중요한 방법은 객체의 상태(인스턴스 변수의 값 전체)를 해당 인스턴스가 살아 있는 동안 항상 바르게 유지하는 것이다.

  • 필드를 비공개로 만들어서 외부에서 직접 접근할 수 없게 유지한다.

메서드 파라미터는 val타입이다. val이 더 분석하기 쉽기때문이다. (값을 재할당 할수 없으므로 추적할 필요가 없다.)

각 메소드가 한 값을 계산하는 표현식인 것처럼 생각하라. 이렇게 계산한 값이 바로 메소드의 반환 값이다.

메소드의 결과 타입이 Unit인 경우는 부수 효과를 위해 사용한다는 뜻이다. 부수 효과는 보통 외부의 상태를 변경하거나 I/O를 수행하는 것으로 정의한다.

프로시저: 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다.

세미콜론 추론

스칼라 프로그램에서는 보통 문장끝의 세미콜론(;)을 생략할 수있다. 반면, 한줄에 여러 문장을 넣으려면 중간에 세미콜론을 넣어야 한다.

val s = "hello"; println(s)

싱글톤 객체

스칼라 클래스에는 정적멤버가 없다. 스칼라는 싱글톤 객체를 제공한다. 싱글톤 객체는 Object 키워드로 시작한다.

import scala.collection.mutable.Map

object ChecksumAccumulator {
   private val cache = Map[String, Int] ()
     def calculate(s: String): Int ={
        if(cache.contains(s))
            cache(s)
        else {
            val acc = new ChecksumAccumulator 
            for (c <- s)
               acc.add(c.toByte)
            val cs = acc.checksum()
            cache += (s -> cs)
        }           
      }
}

어떤 싱글톤 객체의 이름이 어떤 클래스와 같을때, 그 객체를 클래스의 동반객체라고 한다. (클래스와 동반객체는 반드시 같은 소스 파일안에 정의 해야한다.)

반대로 해당 클래스를 싱글톤 객체의 동반클래스라 부른다. -> 클래스와 동반객체는 상대방의 비공개 멤버에 접근할 수 있다.

싱글톤 객체는 타입을 정의하지 않는다.

싱글톤은 슈퍼클래스를 확장하거나 트레이트 믹스인할수 있다. 각 싱글톤 객체는 슈퍼클래스나 믹스인 트레이트의 인스턴스다.

클래스와 싱글톤 객체의 차이점

싱글톤 객체: 파라미터를 받을 수 없다. -> 싱글톤을 new로 인스턴스할 수 없기 때문

클래스: 파라미터를 받을 수 있다.

컴파일러는 각 싱글톤 객체를 합성한 클래스의 인스턴스로 구현하고, 이를 정적 변수가 참조한다.

동반 클래스가 없는 싱글톤 객체는 독립객체라 한다.

(필요한 도구메소드를 한데 모아두거나, 스칼라 애플리케이션의 진입점을 만들 때 사용)

스칼라 애플리케이션

스칼라 프로그램을 실행하려면 Array[String]을 유일한 인자로 받고 Unit를 반환하는 main이라는 메소드가 들어 있는 독립 싱글톤 객체 이름을 알아야 한다. 타입이 맞는 main 메소드만 있으면 어떤 독립 객체이든 애플리케이션 시작점 역할을 할 수 있다.

def main (args: Array[String]) {
}

Application 트레이트

트레이트를 사용하기 위해 정의할 싱글톤 뒤에 extends Application 이라고 써야한다. 그후 main 메소드 대신에 넣고 싶은 코드를 직접 중괄호 사이에 넣는다. -> Application트레이트 안에 적절한 시그니처의 main 메소드를 정의 했기 때문에 동작한다. 싱글톤 객체의 중괄호 사이에 있는 코드는 싱글톤 객체의 주생성자안에 들어가며, 클래스를 초기화할때 실행된다.

Clone this wiki locally