Skip to content
LazySoul edited this page Aug 20, 2016 · 4 revisions

3.10 이름에 의한 호출과 값에 의한 호출

다음은 재사용 할 수 있는 애플리케이션 자원관리자의 구현이다.

// src/main/scala/progscala2/rounding/TryCatchArm.scala
package progscala2.rounding
import scala.language.reflectiveCalls
import scala.util.control.NonFatal

// DeanW (Dec. 21, 2015): Refined the implementation and the usage
// example below to more clearly indicate the handling of the returned
// object of type T.
object manage {
  def apply[R <: { def close():Unit }, T](resource: => R)(f: R => T): T = {
    var res: Option[R] = None
    try {
      res = Some(resource)         // Only reference "resource" once!!
      f(res.get)                   // Return the T instance
    } catch {
      case NonFatal(ex) =>
        println(s"manage.apply(): Non fatal exception! $ex")
        throw ex
    } finally {
      if (res != None) {
        println(s"Closing resource...")
        res.get.close
      }
    }
  }
}

object TryCatchARM {
  /** Usage: scala rounding.TryCatch filename1 filename2 ... */
  def main(args: Array[String]) = {
    val sizes = args map (arg => returnFileLength(arg))
    println("Returned sizes: " + (sizes.mkString(", ")))
  }

  import scala.io.Source

  def returnFileLength(fileName: String): Int = {
    println()  // Add a blank line for legibility
    manage(Source.fromFile(fileName)) { source =>
      val size = source.getLines.size
      println(s"file $fileName has $size lines")
      if (size > 200) throw new RuntimeException(s"Big file: $fileName!")
      size
    }
  }
}

이 예제는 '관심사의 분리'(separation of concerns)를 멋지게 보여준다.

def apply[
  R <: { def close():Unit },      //1
  T ]                             //2
  (resource: => R)                //3
  (f: R => T) = { ... }           //4
  1. R은 관리할 자원의 타입이다. <: 는 R이 다른 어떤 것의 서브 클래스임을 의미한다. 아마도 Closble 인터페이스를 사용 했을 것이다.
  2. T는 자원을 가지고 작업을 수행하도록 넘겨지는 익명 함수에서 반환하는 타입이다.
  3. resource는 이름에 의한(by-name)매개변수다. 지금 당장은 이를 괄호 없이 호출할 수 있는 함수로 생각하라.
  4. 자원을 가지고 처리할 작업이 들어 있는 두 번째 인자 목록을 넘긴다. 자원을 인자로 받고, T라는 타입의 결과를 반환하는 함수다.

1번 지점을 Closable 추상화를 구현한다면

object manage{
  def apply[R>: Closable, T](resource: => R)(f: R => T) = {...}
  ...
}
  • val res = Some(resource) 라는 줄이 resource가 평가되는 유일한 장소다.
  • Source를 만드는 첫 식은 실행이 manage로 넘어가기 전에 즉시 평가되지 않는다.
  • 이 식의 계산은 val res = Some(resource)에 이를 때까지 지연된다.
  • 이것이 이름에 의한 호출 매개변수인 resource가 우리에게 제공하는 기능이다. 이를 사용해서 임의의 식을 매개변수로 받는 manage.apply 함수를 작성하되, 실행을 나중으로 유예할 수 있다.
  • 다른 대부분의 언어처럼 스칼라는 일반적으로 **값에 의한 호출(call-by-value)**을 사용한다.
  • manage(Source.fromFile(fileName))을 값에 의한 호출 문맥으로 쓰면 Source.fromFile을 먼저 호출해서 반화된 결과를 manage에 넘긴다.

apply안에 있는 val res = Some(resource)까지 평가를 지연시키면, 그 줄을 실질적으로는 다음을 실행하는 것과 같다.

val res = Some(Source.fromFile(fileName))

이와 같은 관용구는 스칼라가 이름에 의한 호출 매개변수를 제공하는 이유다.

이름에 의한 호출 매개변수가 없다면 어떻게 해야 할까? 아마 익명 함수를 사용할 수 있을 것이다. 하지만 예쁘지 않다. 이 경우 manage 호출은 다음과 같을 것이다.

manage(() => Source.fromFile(fileName)) { source =>

apply 안에서는 resource 에 대한 참조를 '분명한' 함수 호출로 바꿔야 한다.

val res = Some(resource())

끔찍한 부담은 아니지만 이름에 의한 호출은 manage와 같이 자신만의 제어 구조를 만들기 위한 구믄을 가능하게 한다.

이름에 의한 매개변수 가 함수처럼 작동한다는 점을 기억하라. 즉, 이름에 의한 매개변수는 매번 사용할 때마다 재평가된다. 우리의 ARM 예제에서는 한 번만 사용했지만, 이는 일반적이지 않다.

다음은 continue라는 이름으로 while과 비슷한 루프 구조를 구현한 것이다.

// src/main/scala/progscala2/rounding/call-by-name.sc

@annotation.tailrec                                                  // <1>
def continue(conditional: => Boolean)(body: => Unit) {               // <2>
  if (conditional) {                                                 // <3>
    body                                                             // <4>
    continue(conditional)(body)
  }
}

var count = 0                                                        
continue(count < 5) {
  println(s"at $count")
  count += 1
}
  1. 구현이 꼬리재귀가 되도록 보장한다.
  2. 인자 목록을 둘 받는 continue 함수를 정의한다. 첫 번째 인자 목록은 조건 역할을 할 이름에 의한 호출 매개변수 하나다. 두 번째 목록은 매 반복마다 계산할 본문이 될 이름에 의한 호출 값이다.
  3. 조건을 검사한다.
  4. 차이면 body를 계산한 다음 continue를 재귀 호출한다.

이름에 의한 호출 매개변수는 매번 참조될 때마다 평가된다는 사실을 기억해야 한다.

  • 이름에 의한 매개변수는 평가를 지연시키므로 어떤 의미에서는 지연(lazy) 계산이기는 하다.
  • 하지만 계산을 매번 반복하는 지연 계산이다. 스칼라는 지연값도 제공한다.
Clone this wiki locally