Skip to content
meye edited this page May 11, 2017 · 2 revisions

Ref : https://gist.github.com/meye/154d09a95c16e67e2b633953c810c71a

1장

스칼라

  • 확장 가능 언어
  • 객체지향과 함수형 프로그래밍 개념을 정적 타입 언어에 합쳐 놓은 언어

확장성

타입

  • 언어가 기본 지원하는 것처럼 느껴지는 스칼라 표준 라이브러리로 타입을 제공
  • 라이브러리로 새로운 타입을 만들어 내장 타입처럼 편하게 사용할 수 있음

제어구조

  • 액터 모델을 구현하는 라이브러리를 제공하여 스칼라 내장 구성요소처럼 사용함
  • 새로운 분야에 대한 추상화를 설계/구현하고 언어가 원래 지원하는 것처럼 사용 가능

확장성의 이유

객체지향적

  • 스칼라는 순수한 형태의 객체지향 언어 : 모든 값은 객체, 모든 연산은 메소드
  • Trait : 끼워넣기 좋은 구성 요소

함수형

  • 함수는 1급 계층 값 : 인자로 넘길 수 있고, 변수에 저장 가능하고, 함수에서 반환 가능
  • 메소드에는 부수 효과가 없어야 함

스칼라 장점

  • 호환성 : 자바 메소드 호출, 필드 접근, 클래스 상속, 인터페이스 구현 가능
  • 간결함 : 적은 얼개 코드 사용, 타입 추론, 라이브러리 정의 도구
  • 고수준 : 높은 수준 추상화 가능
  • 정적타입
    • 타입 추론으로 장황함 회피, 패턴 매치와 타입을 쓰고 합성하는 방법으로 유연성 확보
    • 프로퍼티 검증, 안전한 리펙토링, 문서화

스칼라의 뿌리

  • 문법 : 자바, C#
  • 일관성 있는 객체 모델 : 스몰토크, 루비
  • 보편적인 내포 : 알골, 시뮬라, Beta, gbeta
  • 메소드 호출, 필드 선택을 통일하게 처리하는 단일 접근 원칙 : Eiffel
  • 함수형 프로그래밍 접근 방식 : SML, OCaml, F#
  • 스칼라 표준 라이브러리의 고차 함수 중 다수 : ML, 하스켈
  • 암시적 파라미터 : 하스켈의 타입 클래스
  • 액터 기반 동시성 라이브러리 : 얼랑

2장

인터프리터 사용법

  • 대화형 셸 $ scala
  • res번호 식별자는 변수 처럼 사용 가능 ex> res0 * 3
  • println("Hello, world!")

변수 정의

  • val : 재할당 불가능
  • var : 재할당 가능
  • 타입 추론으로 타입을 지정하지 않아도 됨
val msg = "Hello"
val msg2: java.lang.String = "Hello"
val msg3: String = "Hello"

var msg4 = "Hello"
msg4 = "World"

함수 정의

def max(x: Int, y: Int): Int = {
    if (x < y) x
    else y
}
  • def : 함수 정의 시작
  • max : 함수 이름
  • (x: Int, y: Int) : 인자 항목
  • Int : 함수 결과 타입
  • = : 등호
  • { } : 함수 본문

Unit 결과 타입은 함수가 의미있는 결과를 반환하지 않는다는 의미

스칼라 스크립트

  • 실행 방법 예 $ scala hello.scala
  • 명령행 인자 접근 : args args(0), args(1), ...

반복 / 분기

  • while while (i < args.length)
  • if if (i != 0) print(" ")
  • foreach args.foreach(arg => println(arg)) args.foreach(println)
  • for for (arg <- args) println(arg)

함수 리터럴

(x: Int, y: Int) => x + y
  • (x: Int, y: Int) : 인자 항목
  • => : 오른쪽 화살표
  • x + y : 함수 본문

함수 리터럴이 인자를 하나만 받는 문장인 경우에는 해당 인자에 이름을 붙일 필요가 없다 (8.6절)

3장

