8장 functions and closures - codeport/scala GitHub Wiki

  • 프로그램이 커지면 코드를 함수로 분리한다.
  • scala는 자바의 method 외에 function 내부의 function, function 리터럴, function value도 제공함.

8.1 Methods

  • 객체의 멤버로 정의된 함수를 메서드라고 부른다.
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)
  }
}

8.2 Local functions

  • 프로그램을 작은 함수들로 나눈다.
  • 복잡한 작업을 유연하게 구성할 수 있다는 장점이 있다.
  • 단점은 이러한 헬퍼함수들의 이름이 네임스페이스를 더렵힐 수 있다는 것이다.
  • 헬퍼함수는 하나의 개별 단위로는 의미가 없기 때문에 감추는것이 바람직 하다.
  • 함수내에 함수를 정의할 수 있고 이를 local function이라고 부른다.
def processFile(filename: String, width: Int) {
  def processLine(filename: String, width: Int, line: String) {
    if (line.length > width)
      println(filename + ": " + line.trim)
  }

  val source = Source.fromFile(filename)
  for (line <- source.getLines()) {
    processLine(filename, width, line)
  }
}
  • local function은 enclosing function 의 parameter에도 접근할 수 있음.

8.3 First-class functions

  • 스칼라에는 first-class 함수가 있다.함수를 정의하고 호출하고 이름을 주지 않고 리터럴로 작성해서 값으로 전달할 수 있다.
  • function literal은 클래스로 컴파일되고 런타임시에 function value가 된다. function literal 과 function value 의 차이는 소스코드에 존재하는냐, 런타임시에 객체로 존재하느냐의 차이이다.
(x: Int) => x + 1
  • 함수값은 객체이므로 변수에 정의할 수 있다.
scala> var increase = (x: Int) => x + 1
increase: (Int) => Int = <function1>
scala> increase(10)
res0: Int = 11
  • function value는 scala.FunctoinN trait를 확장한 클래스의 인스턴스임. 이 trait는 apply 메서드를 이용해 펑션을 호출함.

8.4 Short forms of function literals

  • 다양한 function literal의 축약형을 보여줌
someNumbers.filter((x: Int) => x > 0 )
someNumbers.filter((x) => x > 0 ) // paramter의 type 생략 - target typing
someNumbers.filter( x => x > 0 ) // parameter의 괄호 생략
  • target typing: 여기서 x가 integer라는 것을 표현식의 대상이되는 타입으로 알 수 있다.(여기서는 someNumbers.filter()의 인자)

8.5 Placeholder syntax

  • 파라미터를 생략하고 _로 대치할 수 있다. 즉, _ > 0x => x > 0과 같다.
  • val f = _ + _처럼 언더스코어를 여러개 써주려면 파라미터가 한번씩만 나와야 한다.
  • _를 parameter placeholder로 사용하려면 컴파일러가 타입을 인지할 수 있어야 함
val f=  (_: Int) + (_: Int)

8.6 Partially applied functions

  • 매개변수 목록 전체를 _로 치환할 수도 있음(함수명과 언더스코어 사이에 공백이 있어야 한다.)
someNumbers.foreach(x => println(x))
someNumbers.foreach(println _) // _ as placeholder for entire parameter list -> partially applied function
  • partially applied function: function이 요구하는 모든 인자를 전달하지 않고 일부 혹은 아무 인자도 전달하지 않은 표현식
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int
scala> val a = sum _
a: (Int, Int, Int) => Int = <function3>
scala> a(1, 2, 3) 
res11: Int = 6 
scala> a.apply(1, 2, 3)
res12: Int = 6
  • _가 전체 parameter list를 대체하는 기능을 이용하면 def를 _function value_로 변형하는 방법도 가능함.
  • _가 전체 parameter list가 아닌 일부 parameter만 치환하게 할 수도 있음
val b = sum(1, _: Int, 3)
b(2) == sum(1,2,3) // b.apply() function call sum(1,2,3)
  • 전체 parameter list를 대체하는 _자체도 생략 가능
someNumbers.foreach(println _)
someNumbers.foreach(println) //function이 들어가야만 하는 위치에서만 가능한 형태

8.7 Closures

  • (x: Int) => x + more에서 more는 함수리터럴이 주지 않는 값이므로 _free variable_라고 부르고 x는 함수의 컨텍스트내에 있으므로 _bound variable_라고 부른다.
  • 이렇게 _free variable_이 포함된 _function value_를 _closure_라고 부름.
    • _free variable_이 포함되지 않은 _function literal_은 closed term
    • _free variable_이 포함된 _function literal_은 open term
  • _open term_인 _function literal_이 _free variable_을 'capture'해서 만들어진 _function value_가 closure
  • 클로저가 참조한 변수의 값이 바뀔경우 참조하는 클로저의 값도 변경된다.
    • cf) java의 inner class는 final 변수만을 받아들이므로 값이 바뀔 수가 없음
  • _closure_가 바라보는 _free variable_의 인스턴스는 _closure_가 생성된 시점의 인스턴스임.

8.8 Special function call forms

  • Repeated parameters
    • java의 가변인자( var args )와 동일한 의미.
    • 마지막 파라미터가 여러개일수 있다는 의미로 타입뒤에 *를 써준다. def echo(args: String*) = body
    • _repeated parameter_의 타입은 선언된 타입의 배열이 되지만 배열로 파라미터를 전달할 수는 없다. 배열로 전달하려면 echo(arr: _*)처럼 _*를 사용해야 한다.
  • Named arguments
    • Named arguments는 순서에 상관없이 이름으로 파라미터를 전달할 수 있다. method(a= 100, b = 10)
  • 파라미터 기본값
    • 함수 파라미터에 기본값을 지정해서 호출시 생략할 수 있다. def a(b=10) 보통 named arguments와 함께 쓰인다.

8.9 Tail recursion

  • tail recursion : 재귀함수의 마지막 호출 대상이 자기 자신일 경우. compiler가 꼬리재귀를 인지하여 최적화 수행.
  • tail recursion의 경우엔 각 호출마다 새로운 stack이 쌓이지 않고, 단일 frame내에서 처리됨. 최적화를 끄고자 할 때는 -g:notailcalls 옵션을 scalacscala에 줌
  • JVM 때문에 scala의 tail recursion도 제약을 받음. 무조건 자기 자신을 직접 호출하는 경우에만 최적화가 가능함.
⚠️ **GitHub.com Fallback** ⚠️