Sub — 서브루틴과 연산자의 타입
Sub — 서브루틴과 연산자의 타입 (Type for subroutines and operators)
Raku에서 조각 코드에 이름을 붙여 재사용하는 가장 기본적인 도구가 서브루틴, 즉 sub이에요. 함수형 언어답게 Raku의 연산자도 사실은 서브루틴의 일종이에요. 이 문서에서는 Sub 타입이 무엇이고, 서브루틴의 스코프와 트레이트(trait)가 어떻게 동작하는지 살펴볼게요.
본문
Sub은 서브루틴과 연산자를 위한 타입이에요. 루틴(routine) 계열 타입 Routine을 상속받아요.
class Sub is Routine { }
서브루틴은 sub 선언 키워드 뒤에 선택적인 식별자(identifier)를 붙여서 만들어요. 연산자를 어떻게 선언하는지는 짧은 튜토리얼이 잘 설명해주고, 서브루틴의 매개변수 목록에 대한 자세한 내용은 Signature 문서에서 다뤄요.
주의할 점이 하나 있어요. 형변환자(coercer)와 같은 이름을 가진 서브루틴은 그 형변환자를 이기지 못해요. 그런 경우 & 시길을 붙여서 호출해야 해요.
sub Int(Str $s){'what?'};
say [Int, Int('42'),&Int('42')];
# OUTPUT: «[(Int) 42 what?]»
Int는 이미 형변환자 이름으로 쓰이고 있어서, Int('42')는 형변환으로 동작해 42를 돌려줘요. 반면 &Int('42')처럼 &를 붙여 호출하면 서브루틴이 실행돼 'what?'이 나오죠.
스코프와 클로저
서브루틴은 중첩해서 쓸 수 있고, my와 our로 스코프를 정할 수 있어요. 기본은 my예요. my로 선언된 서브루틴은 어떤 바깥 스코프에서도 접근할 수 없고, our로 선언된 서브루틴은 바깥 스코프의 같은 이름 서브루틴을 재정의하지 않아요. 그리고 어떤 서브루틴이든 바깥 스코프에서 클로저(closure)를 통해 접근할 수 있어요.
sub can-be-seener( $whatever ) {
my sub can-be-seen ( $objection ) {
return $whatever but $objection;
}
return &can-be-seen
}
my $objectioner = can-be-seener( "Really?");
say $objectioner(42).Int; # OUTPUT: «42»
$objectioner에는 다른 스코프에서 선언된 can-be-seen 서브루틴이 담겨요. 이걸 42로 호출하면, "Really?"에 숫자 42가 섞인 값이 반환되는 구조예요. 서브루틴을 값처럼 주고받을 수 있는 게 Raku의 특징이죠.
연산자
연산자도 사실은 Sub이에요. 연산자의 정의에는 그것이 속한 카테고리와 코드, 우선순위, 결합성이 포함돼요. 정의에 쓰이는 문법은 확장 식별자(extended identifiers)의 한 예시예요.
트레이트 (Traits)
트레이트는 컴파일 시점에 클래스, 루틴, 컨테이너 같은 다양한 객체에 적용되는 서브루틴이에요. trait_mod 선언 키워드 뒤에 콜론과 트레이트 이름을 담은 문자열 리터럴을 붙여 선언해요. 하나의 위치 매개변수는 트레이트가 적용되는 객체의 타입을 정의하고, 하나의 이름 매개변수는 이차 이름을 정의하며 호출 시 인자를 담을 수도 있어요. 트레이트는 특별한 문법 카테고리라서, 대부분의 언어 객체 이름이나 매개변수 목록 뒤에 올 수 있어요.
say 'start';
multi trait_mod:<is>(Sub $s, :$foo){
say "⟨is foo⟩ has been called with ⟨$foo⟩ on {$s.WHICH}";
}
sub bar() is foo<oi‽> {
say 'bar has been called'
}
bar();
# OUTPUT: «⟨is foo⟩ has been called with ⟨oi‽⟩ on Sub|47563000startbar has been called»
복잡한 인자를 가진 트레이트를 호출할 때는 구조 분해(destructuring)를 사용할 수 있어요.
multi trait_mod:<is>(Variable $a, :@foo [$firstpos, *@restpos, :$named, *%restnameds]) {
say [$firstpos, @restpos, $named, %restnameds]
}
my $x is foo[1,2,3,:named<a>, :2b, :3c] = 1
# OUTPUT: «[1 [2 3] a {b => 2, c => 3}]»
문법이 다소 특이해 보여도, 트레이트는 그냥 평범한 Sub이에요. 트레이트에 트레이트를 적용할 수도 있고, 런타임에 객체에 적용할 수도 있어요.
multi trait_mod:<is> (Sub $s, :$foo) is foo {
say 'is foo called'
}
sub bar {}
&trait_mod:<is>(&bar, :foo);
# OUTPUT: «is foo calledis foo called»