배열

  • 파라미터화(parameterization) : new를 사용해 객체를 인스턴스화 할때에 값과 타입을 파라미터로 전달하는 것
val greetStrings: Array[String] = new Array[String](3)
  • 인스턴스 타입은 타입 파라미터 까지만 포함됨 : (3) 은 제외
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "World!\n"
for (i <- 0 to 2)
  print(greetStrings(i))
  • 메소드가 파라미터를 하나만 요구하는 경우, 그 메소드를 점(.)과 괄호 없이 호출할 수 있다. 단, 호출 대상 객체가 명시적으로 지정되어 있는 경우에 가능 0 to 2 === (0).to(2)
  • 스칼라는 전통적인 의미의 연산자가 없으므로 연산자 오버로드를 제공하지 않으며, 대신 +, -, *, / 등의 문자를 메소드 이름으로 사용 가능 1 + 2 === (1).+(2)
  • 배열도 평범한 인스턴스 : 변수 뒤에 하나 이상의 값을 괄호로 둘러싸서 호출하면, 스칼라는 변수에 대해 apply 메소드 호출 greetStrings(i) === greetStrings.apply(i)
  • 변수 뒤에 괄호로 둘러싼 인자들이 있는 표현식에 할당을 하면, 스칼라는 update 메소드를 호출 greeStrings(0) = "Hello" === greetStrings.update(0, "Hello")
val numNames = Array("zero", "one", "two")
  • Array의 동반 객체에 정의된 apply라는 팩토리 메소드 호출

리스트

같은 타입의 변경 불가능한 시퀀스

val oneTwo = List(1,2)
val threeFour = List(3,4)
val oneTwoThreeFour = oneTwo ::: threeFour
  • 리스트 자체를 변환하지 않고 새 값을 갖는 리스트를 만든다.
val twoThree = List(2,3)
val oneTwoThree = 1 :: twoThree
  • : 로 이름이 끝나는 메소드는 오른쪽 피연산자에 대해 호출한다.
val oneTwoThree = 1 :: 2 :: 3 :: Nil
  • List() === Nil

리스트 메소드

  • List() / Nill : 빈 리스트
  • List(1,2,3) / 1 :: 2 :: 3 :: Nill : 원소들로 구성된 리스트
  • List(1, 2) ::: List(3, 4) : 두 리스트를 연결한 리스트
  • thrill(2) : thrill 리스트의 세번째 원소
  • thrill.count(s => s.length == 4) : 길이가 4인 것의 갯수
  • thrill.drop(2) : 처음 두 원소를 뺀 리스트
  • thrill.dropRight(2) : 마지막 두 원소를 뺀 리스트
  • thrill.exists(s => s == "until") : "until"이 리스트에 있는지 여부
  • thrill.filter(s => s.length == 4) : 길이가 4인 것들의 리스트
  • thrill.forall(s => s.endsWith("l")) : 모든 원소가 끝이 "l"로 끝나는지 여부
  • thrill.foreach(s => print(s)) : 리스트의 모든 원소에 대해서 print 실행
  • thrill.foreach(print) : 상동
  • thrill.head : 첫번째 원소
  • thrill.init : 마지막 원소 제외한 나머지
  • thrill.isEmpty : 리스트가 비어있는지 여부
  • thrill.last : 마지막 원소
  • thrill.length : 리스트의 길이
  • thrill.map(s => s + "y") : 각 원소 뒤에 "y"를 추가한 리스트
  • thrill.mkString(", ") : 원소들을 ", "로 연결한 문자열
  • thrill.remove(s => s.length == 4) : 길이가 4인 것을 제외한 리스트
  • thrill.reverse : 원소를 역순으로 한 리스트
  • thrill.sort((s, t) => s.charAt(0).toLower < t.charAt(0).toLower) : 알파벳 순서 정렬한 리스트
  • thrill.tail : 첫번째 원소를 제외한 리스트

튜플

다른 타입의 원소들을 갖는 변경 불가능한 시퀀스

