Failure — 지연된 예외
Failure — 지연된 예외
에러가 났을 때 꼭 그 자리에서 터지지 않고, 값처럼 나중에 처리되길 바랄 때가 있어요. 그런 "아직 던져지지 않은 부드러운 예외"를 나타내는 타입이 Failure예요. 보통 &fail을 호출해 만들어져요.
본문
class Failure is Nil { }
Failure는 부드러운(soft), 즉 던져지지 않은 Exception으로, 보통 &fail을 호출해 생성돼요. Exception 객체를 감싸는 래퍼처럼 동작해요.
싱크(void) 문맥은 Failure가 던져지게, 즉 일반 예외로 바뀌게 만들어요. use fatal 프래그마를 쓰면 그 프래그마 범위 안의 모든 문맥에서 그렇게 돼요. try 블록 안에서는 use fatal이 자동으로 설정되고, no fatal로 끌 수 있어요.
즉 Failure는 주로 정상적으로 rvalue를 만들어내는 코드에서 유용해요. 싱크 문맥(즉 say처럼 부수 효과를 위해 자주 호출되는 코드)에서 Failure는 Exception과 거의 동등해져요.
마찬가지로, 일반적으로 &fail은 뭔가를 반환하길 기대하는 코드 안에서만 써야 해요.
Failure를 진리값으로 검사하거나(Bool 메서드) 정의 여부를 검사하면(defined 메서드) 그 실패가 "처리됨(handled)"으로 표시되고, 이제 싱크 문맥에서 던져지지 않아요.
handled 메서드를 호출하면 실패가 처리되었는지 확인할 수 있어요.
처리되지 않은 실패에 메서드를 호출하면 실패가 전파돼요. 명세는 결과가 또 다른 Failure라고 말하지만, Rakudo에서는 실패가 던져지게 해요.
Failure는 정의되지 않은 Nil이기 때문에, 실패할 수 있는 코드를 안전하게 실행하는 흔한 관용구는 with/else 문을 쓰는 거예요.
sub may_fail( --> Numeric:D ) {
my $value = (^10).pick || fail "Zero is unacceptable";
fail "Odd is also not okay" if $value % 2;
return $value;
}
with may_fail() -> $value { # defined, so didn't fail
say "I know $value isn't zero or odd."
} else { # undefined, so failed, and the Failure is the topic
say "Uh-oh: {.exception.message}."
}
method new
multi method new(Failure:D:)
multi method new(Failure:U:)
multi method new(Failure:U: Exception:D \exception)
multi method new(Failure:U: $payload)
multi method new(Failure:U: |cap (*@msg))
인자로 주어진 payload로 새 Failure 인스턴스를 반환해요. Failure 객체에 인자 없이 호출하면 던져지고, 타입 값에 호출하면 payload가 없는 빈 Failure를 만들어요. 후자의 payload는 Exception이거나 Exception용 payload일 수 있어요. 전형적인 payload는 오류 메시지를 담은 Str이에요. payload 리스트도 받아들여져요.
my $e = Failure.new(now.DateTime, 'WELP‼');
say $e;
CATCH{ default { say .^name, ': ', .Str } }
# OUTPUT: «X::AdHoc: 2017-09-10T11:56:05.477237ZWELP‼»
method handled
method handled(Failure:D: --> Bool:D) is rw
처리된 실패면 True, 아니면 False를 반환해요.
sub f() { fail }; my $v = f; say $v.handled; # OUTPUT: «False»
handled 메서드는 lvalue예요(참고: routine trait is rw). 그래서 handled 상태를 설정하는 데 쓸 수도 있어요.
sub f() { fail }
my $v = f;
$v.handled = True;
say $v.handled; # OUTPUT: «True»
method exception
method exception(Failure:D: --> Exception)
실패가 감싸고 있는 Exception 객체를 반환해요.
sub failer() { fail };
my $failure = failer;
my $ex = $failure.exception;
put "$ex.^name(): $ex";
# OUTPUT: «X::AdHoc: Failed»
method self
method self(Failure:D: --> Failure:D)
invocant가 처리된 Failure면 그대로 반환해요. 처리되지 않았다면 그 Exception을 던져요. Mu 타입이 모든 클래스에 .self를 제공하므로, 이 메서드를 호출하는 것은 Failure를 폭발적으로 걸러내는 편리한 방법이에요.
my $num1 = '♥'.Int;
# $num1 now contains a Failure object, which may not be desirable
my $num2 = '♥'.Int.self;
# .self method call on Failure causes an exception to be thrown
my $num3 = '42'.Int.self;
# Int type has a .self method, so here $num3 has `42` in it
(my $stuff = '♥'.Int).so;
say $stuff.self; # OUTPUT: «(HANDLED) Cannot convert string to number…»
# Here, Failure is handled, so .self just returns it as is
method Bool
multi method Bool(Failure:D: --> Bool:D)
False를 반환하고 실패를 처리된 것으로 표시해요.
sub f() { fail };
my $v = f;
say $v.handled; # OUTPUT: «False»
say $v.Bool; # OUTPUT: «False»
say $v.handled; # OUTPUT: «True»
method Capture
method Capture()
invocant가 타입 객체이거나 처리된 Failure면 X::Cannot::Capture를 던져요. 그렇지 않으면 invocant의 exception을 던져요.
method defined
multi method defined(Failure:D: --> Bool:D)
False를 반환하고(실패는 공식적으로 정의되지 않음) 실패를 처리된 것으로 표시해요.
sub f() { fail };
my $v = f;
say $v.handled; # OUTPUT: «False»
say $v.defined; # OUTPUT: «False»
say $v.handled; # OUTPUT: «True»
method list
multi method list(Failure:D:)
실패를 처리된 것으로 표시하고 invocant의 exception을 던져요.
sub fail
multi fail(--> Nil)
multi fail(*@text)
multi fail(Exception:U $e --> Nil )
multi fail($payload --> Nil)
multi fail(|cap (*@msg) --> Nil)
multi fail(Failure:U $f --> Nil)
multi fail(Failure:D $fail --> Nil)
호출한 루틴을 빠져나가고, $e를 감싼 Failure 객체—또는 cap이나 $payload 형식이면 @text의 연결로 만들어진 X::AdHoc 예외—를 반환해요. 호출자가 use fatal; 프래그마로 치명적 예외를 활성화했다면, Failure로 반환하는 대신 예외가 던져져요.
# A custom exception defined
class ForbiddenDirectory is Exception {
has Str $.name;
method message { "This directory is forbidden: '$!name'" }
}
sub copy-directory-tree ($dir) {
# We don't allow for non-directories to be copied
fail "$dir is not a directory" if !$dir.IO.d;
# We don't allow 'foo' directory to be copied too
fail ForbiddenDirectory.new(:name($dir)) if $dir eq 'foo';
# or above can be written in method form as:
# ForbiddenDirectory.new(:name($dir)).fail if $dir eq 'foo';
# Do some actual copying here
...
}
# A Failure with X::AdHoc exception object is returned and
# assigned, so no throwing Would be thrown without an assignment
my $result = copy-directory-tree("cat.jpg");
say $result.exception; # OUTPUT: «cat.jpg is not a directory»
# A Failure with a custom Exception object is returned
$result = copy-directory-tree('foo');
say $result.exception; # OUTPUT: «This directory is forbidden: 'foo'»
일반적인(undefined) Failure로 호출하면 임시(ad-hoc)의 정의되지 않은 실패가 던져지고, 정의된 Failure면 처리되지 않은 것으로 표시돼요.
sub re-fail {
my $x = +"a";
unless $x.defined {
$x.handled = True;
say "Something has failed in \$x ", $x.^name;
# OUTPUT: «Something has failed in $x Failure»
fail($x);
return $x;
}
}
my $x = re-fail;
say $x.handled; # OUTPUT: «False»