Perl 서브루틴

Perl 서브루틴 (perlsub)

Perl에서 여러 번 재사용할 코드를 묶는 방법이 바로 서브루틴이에요. 서브루틴은 프로그램 어디에나 둘 수 있고, 다른 파일에서 do, require, use로 불러오거나 eval, 익명 서브루틴으로 즉석에서 만들 수도 있어요. Perl의 함수 호출·반환 모델이 단순해서, 인자도 반환값도 하나의 평평한 스칼라 리스트로 주고받는다는 점만 이해하면 부담이 확 줄어요.

출처: perlsub - Perl subroutines (user-defined functions)

본문

Perl의 함수 호출·반환 모델은 단순해요. 모든 함수는 하나의 평평한 스칼라 리스트를 인자로 받고, 같은 방식으로 호출자에게 하나의 평평한 스칼라 리스트를 돌려줘요. 호출·반환 리스트에 배열이나 해시가 들어 있으면 그 정체성이 소멸돼 평평하게 펼쳐지는데, 참조(by reference)로 넘기면 그 문제를 피할 수 있어요.

서브루틴을 선언하는 방법은 다음과 같아요.

sub NAME;                    # "전방" 선언
sub NAME BLOCK               # 선언과 정의
sub NAME(PROTO) BLOCK        # 프로토타입 포함
sub NAME : ATTRS BLOCK       # 속성 포함

익명 서브루틴은 런타임에 이렇게 만들어요.

$subref = sub BLOCK;         # no proto
$subref = sub (PROTO) BLOCK; # with proto

인자 받기: @_

시그니처(signature)를 쓰지 않는 서브루틴에서는 넘어온 인자가 모두 @_ 배열에 담겨요. 두 인자로 함수를 호출했다면 그 값은 $_[0]$_[1]에 들어 있어요. @_는 지역 배열이지만, 그 요소는 실제 스칼라 인자의 **앨리어스(alias)**예요. 그래서 $_[0]을 수정하면 대응하는 인자도 함께 바뀌어요.

sub maybeset {
    my($key, $value) = @_;
    $Foo{$key} = $value unless $Foo{$key};
}

my 목록에 대입하면 값이 복사되므로, 호출 시 참조(by reference)가 값(by value)으로 바뀌는 효과가 있어요. 아니면 함수가 @_를 제자리에서 수정해 호출자의 값을 바꿀 수도 있지요.

upcase_in($v1, $v2);    # 이 호출로 $v1, $v2가 바뀜
sub upcase_in {
    for (@_) { tr/a-z/A-Z/ }
}

반환값과 return

return 문은 서브루틴을 빠져나가며, 선택적으로 반환값을 지정해요. 반환값은 호출 컨텍스트(리스트·스칼라·void)에 맞춰 평가돼요. 반환값을 지정하지 않으면 리스트 컨텍스트에서는 빈 리스트, 스칼라 컨텍스트에서는 undefined 값을, void 컨텍스트에서는 아무것도 돌려주지 않아요.

return이 없고 마지막 문장이 표현식이면 그 값이 반환돼요. 마지막 문장이 foreachwhile 같은 루프 제어 구조라면 반환값은 불명확하니 명시적으로 return을 쓰는 게 안전해요.

sub max {
    my $max = shift(@_);
    foreach $foo (@_) {
        $max = $foo if $max < $foo;
    }
    return $max;
}
$bestday = max($mon,$tue,$wed,$thu,$fri);

mystate로 만든 지역·영속 변수

my로 만든 변수는 비공개(private) 변수예요. 시그니처 없는 서브루틴에서는 명명된 형식 매개변수가 없으므로, 실질적으로 my 목록에 @_를 대입해 인자 이름을 붙이는 게 일반적이에요. my로 선언하지 않은 변수는 전역(global)이에요.

state 변수는 서브루틴이 호출될 때마다 새로 초기화되지 않고, 서브루틴이 실행되는 동안 값이 유지되는 영속(private persistent) 변수예요. 단순히 한 번 선언했다가 그 다음 호출에서도 같은 값을 간직하게 돼요.

sub counter {
    state $count = 0;
    return ++$count;
}

서브루틴 호출과 &

서브루틴은 & 접두사를 붙여 호출할 수 있는데, 현대 Perl에서는 &가 선택 사항이고 서브루틴이 미리 선언됐다면 괄호도 생략 가능해요. 단, defined()undef()의 인자로 이름만 쓰일 때는 &가 필수고, 참조를 통한 간접 호출(&$subref() 등)에도 &가 필요해요.

&foo(1,2,3);    # 세 인자 전달
foo(1,2,3);     # 동일
foo();          # 빈 인자 목록
&foo;           # 현재 @_를 그대로 전달, foo(@_)와 같음

&로 호출하면 인자 목록이 선택 사항이 되고, 생략하면 호출 시점의 @_가 그대로 서브루틴에 보여요. 이는 효율 메커니즘이라 새로 시작하는 분은 피하는 게 좋아요. Perl 5.16부터 use feature 'current_sub'(또는 use v5.16)에서 __SUB__ 토큰을 쓸 수 있고, 이는 현재 실행 중인 서브루틴의 참조로 평가돼요. 이름을 몰라도 재귀 호출을 할 수 있게 해줘요.

프로토타입과 시그니처

프로토타입(prototype)은 서브루틴 선언에서 인자 형태를 컴파일 타임에 검사·제약하는 기능이에요. Perl 5.20 이후 use feature 'signatures'(또는 use v5.36)에서 지원하는 **시그니처(subroutine signature)**는 인자를 명명된 어휘 변수에 직접 대입해줘서 @_에서 꺼내 쓰는 번거로움을 덜어줘요.

use feature 'signatures';
sub greet ($name) {
    return "Hello, $name!";
}

더 알아보기