val pair = (99, "Luftballons")
println(pair._1) // 99
println(pair._2) // Luftballons
  • apply 메소드는 항상 동일한 타입 객체를 반환하기 때문에 pair(0)과 같이 할 수 없음.
  • 하스켈, ML 등의 영향으로 인덱스가 1부터 시작

집합과 맵

변경가능 / 불가능 모두 제공

변경 불가능 Set

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"

변경 가능 Set

import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"

변경 불가능 Map

val romanNumeral = Map(
    1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
  • -> 메소드를 호출하면 해당 객체를 키로하고 인자를 값으로 하는 원소가 2개인 튜플을 생성

변경 가능 Map

import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap += (1 -> "Go to Island.")

함수형 스타일

  • 코드에 var가 있으면 명령형 스타일, 아니면 함수형 스타일
  • val, 불변객체, 부수 효과 없는 메소드를 먼저 사용해보고, 꼭 필요한 경우에만 var, 가변객체, 부수 효과 있는 메소드를 사용하라

파일

import scala.io.Source
if (args.length > 0) {
    for (line <- Source.fromFile(args(0)).getLines())
        println(line.length + " " + line)
}
else
    Console.err.println("Please enter filename")

4장

클래스

  • 클래스 정의
class ChecksumAccumulator {
    // 클래스 정의
}
  • 객체 생성 : new
new ChecksumAccumulator
  • 필드, 메소드는 멤버

필드

  • 필드 정의 : val, var
  • 인스턴스 변수라고도 함
  • 기본 접근 수준은 전체 공개
  • 비공개 필드 : private
class ChecksumAccumulator {
    private var sum = 0
}

메소드

  • 메소드 정의 : def
  • 메소드 파라미터는 val
  • 권장 스타일
    • return을 명시적으로 사용하지 않는 것 -> 메소드는 한 값을 계산하는 표현식
    • 하나의 표현식만 있으면 중괄호 생략
    • 결과식이 짧으면 def 문 줄에 함께 쓰기
class ChecksumAccumulator {
    private var sum = 0
    def add(b: Byte): Unit = sum += b
    def checksum(): Int = ~(sum & 0xFF) + 1
}
  • 결과 타입이 Unit 인 경우
    • 부수 효과를 위해 사용
    • = 생략하고 {}로 감싸는 것으로 표현 가능
    def add(b:Byte) { sum += b }

세미콜론 추론

다음 세가지 경우가 아니면 줄의 끝은 세미콜론으로 취급된다.

  1. 줄이 명령을 끝낼 수 있는 단어로 끝나지 않는다. 마침표(.)나 중위 연산자 등의 줄의 맨 끝에 있는 경우
  2. 다음 줄의 맨 앞이 문장을 시작할 수 없는 단어로 시작한다.
  3. 줄이 () 사이나 [] 사이에서 끝난다.

싱글톤 객체

  • 클래스 정의와 비슷하나 object 키워드 사용
object ChecksumAccumulator {
    // ...
}
  • 싱글톤 객체는 1급 계층
  • 파라미터를 전달할 방법 없음
  • 클래스를 확장(extend)하거나 트레이트를 믹스인(mix in) 가능
  • 자체 타입을 정의하지는 않음

동반 객체와 동반 클래스

  • 동반 객체(companion object) : 어떤 클래스 이름과 동일한 이름의 싱글톤 객체
  • 동반 클래스(companion class) : 어떤 싱글톤 객체와 동일한 이름의 클래스
  • 독립 객체(standalone object) : 동반 클래스가 없는 싱글톤 객체
  • 클래스와 동반 객체는 서로의 비공개 멤버에 접근할 수 있다.

스칼라 애플리케이션

  • 다음과 같은 main 메소드를 가진 독립 싱글톤 객체가 애플리케이션의 시작점
    • 인자 : Array[String]
    • 반환값 : Unit
object Summer {
    def main(args: Array[String]) {
        // ...
    }
}
  • 클래스 이름과 파일이름을 동일하게 만드는 것이 강제 사항은 아님.
  • 컴파일 : scalac, fsc

Application 트레이트

object FallWinterSpringSummer extends Application {
    // main 메소드 내용
}
  • scala.Application -> scala.App

5장

기본 타입

  • Byte / Short / Int / Long : 8 / 16 / 32 / 64 bits 부호 있는 정수(2의 보수)
  • Char : 16 bits 유니코드 문자
  • String : Char 시퀀스
  • Float / Double : 32 / 64 bits IEEE 754 단정도/배정도 부동소수점 수
  • Boolean : true / false

리터럴

정수

  • 10진 : 5, 7, ..
  • 16진 : 0x5, 0x00FF
  • Long : 35L, 31l

부동소수점

  • 1.2345
  • 1,2345e1
  • 123E45
  • Float : 1.2345F, 3e5f
  • Double(기본) : 3e5D

문자

  • 'A'
  • '\101'
  • '\u0041'

이스케이프 시퀀스(escape sequence)

  • \n : 줄바꿈(\u000A)
  • \b : 백스페이스(\u0008)
  • \t : 탭(\u0009)
  • \f : 페이지 넘김(\u000C)
  • \r : 줄 맨 앞으로(\u000D)
  • " : 큰따옴표(\u0022)
  • ' : 작은따옴표(\u0027)
  • \ : 역슬래스(\u005C)

문자열

  • "hello"
  • """Welcome ... """
  • stripMargin
println("""|Welcome to Ultamix 3000.
           |Type "HELP" for help.""".stripMargin)

심볼

  • 작은따옴표 뒤에 오는 식별자(알파벳,숫자) : 'ident
  • 'ident -> 내부에서 Symbol("ident") 호출됨
  • intern. 같은 심볼 리터럴은 동일한 객체를 참조.

불리언

  • true / false

연산자

  • 어떤 메소드든지 연산자가 될 수 있음
s.indexOf('o', 5)
s indexOf ('o', 5) // 동일함
  • 연산자는 메소드
    • 중위 연산자 : 1 + 2 == (1).+(2)
    • 전위 연산자 : -2.0 == (2.0).unary_-
    • 후위 연산자 : s toLowerCase == s.toLowerCase()

산술 연산자

  • +, -, *, /, %

관계, 논리 연산자

  • , <, >=, <=, !

  • &&, ||

비트 연산자

  • &, |, ^

객체 동일성

  • ==, != 사용하여 두 객체가 동일한지 여부를 확인
  • null을 포함한 어떤 객체라도 사용 가능
  • 내부적으로 null인지 확인하고, null 이 아닌 경우 equals 메소드를 수행함

연산자 우선순위

  • 연산자 시작하는 문자에 따라서 아래와 같은 순서로 우선 순위 판단
  1. (all other special characters)
  2. */%
  3. +-
  4. :
  5. =! <> &
  6. ˆ
  7. |
  8. (all letters)
  9. (all assignment operators)
  • 메소드가 ':'로 끝나면 오른쪽에서 왼쪽으로 연산되고, 다른 경우는 왼쪽에서 오른쪽으로 연산

리치 래퍼

  • 묵시적 변환(implicit conversion)으로 기본 타입에 더 많은 메소드를 실행 가능
0 max 5             // 5
0 min 5             // 0
-2.7 abs            // 2.7
-2.7 round          // -3L
1.5 isInfinity      // false
(1.0/0) isInfinity  // true
4 to 6              // Range(4,5,6)
"bob" capitalize    // "Bob"
"robert" drop 2     // "bert"
  • 위와 같은 메소드는 다음과 같은 레퍼 타입에 정의되어 있음
    • Byte - scala.runtime.RichByte
    • Short - scala.runtime.RichShort
    • Int - scala.runtime.RichInt
    • Char - scala.runtime.RichChar
    • Float - scala.runtime.RichFloat
    • Double - scala.runtime.RichDouble

6장

생성자

주 생성자

class Rational(n: Int, d: Int)
  • 클래스 파라미터 : n, d

주 생성자 코드

  • 클래스 내부에 있으면서 필드나 메소드 정의에 들어 있지 않은 코드는 주 생성자에 들어감
class Rational(n: Int, d: Int) {
    println("Created "+ n +"/"+d)
}

toString 메소드 오버라이드

class Rational(n: Int, d: Int) {
    override def toString = n +"/"+ d
}

전제 조건 확인

  • require 문 사용
  • 인자가 false 이면 IllegalArgumentException 예외 발생
class Rational(n: Int, d: Int) {
    require(d != 0)
    override def toString = n +"/"+ d
}

공개 필드

class Rational(n: Int, d: Int) {
    require(d != 0)
    val numer: Int = n
    val denom: Int = d
    override def toString = n +"/"+ d
    def add(that: Rational): Rational =
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )
}
  • 파라미터 필드(10.6절)를 사용하면 간단하게 작성 가능

