검사된 예외
검사된 예외 (Checked Exceptions)
소개
Scala는 언어 import를 통해 검사된 예외(checked exceptions)를 켤 수 있어요. 아래는 safer exceptions 페이지에서 가져온 예시로, 2021 Scala Symposium에서 발표된 논문에서도 설명돼 있어요.
본문
import language.experimental.saferExceptions
import language.experimental.captureChecking
import caps.*
import language.experimental.saferExceptions
class LimitExceeded extends Exception
val limit = 10e+10
def f(x: Double): Double throws LimitExceeded =
if x < limit then x * x else throw LimitExceeded()
새 throws 절은 CanThrow 역량을 제공하는 암시적 파라미터로 확장돼요. 따라서 함수 f는 다음과 같이 동등하게 쓸 수 있어요.
import language.experimental.saferExceptions
import language.experimental.captureChecking
import caps.*
import language.experimental.saferExceptions
class LimitExceeded extends Exception
val limit = 10e+10
def f(x: Double): Double throws LimitExceeded =
if x < limit then x * x else throw LimitExceeded()
object Desugared:
def f(x: Double)(using CanThrow[LimitExceeded]): Double =
if x < limit then x * x else throw LimitExceeded()
암시적 파라미터가 없으면 오류가 보고돼요. 예를 들어 함수 정의
import language.experimental.saferExceptions
import language.experimental.captureChecking
import caps.*
import language.experimental.saferExceptions
class LimitExceeded extends Exception
val limit = 10e+10
def f(x: Double): Double throws LimitExceeded =
if x < limit then x * x else throw LimitExceeded()
def g(x: Double): Double =
if x < limit then x * x else throw LimitExceeded() // error: missing CanThrow[LimitExceeded]
는 다음 오류 메시지와 함께 거부돼요.
| if x < limit then x * x else throw LimitExceeded()
| ^^^^^^^^^^^^^^^^^^^^^
|The capability to throw exception LimitExceeded is missing.
|The capability can be provided by one of the following:
| - Adding a using clause `(using CanThrow[LimitExceeded])` to the definition of the enclosing method
| - Adding `throws LimitExceeded` clause after the result type of the enclosing method
| - Wrapping this piece of code with a `try` block that catches LimitExceeded
CanThrow 역량은 throw 표현식이 요구하고 try 표현식이 만들어요. 예를 들어 표현식
import language.experimental.saferExceptions
import language.experimental.captureChecking
import caps.*
import language.experimental.saferExceptions
class LimitExceeded extends Exception
val limit = 10e+10
def f(x: Double): Double throws LimitExceeded =
if x < limit then x * x else throw LimitExceeded()
def sumAll(xs: Double*): Double =
try xs.map(f).sum
catch case ex: LimitExceeded => -1
은 컴파일러가 대략 다음과 같이 확장해요.
try
erased given ctl: CanThrow[LimitExceeded] = compiletime.erasedValue
xs.map(f).sum
catch case ex: LimitExceeded => -1
(ctl 역량은 타입 검사에만 쓰이고 생성된 코드에는 나타날 필요가 없으므로 erased로 선언할 수 있어요.)
다른 역량 기반 기법들과 마찬가지로, 결과에 캡처된 역량을 경계해야 해요. 예를 들어 여기 문제가 되는 사용 사례가 있어요.
import language.experimental.saferExceptions
import language.experimental.captureChecking
import caps.*
import language.experimental.saferExceptions
class LimitExceeded extends Exception
val limit = 10e+10
def f(x: Double): Double throws LimitExceeded =
if x < limit then x * x else throw LimitExceeded()
def escaped(xs: Double*): (() => Double) throws LimitExceeded =
try () => xs.map(f).sum
catch case ex: LimitExceeded => () => -1
val crasher = escaped(1, 2, 10e+11) // error: CanThrow escapes into returned closure
crasher()
이 코드는 거부되어야 해요. 그렇지 않으면 crasher() 호출이 처리되지 않은 LimitExceeded 예외를 던지게 되기 때문이에요.
language.experimental.captureChecking 언어 import 아래에서는 이 코드가 실제로 거부돼요.
예외 검사와 캡처 검사를 통합하려면 두 가지만 바꾸면 돼요.
CanThrow는Control을 상속하는 클래스로 선언되어, 모든CanThrow인스턴스에 대한 참조가 추적돼요.- 도피 검사(escape checking)가
try표현식으로 확장돼요.try의 결과 타입은try본문에서 정의된 역량을 캡처하는 것이 허용되지 않아요.