Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


8장

메소드

  • 객체의 멤버인 함수
import scala.io.Source
object LongLines {
    def processFile(filename: String, width: Int) { // 메소드
        val source = Source.fromFile(filename)
        for (line <- source.getLines())
            processLine(filename, width, line)
    }

    private def processLine(filename: String, width: Int, line: String) { // 도우미 메소드
        if (line.length > width)
            println(filename +": "+ line.trim)
    }
}

지역 함수

  • 함수 안에서 정의한 함수
  • 정의를 감싸고 있는 블록 내에서만 접근 가능
def processFile(filename: String, width: Int) {
    def processLine(filename: String, width: Int, line: String) { // 지역 함수
        if (line.length > width)
            println(filename +": "+ line)
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
        processLine(line)
}
  • 바깥 함수의 파라미터를 사용 가능
def processFile(filename: String, width: Int) {
    def processLine(line: String) {
        if (line.length > width) // 바깥 함수의 파라미터 width를 사용
            println(filename +": "+ line) // 바깥 함수의 파라미터 filename을 사용
    }

    val source = Source.fromFile(filename)
    for (line <- source.getLines())
        processLine(filename, width, line)
}

1급 계층 함수

  • 함수를 정의, 호출, 리터럴로 표기해 값처럼 주고받을 수 있다.
  • 함수 리터럴 : 소스 코드 상의 표기로 존재
  • 함수 값(function value) : 실행 시점에 객체로 존재
(x: Int) => x + 1 // 함수 리터럴 예
scala> var increase = (x: Int) => x + 1
increase: Int => Int = <function1>

scala> increase(10)
res0: Int = 11

scala> increase = (x: Int) => x + 9999 // 재할당 가능
increase: Int => Int = <function1>

scala> increase(10)
res1: Int = 10009
val someNumbers = List(-11, -10, -5, 0, 5, 10)
someNumbers.foreach((x: Int) => println(x)) // 함수 리터럴 사용 예 - foreach
someNumbers.filter((x: Int) => x > 0) // 함수 리터럴 사용 예 - filter

간단한 형태의 함수 리터럴

someNumbers.filter((x: Int) => x > 0)
someNumbers.filter((x) => x > 0) // 타깃 타이핑
someNumbers.fileter(x => x > 0)

위치 표시자

  • 하나 이상의 파라미터에 대한 위치 표시자로 밑줄(_) 사용
  • '채워넣어야 할 빈칸'
someNumbers.fileter(x => x > 0)
someNumbers.filter(_ > 0)
val f = _ + _   // error
val f = (_: Int) + (_: Int) // 타입 명시

부분 적용한 함수

someNumbers.foreach(x => println(x))
someNumbers.foreach(println _)  // 전체 인자 목록에 대한 위치 표시자 사용 = 부분 적용한 함수
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int

scala> val a = sum _    // 인자 3개를 받는 함수 값을 인스턴스화
a: (Int, Int, Int) => Int = <function3>

scala> a(1,2,3) // == a.apply(1,2,3)
res0: Int = 6
  1. 변수 a : 함수 값 객체
  2. 함수 값 : 컴파일러가 sum _을 해석해 자동으로 만든 클래스의 인스턴스
  3. 클래스 : 인자 3개를 받는 apply 메소드 존재
  4. apply 메소드 : sum _에서 빠진 인자가 3개이기 때문에 3개의 인자를 받음
  • 밑줄 있는 함수 값으로 감싸면, 메소드나 중첩 함수를 변수에 할당하거나, 인자로 전달 가능
  • 인자 중 일부만 넘기는 것도 가능
val b = sum(1, _:Int, 3)
  • 명확하게 함수가 필요한 경우 밑줄 생략 가능
someNumbers.foreach(println _)
someNumbers.foreach(println)

클로저

  • 클로저(closure)
    • 함수 리터럴로부터 실행 시점에 만들어낸 객체인 함수 값
    • 함수 리터럴 본분에 있는 모든 자유 변수에 대한 바인딩을 포획해서 자유 변수가 없게 닫음
  • 자유 변수(free variable) : 함수에서 의미를 부여하지 않은 변수
  • 바운드 변수(bound variable) : 함수의 문맥 내에서만 의미가 있는 변수
  • 닫힌 코드 조각(closed term) : 자유 변수가 없는 함수 리터럴
  • 열린 코드 조각(open term) : 자유 변수가 있는 함수 리터럴 -> 클로저
(x: Int) => x + more
// x : 바운드 변수
// more : 자유 변수
  • 클로저가 변화를 감지함.
  • 표획한 인자는 힙에 재배치됨

특별한 형태의 함수 호출

반복 파라미터

def echo(args: String*) =   // args: Array[String]
    for (arg <- args) println(arg)
echo("hello", "world")

val arr = Array(...)
echo(arr: _*)   // 배열을 직접 전달하는 경우

이름 붙인 인자

def speed(distance: Float, time: Float): Float =
    distance / time

speed(100, 10)
speed(distance = 100, time = 10)    // 이름 붙인 인자 사용
speed(time = 10, distance = 100)    // 순서 변경 가능

디폴트 인자 값

  • 인자에 기본값 지정시 생략 가능
def printTime(out: java.io.PrintStream = Console.out) =
    out.println("time = "+ System.currentTimeMillis())

printTime() // 디폴트 값인 Console.out 사용
printTime(Console.err)  // 인자 지정
  • 일부 인자만 이름 붙여서 지정 가능
def printTime2(out: java.io.PrintStream = Console.out, divisor: Int = 1) =
    out.println("time = "+ System.currentTimeMillis()/divisor)

printTime2()
printTime2(out = Console.err)
printTime2(divisor = 1000)

꼬리 재귀

def approximate(guess: Double): Double =
    if (isGoodEnough(guess)) guess
    else approximate(improve(guess))    // 재귀 호출
  • 마지막에 자신을 재귀호출 하는 꼬리 재귀의 경우 최적화함
  • 단, 간접적 재귀(번갈아 호출하는 등), 함수 값 호출 등은 최적화되지 않음


+ Recent posts