자기 참조

  • this
def lessThan(that: Rational) =
    this.numer * that.denom < that.numer * this.denom // this 생략 가능
def max(that: Rational) =
    if (this.lessThan(that)) that else this // lessThan 앞의 this는 생략 가능

보조 생성자

  • def this(...) 으로 시작
class Rational(n: Int, d: Int) {
    require(d != 0)
    val numer: Int = n
    val denom: Int = d
    def this(n: Int) = this(n, 1) // 보조 생성자
    override def toString = n +"/"+ d
    def add(that: Rational): Rational =
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )
}

비공개 필드, 메소드

class Rational(n: Int, d: Int) {
    require(d != 0)
    private val g = gcd(n.abs, d.abs) // 비공개 필드
    val numer: Int = n / g // 초기화 코드
    val denom: Int = d / g // 초기화 코드
    def this(n: Int) = this(n, 1)
    override def toString = n +"/"+ d
    def add(that: Rational): Rational =
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )
    private def gcd(a: Int, b: Int): Int = // 비공개 메소드
        if (b == 0) a else gcd(b, a % b)
}

연산자 메소드

class Rational(n: Int, d: Int) {
    require(d != 0)
    private val g = gcd(n.abs, d.abs)
    val numer: Int = n / g
    val denom: Int = d / g
    def this(n: Int) = this(n, 1)
    override def toString = n +"/"+ d
    def + (that: Rational): Rational = // 덧셈 연산자 정의
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )
    def * (that: Rational): Rational = // 곱셈 연산자 정의
        new Rational(numer * that.numer, denom * that.denom)
    private def gcd(a: Int, b: Int): Int =
        if (b == 0) a else gcd(b, a % b)
}

