-
Notifications
You must be signed in to change notification settings - Fork 7
3 10
LazySoul edited this page Aug 20, 2016
·
4 revisions
다음은 재사용 할 수 있는 애플리케이션 자원관리자의 구현이다.
// 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
- R은 관리할 자원의 타입이다. <: 는 R이 다른 어떤 것의 서브 클래스임을 의미한다. 아마도 Closble 인터페이스를 사용 했을 것이다.
- T는 자원을 가지고 작업을 수행하도록 넘겨지는 익명 함수에서 반환하는 타입이다.
- resource는 이름에 의한(by-name)매개변수다. 지금 당장은 이를 괄호 없이 호출할 수 있는 함수로 생각하라.
- 자원을 가지고 처리할 작업이 들어 있는 두 번째 인자 목록을 넘긴다. 자원을 인자로 받고, 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
}
- 구현이 꼬리재귀가 되도록 보장한다.
- 인자 목록을 둘 받는 continue 함수를 정의한다. 첫 번째 인자 목록은 조건 역할을 할 이름에 의한 호출 매개변수 하나다. 두 번째 목록은 매 반복마다 계산할 본문이 될 이름에 의한 호출 값이다.
- 조건을 검사한다.
- 차이면 body를 계산한 다음 continue를 재귀 호출한다.
이름에 의한 호출 매개변수는 매번 참조될 때마다 평가된다는 사실을 기억해야 한다.
- 이름에 의한 매개변수는 평가를 지연시키므로 어떤 의미에서는 지연(lazy) 계산이기는 하다.
- 하지만 계산을 매번 반복하는 지연 계산이다. 스칼라는 지연값도 제공한다.