식별자 규칙

  • 영숫자 식별자 : 문자나 밑줄(_)로 시작. 문자, 숫자, 밑줄 가능
  • 필드, 메소드 인자, 지역 변수, 함수 등 : camelCase 사용이 관례
  • 클래스, 트레이트, 상수 등 : CamelCase 사용이 관례
  • 연산자 식별자 : 하나 이상의 연산자 문자로 이루어져 있음. + ++ ::: <?> :->
  • 혼합 식별자 : unary_+ myvar_=
  • 리터럴 식별자 : 역따옴표로 둘러싼 문자열

메소드 오버로드

class Rational(n: Int, d: Int) {
    require(d != 0)
    private val g = gcd(n.abs, d.abs)
    val numer: Int = n / g
    val denom: Int = d / g
    def this(n: Int) = this(n, 1)
    override def toString = n +"/"+ d
    def + (that: Rational): Rational =
        new Rational(
            numer * that.denom + that.numer * denom,
            denom * that.denom
        )
    def + (i: Int): Rational = // 덧셈 연산자 메소드 오버로드
        new Rational(numer + i * denom, denom)
    def - (that: Rational): Rational =
        new Rational(
            numer * that.denom - that.numer * denom,
            denom * that.denom
        )
    def - (i: Int): Rational = // 뺄셈 연산자 메소드 오버로드
        new Rational(numer - i * denom, denom)
    def * (that: Rational): Rational =
        new Rational(numer * that.numer, denom * that.denom)
    def * (i: Int): Rational = // 곱셈 연산자 메소드 오버로드
        new Rational(numer * i, denom)
    def / (that: Rational): Rational =
        new Rational(numer * that.denom, denom * that.numer)
    def / (i: Int): Rational = // 나눗셈 연산자 메소드 오버로드
        new Rational(numer, denom * i)
    private def gcd(a: Int, b: Int): Int =
        if (b == 0) a else gcd(b, a % b)
}

암시적 타입 변환

  • 암시적 타입 변환 메소드 지정 가능
  • 해당 스코프 내에 메소드가 존재해야 동작함
implicit def intToRational(x: Int) = new Rational(x)

7장

if 표현식

var filename = "default.txt"
if (!args.isEmpty)
  filename = args(0)
val filename =
  if (!args.isEmpty) args(0)
  else "default.txt"

while 루프

  • 수행 결과는 Unit 타입
  • 할당의 결과는 Unit 값(())
(line == readLine()) != "" // () != "" 이므로 항상 참

while

while (a != 0) {
    val temp = a
    a = b%a
    b = temp
}

do-while

do {
    line = readLine()
    println("Read: " + line)
} while (line != "")

for 표현식

컬렉션 순회

  • 배열 순회
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere)
  println(file)
  • Range 순회
for (i <- 1 to 4) // 1,2,3,4
  println("Iteration "+i)
for (i <- 1 until 4) // 1,2,3
  println("Iteration "+i)

필터링

val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere if file.getName.endsWith(".scala")) // '.scala'로 끝나는 파일만
  println(file)
for {
    file <- filesHere
    if file.isFile // 필터 추가
    if file.getName.endsWith(".scala")
} println(file)

중첩 순회

def fileLines(file: java.io.File) =
  scala.io.Source.fromFile(file).getLines().toList

def grep(pattern: String) =
  for {
    file <- fileHere
    if file.getName.endsWith(".scala")  // 바깥 루프
    line <- fileLines(file)
    if line.trim.matches(pattern)  // 안쪽 루프
   } println(file +": "+ line.trim)

grep(".*gcd.*")

변수 바인딩

def fileLines(file: java.io.File) =
  scala.io.Source.fromFile(file).getLines().toList

def grep(pattern: String) =
  for {
    file <- fileHere
    if file.getName.endsWith(".scala")
    line <- fileLines(file)
    trimmed = line.trim  // val 변수 처럼 선언하고 사용
    if trimmed.matches(pattern)
  } println(file +": "+ trimmed)

grep(".*gcd.*")

컬렉션 생성

def scalaFiles =
  for {
    file <- filesHere
    if file.getName.endsWith(".scala")
  } yield file
  • yield 는 전체 본문의 앞에 위치

try 표현식

예외 발생

throw new IllegalArgumentException // throw
  • throw는 Nothing이라는 타입을 결과값으로 갖는다.

예외 잡기

try {
  val f = new FileReader("input.txt")
} catch {
  case ex: FileNotFoundException => // ...
  case ex: IOExceptoin => // ...
}

finally 절

val file = new FileReader("input.txt")
try {
  // ...
} finally {
  file.close()
}

결과값

  • try, catch 값이 결과값으로 사용됨
  • finally는 결과값을 바꾸지 않음

match 표현식

  • case 내에는 어떤 종류의 상수라도 사용 가능
  • 모든 case마다 break문이 암묵적으로 존재
val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
  case "salt" => println("pepper")
  case "chips" => println("salsa")
  case "eggs" => println("bacon")
  case _ => println("huh?")     // default case
}

break / continue

  • continue -> if
  • break -> 불리언 변수
  • 재귀 함수 사용(꼬리 재귀 호출)
  • scala.util.control.Breaks 클래스
import scala.util.control.Breaks._
import java.io._

val in = new BufferedReader(new InputStreamReader(System.in))

breakable {
    while (true) {
        println("? ")
        if (in.readLine() == "") break
    }
}

변수 스코프

  • 중괄호 사용시 새로운 스코프 생성(예외 존재 - for 등)
  • 안쪽 스코프에서 바깥 스코프의 동일 이름 변수를 선언하면 바깥 변수는 가려짐(shadow)
  • 인터프리터는 구문마다 새로운 스코프 생성
Clone this wiki locally