Perl의 subroutine
-
- 1. perlsub document 요약
-
-
- 1.1. 개요
-
- 1.2. 설명
-
-
- 1.2.1. my()를 통한 프라이빗 변수 ( Private Variables via my() )
-
- 1.2.2. 영구 private 변수 ( Persistent Private Variables )
-
-
- 1.2.2.1. state()를 통한 영구 변수 ( Persistent variables via state() )
-
- 1.2.2.2. closure를 통한 영구 변수 ( Persistent variables with closures )
-
1.2.3. local()을 통한 임시값 ( Temporary Values via local() )
-
-
- 1.2.3.1. local()의 문법에 관하여 ( Grammatical note on local() )
-
- 1.2.3.2. 특수 변수들의 지역화 ( Localization of special variables )
-
- 1.2.3.3. glob 지역화 ( Localization of globs )
-
- 1.2.3.4. 구조형 변수의 원소 지역화 ( Localization of elements of composite types )
-
- 1.2.3.5. 구조형 변수의 원소를 지역화 후 삭제 ( Localized deletion of elements of composite types )
-
1.2.4. L밸류 서브루틴 ( Lvalue subroutines )
-
- 1.2.5. 심볼테이블 엔트리 넘겨주기 (typeglobs) ( Passing Symbol Table Entries (typeglobs) )
-
- 1.2.6. 여전히 local()을 사용해야 하는 경우들 ( When to Still Use local() )
-
- 1.2.7. 참조에 의한 전달 ( Pass by Reference )
-
- 1.2.8. 프로토타입 ( Prototypes )
-
- 1.2.9. 상수 함수 ( Constant Functions )
-
- 1.2.10. 내장 함수 오버라이드 ( Overriding Built-in Functions )
-
- 1.2.11. 자동 로드 ( Autoloading )
-
- 1.2.12. 서브루틴 속성 ( Subroutine Attributes )
-
1.3. 참고
-
2. 다른 문서들에서
-
-
- 2.1. 선언 순서
-
- 2.2. Context
-
- 2.3. Prototype
-
- 2.4. 인자로 레퍼런스 넘겨주기
-
- 2.5. 인자로 파일핸들 넘겨주기
-
- 2.6. Named parameter
-
- 2.7. 서브루틴의 레퍼런스
-
- 2.8. Closure
-
- 2.9. 서브루틴을 반환하는 서브루틴
-
- 2.10. closure variable를 static local variable로 쓰기
-
- 2.11. 서브루틴을 인자로 넘기기
-
3. 기타
-
- 4. Comments
-
1. perlsub document 요약
서브루틴 선언하기
sub NAME;
sub NAME(PROTO);
sub NAME : ATTRS;
sub NAME(PROTO) : ATTRS;
sub NAME BLOCK
sub NAME(PROTO) BLOCK
sub NAME : ATTRS BLOCK
sub NAME(PROTO) : ATTRS BLOCK
런타임에 익명 서브루틴 정의하기:
$subref = sub BLOCK;
$subref = sub (PROTO) BLOCK;
$subref = sub : ATTRS BLOCK;
$subref = sub (PROTO) : ATTRS BLOCK;
서브루틴 임포트하기:
use MODULE qw(NAME1 NAME2 NAME3);
서브루틴 호출하기:
NAME(LIST);
NAME LIST;
&NAME(LIST);
&NAME;
사용자가 정의하는 서브루틴:
- 메인 프로그램 내 아무 곳에나 있을 수 있다
-
do
, require
, use
를 사용하여 다른 파일에서 로드할 수 있다
-
eval
또는 익명 서브루틴을 사용하여 실행 시간에 생성할 수 있다
- 서브루틴의 이름 또는 CODE 레퍼런스를 담고 있는 변수를 사용하여 간접적으로 호출할 수도 있다
Perl에서의 함수 호출과 리턴값 모델:
- 함수에 넘겨지는 인자는 스칼라들의 리스트 하나로 구성되어 넘겨진다.
- 함수가 호출자에게 리턴하는 것도 스칼라들의 리스트 하나이다
- 호출과 리턴 때 배열과 해쉬들은 하나로 합쳐지면서 각각의 속성을 잃게 된다. 참조에 의한 호출을 하여 이 점을 방지할 수 있다.
- (함수function 중에 명시적인 return 구문이 없는 것을 서브루틴subroutine이라고 하는 경우도 있는데, Perl에서는 함수와 서브루틴엔 아무런 차이가 없다)
넘겨진 인자들은 @_
배열을 통해 접근할 수 있다.
- 예: 인자가 두 개였다면 각각은
$_[0]
, $_[1]
에 담긴다
-
@_
배열 자체는 지역 변수이지만, 이 배열의 원소들은 실제 스칼라 실인자들의 별칭(alias)으로 되어 있다. $_[0]
원소를 변경하면, 대응되는 실인자도 변경된다 (만일 실인자가 변경 불가능한 것이라면 에러가 난다).
- 인자가 배열 또는 해쉬의 원소인데 함수가 호출하는 시점에 존재하지 않는 원소였다면, 함수 내에서 그 원소의 값을 변경하거나 그 원소의 레퍼런스를 취하는 경우에(또한, 그런 경우에만) 생성된다.
- 오래된 Perl 버전의 경우는 무조건 생성된다.
-
@_
배열 전체에 새로운 값을 할당하면 별칭이 제거되며, 인자들은 변경되지 않는다.
return
구문:
- 서브루틴을 종료하고 빠져나올 때 사용된다.
- 추가로 리턴값을 명시해 줄 수 있다.
- 이 리턴값은 서브루틴을 호출한 문맥(리스트, 스칼라, 보이드)에 따라 적절한 문맥으로 평가된다.
- 리턴값을 명시하지 않은 경우
- 리스트 문맥에서는 빈 리스트 반환
- 스칼라 문맥에서는 undefined 값
- 보이드 문맥에서는 아무것도 반환하지 않음
- 하나 이상의 집합적 자료들(배열과 해쉬)을 리턴할 경우, 이 자료들은 하나의 리스트로 평탄화(flatten)되어 반환된다
-
return
구문이 없고, 마지막 구문이 표현식인 경우 그 식의 값이 반환된다.
- 마지막 구문이
foreach
, while
같은 루프 제어 구조인 경우는 반환값이 명세되어 있지 않다
- 빈 서브루틴은 빈 리스트를 반환한다.
Perl에는 이름 있는 형식 인자를 지원하지 않는다.
- 보통은
my()
리스트에 할당해서 사용한다.
- private으로 선언되지 않은 변수들은 전역 변수들이다.
- private 변수를 만드는 자세한 내용은 아래 섹션들 참조
- 개별적인 패키지 내의 함수들에서 사용되는 protected 환경을 만드는 법은 perlmod 의 Packages 섹션을 참조
예:
sub max {
my $max = shift(@_);
foreach $foo (@_) {
$max = $foo if $max < $foo;
}
return $max;
}
$bestday = max($mon,$tue,$wed,$thu,$fri);
sub get_line {
$thisline = $lookahead;
LINE: while (defined($lookahead = <STDIN>)) {
if ($lookahead =~ /^[ \t]/) {
$thisline .= $lookahead;
}
else {
last LINE;
}
}
return $thisline;
}
$lookahead = <STDIN>;
while (defined($line = get_line())) {
...
}
인자에 이름을 붙이기 위해서 프라이빗 변수 리스트에 할당:
sub maybeset {
my($key, $value) = @_;
$Foo{$key} = $value unless $Foo{$key};
}
할당은 값을 복사하여 이뤄지기 때문에, 위의 코드는 참조에 의한 호출을 값에 의한 호출로 전환하는 효과도 있다. 이렇게 전환하지 않는 경우 함수는 @_의 값을 그 자리에서 변경함으로써 호출자 쪽의 값을 바꿀 수 있다.
upcase_in($v1, $v2);
sub upcase_in {
for (@_) { tr/a-z/A-Z/ }
}
당연히 상수나 리터럴은 바꿀 수 없다. 시도할 경우 에러가 난다:
upcase_in("frederick");
인자를 함수 내에서 변경하는 것보다는, 사본을 가지고 작업한 후 리턴하는 형태로 작성하는 게 훨씬 더 안전하다:
($v3, $v4) = upcase($v1, $v2);
sub upcase {
return unless defined wantarray;
my @parms = @_;
for (@parms) { tr/a-z/A-Z/ }
return wantarray ? @parms : $parms[0];
}
Perl에서는 모든 인자가 @_
에 담긴 하나의 평탄한 파라메터 리스트로 보이기 때문에, upcase()의 정의를 바꿀 필요 없이 다음과 같이 호출할 수도 있다:
@newlist = upcase(@list1, @list2);
@newlist = upcase( split /:/, $var );
그러나, 다음과 같이 하면 안 된다:
(@a, @b) = upcase(@list1, @list2);
리턴되는 리스트도 평탄화되기 때문에, 반환하는 모든 것이 @a
에 담기고 @b
는 빈 채로 남는다. 이에 대한 대안은 "Pass by Reference"를 참조
서브루틴을 호출할 때 명시적으로 &
접두어를 붙일 수 있다.
- 최신 Perl에서는
&
는 생략가능하다
- 서브루틴이 미리 선언된 경우는 괄호도 생략가능하다
-
&
가 필수적인 경우:
- defined()나 undef()의 인자로 넣는 경우처럼, 서브루틴의 이름 자체를 지칭할 경우
-
&$subref()
또는 &{$subref}()
구조를 사용하여 이름이나 레퍼런스를 통한 간접 호출을 하는 경우. 이 것은 $subref->()
표기법을 사용하여 피할 수 있다.
- 자세한 것은 perlref 참조
서브루틴이 재귀적으로 호출되는 경우:
-
&
를 붙인 형태로 호출할 경우, 인자 리스트를 생략할 수 있으며, 생략하면 호출하는 시점의 @_
배열이 서브루틴에 보이게 된다. 이것은 효율적이지만 초심자는 피하고 싶을 수 있다:
&foo(1,2,3);
foo(1,2,3);
foo();
&foo();
&foo;
foo;
&
를 붙인 형태는 인자 리스트를 생략가능하게 만들어주는 것 이외에도, 프로토타입 체크를 하지 않도록 하는 기능도 있다
- 부분적으로는 역사적인 이유로, 부분적으로는 편리하게 꼼수를 쓸 수 있게 하기 위함
- 자세한 건 "Prototypes" 섹션 참조
이름이 대문자로만 구성된 서브루틴
- 관례적(엄격하진 않음)으로, 이벤트 발생시 Perl 런타임 시스템에 의해 간접적으로 호출되는 서브루틴임을 의미한다.
- 예:
AUTOLOAD
, CLONE
, DESTROY
, perltie PerlIO::via 에 언급되는 함수들.
BEGIN
, UNITCHECK
, CHECK
, INIT
, END
- 서브루틴이라기보다는, 이름이 있는 특수한 코드 블록이라 볼 수 있다.
- 한 패키지 안에 이런 블록들을 둘 수 있고, 명시적으로 호출할 수는 없다.
- 자세한 것은 perlmod 의 "BEGIN, UNITCHECK, CHECK, INIT and END" 섹션 참조
1.2.1. my()를 통한 프라이빗 변수 ( Private Variables via my() )
my $foo;
my (@wid, %get);
my $foo = "flurp";
my @oof = @bar;
my $x : Foo = $y;
경고: my 선언에서 속성 리스트를 사용하는 것은 개발 중인 기능이며, 시맨틱이나 인터페이스가 차후에 변경될 수 있다. attributes , Attribute::Handlers 참조.
my
연산자
- 나열된 변수들을 다음과 같은 스코프 안에서만 사용가능하게 한정한다:
- 자신을 둘러싼 블록
- 조건문 (if/unless/elsif/else)
- 루프 (for/foreach/while/untile/continue)
- 서브루틴
-
eval
- do/require/use 의 대상 파일
- 둘 이상의 변수를 나열할 때는 괄호로 둘러싸야 함
- 나열된 각 원소는 모두 적법한 lvalue여야 함
- 문자와 숫자로 조합된 식별자만이 렉시컬 스코프로 지정될 수 있다
-
$/
등과 같은 빌트인 변수들은 local
키워드를 사용하여 지역화되어야 한다.
local
연산자를 사용하여 생성된 동적 변수와 달리, my
를 사용하여 선언한 렉시컬 변수는 호출된 서브루틴을 비롯한 외부로부터 완전히 감추어진다.
- 이것은 설령 서브루틴이 자기 자신을 호출한 경우에도 마찬가지이다 - 각각의 호출은 각각 자신만의 사본을 사용한다.
어떤 지점을 정적으로 둘러싸고 있는 스코프 내에서 선언된 my
변수는 볼 수 있다. 오직 동적 스코프만 차단된다.
- 다음 코드에서
bumpx()
함수는 렉시컬 $x 변수에 접근할 수 있다. my
와 sub
가 동일한 스코프에 나타나기 때문이다:
my $x = 10;
sub bumpx { $x++ }
그러나 eval()
구문은 이 구문이 평가되는 스코프 내에 있는 렉시컬 변수를, eval() 자신 내부에서 다시 선언하면서 변수의 이름을 감춰버리지 않는 이상은, 접근할 수 있다. perlref 참조.
원한다면 my()의 파라메터 리스트에 값을 할당함으로써 변수를 초기화할 수 있다.
- 변수를 선언할 때 초기화 값이 주어지지 않는 경우는 undefined 값을 가지고 생성된다.
- 서브루틴의 입력 파라메터에 이름을 부여할 때 사용:
$arg = "fred";
$n = cube_root(27);
print "$arg thinks the root is $n\n";
sub cube_root {
my $arg = shift;
$arg **= 1/3;
return $arg;
}
fred thinks the root is 3
my
는 단지 변경자일 뿐이라서, my의 인자 리스트에 있는 변수에 값을 할당할 때 변수가 스칼라인지 배열인지 여부에 영향을 주지 않는다
- 아래 두 코드는 둘 다 우변에 리스트 문맥을 전달한다
my ($foo) = <STDIN>;
my @FOO = <STDIN>;
my $foo = <STDIN>;
my $foo, $bar = 1;
my $foo;
$bar = 1;
선언된 변수는 현재 구문이 끝난 후에야 사용할 수 있다.
- 새로운 $x 를 기존의 $x 의 값으로 초기화한다:
my $x = $x;
- 아래 식은 기존의 $x의 값이 우연히 123일 경우를 제외하고는 거짓이 된다:
my $x = 123 and $x == 123
제어문의 경우는 실행되는 블록 뿐 아니라 조건식이 있는 부분도 같은 스코프의 일부이다.
- 다음 반복문에서 $line의 스코프는 선언한 부분부터
continue
절까지 포함한 루프 전체:
while (my $line = <>) {
$line = lc $line;
} continue {
print $line;
}
- 다음 조건문에서 $answer의 스코프는 선언한 부분부터
elsif
와 else
를 전부 포함한 조건문 전체:
if ((my $answer = <STDIN>) =~ /^yes$/i) {
user_agrees();
} elsif ($answer =~ /^no$/i) {
user_disagrees();
} else {
chomp $answer;
die "'$answer' is neither 'yes' nor 'no'";
}
- 변경자가 붙은 구문 내에서 변수의 스코프에 관한 것은 perlsyn의 "Simple statements" 섹션 참조
foreach
루프의 경우
- 기본적으로는 인덱스 변수를
local
을 사용한 것처럼 스코프를 동적으로 조정한다
- 인덱스 변수가
my
로 선언되거나, 이미 그 이름의 렉시컬 변수가 존재할 경우는, 새로운 렉시컬 변수를 생성하여 사용한다.
- 다음 루프에서, $i의 스코프는 루프가 끝나는 지점까지이며,
some_function()
안에서는$i에 접근할 수 없다
for my $i (1, 2, 3) {
some_function();
}
렉시컬 변수를 사용하는 걸 장려
- 패키지 변수(언제나 전역인)를 묵시적으로 사용하는 걸 감지하기 위하여 다음과 같이 하면:
use strict 'vars';
- 이 지점부터 둘러싼 블록이 끝날 때까지 사용되는 변수들은 렉시컬 변수이거나
our
또는 use vars
를 사용하여 미리 선언되었거나, 이름에 패키지 이름까지 포함한 형태로 적어주어야 한다.
- 안쪽 블록에서
no strict 'vars'
를 하여 이 제한을 다시 없앨 수 있다.
my
의 컴파일타임과 런타임 효과
- 컴파일 타임 - 컴파일러가 체크함
-
use strict 'vars'
를 사용할 때 에러가 나지 않게 함
- 클로져를 만들 때 필수. perlref 참조.
- 런타임 - 실제 초기화는 런타임에 적절한 시기에 이뤄진다
my
로 선언한 변수는 어느 패키지에도 속하지 않으므로, "fully qualified name"을 쓸 수 없다.
- 패키지 변수를 렉시컬로 선언하는 것도 허용되지 않는다:
my $pack::var;
- 동일한 이름의 렉시컬 변수가 있더라도 동적 변수(패키지 변수 또는 전역 변수라고 알려진)는
::
를 사용한 fully qualified 표기법을 써서 접근할 수 있다:
package main;
local $x = 10;
my $x = 20;
print "$x and $::x\n";
my 변수를 파일의 가장 바깥쪽 스코프에서 선언하여, 파일 외부의 동일한 식별자를 가진 변수들을 숨길 수 있다.
- C에서 파일 레벨에 선언된 static 변수와 유사
- 블록 바깥쪽에서 호출할 수 없는 private 서브루틴을 만들고 싶다면, 익명 서브루틴 레퍼런스를 담는 렉시컬 변수를 선언:
- 이 레퍼런스가 모듈 내 어떤 함수에서 리턴되지 않는 이상은 모듈 외부에서는 이 서브루틴을 볼 수 없다. 이 서브루틴의 이름이 패키지의 심볼 테이블에 들어있지 않기 때문이다. 렉시컬 변수는
$some_pack::secret_version
과 같은 식으로 접근할 수 없다는 걸 상기하라.
my $secret_version = '1.001-beta';
my $secret_sub = sub { print $secret_version };
&$secret_sub();
- 오브젝트 메쏘드의 경우에는 이런 식으로 할 수 없다. 모든 오브젝트 메쏘드는 어떤 패키지의 심볼 테이블 안에 존재해야 나중에 찾을 수 있기 때문이다. 이 문제를 우회하기 위한 내용은 perlref의 "Function Templates" 섹션을 참조
1.2.2. 영구 private 변수 ( Persistent Private Variables )
영구적으로 값이 보존되는(persistent) 변수를 만드는 데는
- Perl 5.10부터는
state
사용
- 5.10 이전 버전과 호환성을 유지하기 위해서는 closure 이용
1.2.2.1. state()를 통한 영구 변수 ( Persistent variables via state() )
my
가 들어갈 자리에 대신 state
키워드 사용
- perl 5.9.4 부터
- 먼저
feature
프라그마를 쓰거나, 원-라이너로 실행할 때는 -E
를 사용하라. feature 참조.
- 다음 코드는 gimme_another()가 호출될 때마다 1씩 증가하는 카운터를 관리한다:
use feature 'state';
sub gimme_another { state $x; return ++$x }
-
$x
도 렉시컬 변수이고, 이 서브루틴 밖에서는 접근할 수 없다.
- 변수를 선언하면서 동시에 스칼라값을 할당하는 경우(
state $x=42
와 같이) 이 할당은 처음 한 번만 실행된다. 이후에는 무시된다. 스칼라가 아닌 변수의 경우에 어떻게 동작하는지는 정의되지 않았다.
1.2.2.2. closure를 통한 영구 변수 ( Persistent variables with closures )
렉시컬 변수는 C 언어에서 auto 변수와 비슷하나,
- 가비지 컬렉션이 적용된다.
- Perl의 렉시컬 변수는 스코프를 빠져나갔다고 반드시 회수되는 건 아니다. 더 오래 유지되는 다른 무엇인가가 그 렉시컬 변수의 존재를 인지하고 있는 상태라면, 그 변수도 회수되지 않고 남아 있게 된다.
- C 언어에서는 오토 변수의 포인터를 리턴하는 건 심각한 실수인 반면, Perl에서는 렉시컬 변수의 레퍼런스를 되돌려주거나 저장해 둘 수 있다.
C에서 함수 내 정적(static) 변수와 같은 효과를 내도록 하는 법
- 렉시컬 스코핑과 정적 라이프타임을 동시에 갖게 됨
- 전체 함수를 외부에서 블록으로 감싸고, 정적 변수로 쓸 변수를 함수보다는 바깥쪽이고 블록보다는 안쪽인 영역에서 선언
{
my $secret_val = 0;
sub gimme_another {
return ++$secret_val;
}
}
- 이 함수가
require
나 use
를 통해 불릴 때는 상관이 없는데, 메인 프로그램 내에 있을 경우는 이 함수를 호출하기 전에 my
선언이 먼저 실행되도록 배치해야 한다. 이 블록 전체를 메인 프로그램 앞부분에 두거나, BEGIN
블록을 사용한다, perlmod의 "BEGIN, UNITCHECK, CHECK, INIT and END" 섹션 참조
BEGIN {
my $secret_val = 0;
sub gimme_another {
return ++$secret_val;
}
}
가장 바깥쪽 스코프(파일 스코프)에서 선언된 렉시컬 변수는 C 언어에서 파일 스태틱와 유사하게 동작한다.
- 그 파일 내의 모든 함수에서 사용 가능
- 파일 외부에서는 접근할 수 없음
- 모듈 내에서 공용으로 사용하는 private 변수를 만들 때 사용 가능
1.2.3. local()을 통한 임시값 ( Temporary Values via local() )
경고: 일반적으로는 local
대신 my
를 사용하라, 그것이 더 빠르고 안전하다.
- 불가피한 경우는 전역 특수 변수나 전역 파일핸들, 심볼 테이블을 직접 접근할 때 등이다
- 호출되는 서브루틴 쪽에서도 변수값을 볼 수 있어야 할 때
local
이 많이 사용됨
local $foo;
local (@wid, %get);
local $foo = "flurp";
local @oof = @bar;
local $hash{key} = "val";
delete local $hash{key};
local ($cond ? $v1 : $v2);
local *FH;
local *merlyn = *randal;
local *merlyn = 'randal';
local *merlyn = \$randal;
local
은 나열된 변수들을 둘러싸고 있는 블록, eval
, do 파일
에 "지역적"이 되게 하며, 그 블록 내에서 호출하는 다른 서브루틴 내에도 적용되도록 한다.
- 전역 변수(즉 패키지 변수)에 일시적인 임시값을 할당
- 지역 변수를 생성하는게 아니다
- 동적 스코핑(dynamic scoping)이라 불림
- 반면에 렉시컬 스코핑은
my
를 사용하여 하며, C의 auto 선언과 유사하게 동작한다.
단순 변수 뿐 아니라 몇 가지 유형의 L밸류들이 지역화될 수 있다:
- 해쉬와 배열의 엘리먼트, 슬라이스
- 조건식 (결과가 지역화될 수 있는 형태인 경우)
- 심볼릭 레퍼런스
- 이 경우 단순 변수와 마찬가지로, 동적 스코프를 적용받는 새로운 값을 생성한다.
하나 이상의 변수나 식이 local
에 주어질 때는 괄호로 둘러쌀 것
- 괄호 안의 인자 리스트에 있는 변수들의 현재 값을 숨겨진 스택에 저장해 두고, 블록이나 서브루틴, eval 등을 빠져나갈 때 그 값을 복원한다.
- 스코프 내에서 다른 서브루틴을 호출할 경우 그 서브루틴에서도 지역화된 값을 참조할 수 있게 된다. 반대로 원래의 전역 변수의 값에는 접근할 수 없다
- 인자 리스트에 할당을 함으로써 초기화를 할 수 있다. (초기화하지 않을 경우 undef 값이 들어있는 상태로 생성됨)
local
은 런타임 연산자이므로, 루프를 돌 때마다 실행된다. 따라서 루프 바깥쪽에서 지역화를 하는 게 더 효율적이다.
1.2.3.1. local()의 문법에 관하여 ( Grammatical note on local() )
local
은 L밸류 표현식에 적용되는 변경자이며, 지역화된 변수에 할당을 할 때 스칼라로 간주될지 리스트로 간주될지에 영향을 주지 않는다. 따라서
- 다음 두 구문은 우변에 리스트 문맥을 적용한다:
local($foo) = <STDIN>;
local @FOO = <STDIN>;
local $foo = <STDIN>;
1.2.3.2. 특수 변수들의 지역화 ( Localization of special variables )
특수 변수를 지역화할 경우, 새로운 값을 부여하지만 그 변수의 특수한 기능은 사라지지 않는다. 즉 그 기능과 관련된 부수작용(side-effects)들은 지역화된 값에 따라서 동작한다.
{ local $/ = undef; $slurp = <FILE>; }
반면 이 특성 때문에 어떤 값들은 지역화하는 데 제한이 있다.
- perl 5.9.0 이후 코드에서는 다음 구문을 실행하면 Modification of a read-only value attempted 에러를 내며 죽는다. $1 변수는 특수 변수이며 읽기 전용이기 때문이다:
local $1 = 2;
- 다음 코드도 유사한데 좀 더 원인을 찾기 어렵다:
sub f { local $_ = "foo"; print }
for ($1) {
f();
}
경고: tie된 배열과 해쉬를 지역화하는 건 지금까지 설명한 것처럼 동작하지 않는다. 이런 코드를 사용하는 것을 피할 것. (개별 원소를 지역화하는 것은 괜찮음) 이에 관해서는 perl58delta의 "Localising Tied Arrays and Hashes Is Broken" 섹션 참조.
1.2.3.3. glob 지역화 ( Localization of globs )
다음 구문은 현재 패키지에 name
glob을 위한 완전히 새로운 심볼 테이블 엔트리를 생성한다:
local *name;
- 이 glob 슬롯에 들어있는 모든 변수($name, @name, %name, &name,
name
파일핸들)가 동적으로 리셋된다
- 이 변수들에 의해 수행되는 모든 특수기능(magic)들이 사라지게된다.
- 예를 들어
local */
는 "입력 레코드 구분자"의 내부 값에 영향을 미치지 않는다.
- 특히, 디폴트 스칼라 변수인 $_에 완전히 새로운 값을 할당하면서, 위 섹션에서 언급했듯이 $_가 특수한 값을 담고 있는 경우 발생할 수 있는 문제점을 피하려면,
local $_
대신에 local */
를 사용해야 한다.
- perl 5.9.1 부터는 $_의 렉시컬 형태(
my $_
로 선언한다)를 써서 이 문제를 완전히 피할 수 있다.
1.2.3.4. 구조형 변수의 원소 지역화 ( Localization of elements of composite types )
구조형 변수(배열 또는 해쉬)의 원소를 지역화할 때,
- 그 원소의 이름을 가지고 지역화 대상으로 삼는다
-
local()
이 적용되는 스코프가 끝났을 때, local()
에서 명명했던 이름을 키값으로 하는 해쉬원소의 기존 값이 복원된다. 배열의 경우는 명명됐던 인덱스에 해당하는 원소의 값이 복원된다.
-
local()
이 적용된 상태에서 원소가 제거되었을 경우(해쉬에서 delete()
를 쓰거나 배열에shift
를 하는 경우처럼), 복원될 때 배열이 확장될 수 있고 이 때 중간에 끼인 원소들은 undef 값을 갖게 된다.
%hash = ( 'This' => 'is', 'a' => 'test' );
@ary = ( 0..5 );
{
local($ary[5]) = 6;
local($hash{'a'}) = 'drill';
while (my $e = pop(@ary)) {
print "$e . . .\n";
last unless $e > 3;
}
if (@ary) {
$hash{'only a'} = 'test';
delete $hash{'a'};
}
}
print join(' ', map { "$_ $hash{$_}" } sort keys %hash),".\n";
print "The array has ",scalar(@ary)," elements: ",
join(', ', map { defined $_ ? $_ : 'undef' } @ary),"\n";
6 . . .
4 . . .
3 . . .
This is a test only a test.
The array has 6 elements: 0, 1, 2, undef, undef, 5
존재하지 않는 원소에 대해 local()을 사용할 경우 어떻게 동작할 것인지는 향후에 변경될 수 있다.
1.2.3.5. 구조형 변수의 원소를 지역화 후 삭제 ( Localized deletion of elements of composite types )
주인장 주: perl 5.12 부터 지원하는 기능으로, 5.10에서는 마지막 예제 코드를 실행하면 설명과 다른 결과가 나옴
delete local $array[$idx]
또는 delete local $hash{$key}
를 사용하여 구조형 변수의 원소를 현재 블록에서만 제거하고 블록이 끝난 후 복원할 수 있다.
- 이 구문들은 지역화하기 전에 담겨 있던 값을 반환한다.
- 다음 코드와 동등하다. 단 다음 코드들에서
local
의 스코프가 do
블록으로 한정된다는 것이 다르다:
do {
my $val = $array[$idx];
local $array[$idx];
delete $array[$idx];
$val
}
do {
my $val = $hash{key};
local $hash{key};
delete $hash{key};
$val
}
my %hash = (
a => [ 7, 8, 9 ],
b => 1,
);
{
my $a = delete local $hash{a};
{
my @nums = delete local @$a[0, 2];
$a[0] = 999;
}
}
1.2.4. L밸류 서브루틴 ( Lvalue subroutines )
경고: L밸류 서브루틴은 실험적인 기능이고, Perl 버전이 변경되면서 구현이 바뀔 수 있음
서브루틴이 수정가능한 값을 반환하게 하는 게 가능하다
- 서브루틴을 선언할 때 L밸류를 반환한다고 선언:
my $val;
sub canmod : lvalue {
$val;
}
sub nomod {
$val;
}
canmod() = 5;
nomod() = 5;
좌변의 서브루틴과 우변의 문맥은, 좌변의 서브루틴을 호출하는 자리를 스칼라로 바꿨을 때 적용되는 것과 같이 적용된다:
- 다음의 좌우 두 서브루틴은 스칼라 문맥에서 호출된다:
data(2,3) = get_data(3,4);
- 다음 경우는 모든 서브루틴이 리스트 문맥에서 호출된다:
(data(2,3)) = get_data(3,4);
(data(2),data(3)) = get_data(3,4);
L밸류 서브루틴은 실험적인 기능이다
- 편해보이지만, 여러 가지 이유로 인해 사용에 주의를 기울여야 한다
- return 키워드를 사용할 수 없고, 서브루틴 스코프의 밖으로 나가기 전에 값을 전달해야 한다. 이것 자체가 문제는 아닌데, 다중으로 중첩된 루프 내에서 명시적으로 리턴하는 것이 불가능해진다.
- 캡슐화를 어기게 된다. 보통 뮤테이터는 자신이 보호하는 속성값을 설정하기 전에 이 값이 올바른지 체크를 하는데, L밸류 서브루틴은 그럴 수 없다:
my $some_array_ref = [];
sub set_arr {
my $val = shift;
die("expected array, you supplied ", ref $val)
unless ref $val eq 'ARRAY';
$some_array_ref = $val;
}
sub set_arr_lv : lvalue {
$some_array_ref;
}
set_arr_lv() = { a => 1 };
1.2.5. 심볼테이블 엔트리 넘겨주기 (typeglobs) ( Passing Symbol Table Entries (typeglobs) )
경고: 이 섹션에 나오는 방법은 오래된 버전의 Perl에서는 참조에 의한 호출을 흉내낼 수 있는 유일한 방법이었다. 최신 버전의 Perl에서도 여전히 동작하긴 하지만, 레퍼런스를 사용하는 새로운 방법이 일반적으로 더 쉽다.
배열 같은 변수의 값이 아니라 이름을 서브루틴에 넘겨주어서, 서브루틴에서 그 변수의 지역화된 복사본이 아니라 원본 자체를 수정할 수 있도록 하고 싶은 경우
-
*foo
처럼, 이름 앞에 별표를 붙여서 그 이름에 해당되는 모든 오브젝트를 참조할 수 있다.
- 이것을 "typeglob"라고 부른다
타입글로브를 평가하면, 파일핸들, 포맷, 서브루틴을 포함하여 그 이름을 가진 모든 오브젝트를 나타내는 스칼라값을 생산한다. 타입글로브에 뭔가를 할당하면, 할당된 것을 타입글로브에 언급된 이름이 참조하게 된다.
sub doubleary {
local(*someary) = @_;
foreach $elem (@someary) {
$elem *= 2;
}
}
doubleary(*foo);
doubleary(*bar);
스칼라는 원래부터 참조에 의해 전달되므로, 이런 방법을 사용하지 않고 명시적으로 $_[0]
등을 참조함으로써 스칼라 인자의 값을 수정할 수 있다. 배열도 배열의 모든 원소를 스칼라로 전달함으로써 그 원소들을 수정할 수 있다. 그러나 배열에 push
, pop
등을 수행하거나 배열의 크기를 바꾸기 위해서는 타입글로브 또는 레퍼런스를 사용하여야 한다.
이 방법은 하나의 리스트에 다수의 배열을 담아 전달할 때도 유용하다. 일반적으로는 다수의 배열을 인자로 넘길 경우는 모든 배열의 값들이 하나의 리스트로 합쳐져서 각각의 배열을 분리할 수 없기 때문이다.
타입글로브에 대한 더 자세한 것은 perldata의 "Typeglobs and Filehandles" 섹션 참조
1.2.6. 여전히 local()을 사용해야 하는 경우들 ( When to Still Use local() )
my
가 있지만 여전히 local
연산자가 빛을 발하는 세 가지 경우가 있으며, 이 때는 my 대신에 local을 사용해야만 한다
1. 전역 변수, 특히 $_에 임시값을 줄 때
-
@ARGV
나 특수문자변수들은 local()
을 사용하여 지역화하여야 한다.
- 아래 코드는 /etc/motd 파일을 읽고, 등호로 이루어진 라인들을 구분자로 삼아서 여러 덩어리로 분리한 후 @Fields 배열에 담는다:
{
local @ARGV = ("/etc/motd");
local $/ = undef;
local $_ = <>;
@Fields = split /^\s*=+\s*$/;
}
- 특히, $_에 값을 할당하는 루틴 안에서는 $_를 지역화하는 것이 중요하다.
while
조건문 안에서는 묵시적으로 할당하는 곳을 살펴보라
2. 지역화된 파일핸들,디렉토리핸들,함수를 생성해야 할 때
- 자신만의 파일핸들이 필요한 함수는 완전한 타입글로브를 대상으로
local()
을 사용하여야 한다. 이것은 새로운 심볼 테이블 엔트리를 생성할 때 사용할 수 있다:
sub ioqueue {
local (*READER, *WRITER);
pipe (READER, WRITER) or die "pipe: $!";
return (*READER, *WRITER);
}
($head, $tail) = ioqueue();
- 익명 심볼 테이블 엔트리를 생성하는 방법은 Symbol 참조
- 타입글로브에 레퍼런스를 할당하면 별칭(alias)가 생성되며, 이것은 지역화된 함수(또는 정확히는 지역화된 별칭)을 생성하는 데 사용된다:
{
local *grow = \&shrink;
grow();
move();
}
grow();
- 함수의 이름을 가지고 함수를 조작하는 것에 대해 더 자세한 내용은 perlref의 "Function Templates" 섹션 참조
3. 배열이나 해쉬의 원소 하나를 임시로 변경하고 싶을 때
- 집합적 데이타의 원소 하나만 지역화할 수 있다.
- 보통은 동적 데이타를 대상으로 사용:
{
local $SIG{INT} = 'IGNORE';
funct();
}
- 렉시컬 데이타에도 사용가능
- Perl 5.5 이전 버전에서는 예상대로 동작하지 않을 수 있음
1.2.7. 참조에 의한 전달 ( Pass by Reference )
둘 이상의 배열 또는 해쉬를 함수에 전달하거나 함수로부터 반환받으면서 그것들의 속성까지 유지되기를 바란다면 명시적인 참조에 의한 전달을 사용해야 한다.
몇가지 예제
- 여러 개의 배열을 함수에 전달하고 각 배열에
pop
을 수행한 후 그렇게 뽑아낸 원소들의 리스트를 반환:
@tailings = popmany ( \@a, \@b, \@c, \@d );
sub popmany {
my $aref;
my @retlist = ();
foreach $aref ( @_ ) {
push @retlist, pop @$aref;
}
return @retlist;
}
- 해쉬들을 함수에 전달하여 모든 해쉬에 공통으로 들어있는 키들의 리스트를 반환:
@common = inter( \%foo, \%bar, \%joe );
sub inter {
my ($k, $href, %seen);
foreach $href (@_) {
while ( $k = each %$href ) {
$seen{$k}++;
}
}
return grep { $seen{$_} == @_ } keys %seen;
}
여기까지는 일반적인 리스트 반환 메커니즘을 사용했다. 해쉬를 전달하거나 리턴하고 싶은 경우는 어떻게 할 것인가? 하나의 해쉬만 리턴하거나, 해쉬들이 병합되어도 상관없다면 이런 일반적인 호출 형태도 상관없다.
문제가 되는 경우:
(@a, @b) = func(@c, @d);
또는
(%a, %b) = func(%c, %d);
- 위 코드는
@a
와 %a
에 모든 리턴값이 들어가고 @b
와 %b
는 깨끗이 비게 된다. 또한 함수는 두 개의 분리된 배열이나 해쉬를 전달받을 수 없고, 항상 하나의 긴 리스트 @_
를 받는다.
레퍼런스를 사용하여서, 아주 보기 좋진 않더라도 더 깔끔한 코드를 만들 수 있다.
- 두 개의 배열 레퍼런스를 인자로 받고, 두 개의 배열 레퍼런스를 반환하는데 이 때 원소의 갯수가 큰 순으로 반환하는 함수:
($aref, $bref) = func(\@c, \@d);
print "@$aref has more than @$bref\n";
sub func {
my ($cref, $dref) = @_;
if (@$cref > @$dref) {
return ($cref, $dref);
} else {
return ($dref, $cref);
}
}
(*a, *b) = func(\@c, \@d);
print "@a has more than @b\n";
sub func {
local (*c, *d) = @_;
if (@c > @d) {
return (\@c, \@d);
} else {
return (\@d, \@c);
}
}
- 여기서는 타입글로브를 써서 심볼 테이블의 별칭을 만들었다. 그러나 이런 방식은 좀 미묘한 구석이 있고,
my
변수들을 사용하고 있다면 제대로 동작하지 않는다. 심볼 테이블에는 전역 변수(local
로 지역화한 경우라도)만 들어있기 때문이다.
파일핸들을 전달하려고 할 때는 *STDOUT
처럼 타입글로브를 그대로(bare typeglob) 쓸 수도 있고, 타입글로브의 레퍼런스를 쓸 수도 있다:
splutter(\*STDOUT);
sub splutter {
my $fh = shift;
print $fh "her um well a hmmm\n";
}
$rec = get_rec(\*STDIN);
sub get_rec {
my $fh = shift;
return scalar <$fh>;
}
새로운 파일핸들을 생성할 경우는 다음과 같이 할 수 있다. 이 때는 레퍼런스가 아니라 있는 그대로의(bare) *FH를 넘기는 것에 유의하라
sub openit {
my $path = shift;
local *FH;
return open (FH, $path) ? *FH : undef;
}
주인장 코멘트:
openit()을 호출하는 쪽에서는 *IN = openit()
와 같이 파일핸들 타입글로브를 쓸 수도 있는데, 이 경우 openit()이 undef을 반환할 경우 경고가 뜬다.
최신 Perl에서는 파일핸들을 스칼라변수에 담아 쓸 수 있으므로, my $in = openit()
와 같이 사용하는 것이 낫지 싶다.
1.2.8. 프로토타입 ( Prototypes )
Perl은 함수 프로토타입을 사용하여 매우 제한된 종류나마 컴파일타임에 인자를 체크하는 것을 지원한다.
- 다음과 같이 선언할 경우,
mypush
는 push()
와 똑같은 형태의 인자를 받는다:
sub mypush (\@@)
- 함수 선언은 컴파일 타임에 보여야 한다
- 프로토타입은 신식 스타일, 즉
&
를 함수명 앞에 쓰지 않는 스타일로 함수를 호출할 때만 영향을 끼친다.
-
\&foo
와 같은 서브루틴 레퍼런스나 &{$subref}
또는 $subref->()
와 같은 간접 호출에는 적용되지 않는다.
- 메쏘드 호출도 프로토타입의 영향을 받지 않는다. 실제로 호출될 함수가 무엇인지는 상속 관계에 따라 달라지므로 컴파일 시간에 결정할 수 없기 때문이다.
프로토타입 기능의 주 목적은 서브루틴을 내장함수들처럼 동작하도록 정의할 수 있게 하는 것이다
- 대응되는 내장함수와 거의 정확히 동일하게 구문이 해석되는 함수들의 프로토타입 예제:
아래와 같이 선언하고 아래와 같이 호출함
sub mylink ($$) mylink $old, $new
sub myvec ($$$) myvec $var, $offset, 1
sub myindex ($$;$) myindex &getstring, "substr"
sub mysyswrite ($$$;$) mysyswrite $buf, 0, length($buf) - $off, $off
sub myreverse (@) myreverse $a, $b, $c
sub myjoin ($@) myjoin ":", $a, $b, $c
sub mypop (\@) mypop @array
sub mysplice (\@$$@) mysplice @array, 0, 2, @pushme
sub mykeys (\%) mykeys %{$hashref}
sub myopen (*;$) myopen HANDLE, $name
sub mypipe (**) mypipe READHANDLE, WRITEHANDLE
sub mygrep (&@) mygrep { /foo/ } $a, $b, $c
sub myrand (;$) myrand 42
sub mytime () mytime
백슬래쉬가 붙은 프로토타입 문자들은, 실인자가 정확히 그 문자로 시작해야 함을 의미한다. @_
에 담겨 전달되는 값은 그 실인자 앞에 \
를 붙여 얻은 레퍼런스가 된다.
-
\[]
표기를 사용하여 여러 인자 타입에 동시에 백슬래쉬를 적용할 수 있다:
sub myref (\[$@%&*])
- 위와 같이 선언된 함수는 아래와 같이 호출할 수 있다:
myref $var
myref @array
myref %hash
myref &sub
myref *glob
- 실제로 myref()로 넘어가는 첫번째 인자는 스칼라,배열,해쉬,코드,글로브의 레퍼런스가 된다.
백슬래쉬가 붙지 않은 프로토타입 문자들은 특별한 의미를 갖는다
-
@
와 %
는 남아있는 인자 전부를 모아서 사용하고, 리스트 문맥을 강제한다
-
$
는 스칼라 문맥을 강제한다.
-
&
는 익명 서브루틴을 요구한다. 이 익명 서브루틴이 첫번째 인자일 경우는 sub
키워드나, 뒤에 오는 컴마를 생략할 수 있다
-
*
는 서브루틴이 bareword, 상수, 스칼라 식, 타입글로브, 타입글로브의 레퍼런스 등을 받아들일 수 있음을 의미한다.
- 서브루틴 쪽에서는 넘어온 인자를 단순한 스칼라 값 또는 (타입글로브 또는 타입글로브의 레퍼런스를 전달한 경우) 타입글로브의 레퍼런스로 받는다.
- 이 인자들을 언제나 타입글로브 레퍼런스로 변환하려면 Symbol::qualify_to_ref()를 사용하라:
use Symbol 'qualify_to_ref';
sub foo (*) {
my $fh = qualify_to_ref(shift, caller);
...
}
세미콜론(;)은 필수적인 인자와 선택적인 인자를 구분한다.
- 나머지 인자를 전부 취하게 되는
@
나 %
앞에는 쓸 필요가 없다.
프로토타입의 가장 마지막 자리 또는 세미콜론 바로 앞자리에 $
가 들어갈 경우 그 대신에 _
를 쓸 수 있다
- 만일 호출할 때 인자가 없다면
$_
가 대신 쓰이게 된다.
위 테이블에서 마지막 세 가지 예제가 구문해석기에 의해 어떻게 해석되는지 유의하라:
-
mygrep()
은 순수하게 리스트 연산자로만 해석된다.
-
myrand()
는 rand()
와 같은 단항 우선순위를 갖는 단항연산자로만 해석된다
-
mytime()
은 time()
처럼 순수하게 인자가 없는 연산자로 해석된다
- 따라서 다음 코드는
mytime() + 2
를 얻게 된다. 프로토타입이 없었다면 mytime(2)
로 해석되었을 것이다:
mytime +2;
&
와 관련된 흥미로운 것은, 이게 제일 앞자리에 주어질 경우 이것을 사용하여 새로운 문법을 생성할 수 있다는 것이다:
sub try (&@) {
my($try,$catch) = @_;
eval { &$try };
if ($@) {
local $_ = $@;
&$catch;
}
}
sub catch (&) { $_[0] }
try {
die "phooey";
} catch {
/phooey/ and print "unphooey\n";
};
다음은 Perl의 grep
연산자를 새로 구현한 코드:
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (@_) {
push(@result, $_) if &$code;
}
@result;
}
일부 펄 유저들은 프로토타입에 알파벳과 숫자를 사용한 이름을 쓸 수 있기를 원한다.
- 이런 문자숫자 조합은 차후에 이름이 부여된 형식인자(named,formal parameter)가 펄에 추가될 때를 위해 남겨져 있다.
- 현재의 방식의 주 목표는 모듈을 작성하는 사람이 모듈 사용자에게 더 좋은 사용법을 제공할 수 있게 하려는 것이다.
- 현재는 프로토타입 자리에 이런 알파벳과 숫자를 사용할 경우 "Illegal character in prototype"이라는 경고가 뜬다.
- 오래된 버전의 Perl에서는 접두어가 올바른 프로토타입일 경우 이렇게 알파벳과 숫자가 들어간 프로토타입을 쓸 수 있었다. 당시 그런 프로토타입을 썼던 코드들의 대부분이 수정되고 나면 향후 Perl 버전에서는 경고 대신 치명적 에러가 나도록 고쳐질 것이다.
기존에 있던 함수에 프로토타입을 도입하여 개선하려 하지 말고, 새로운 함수를 만들어 프로토타입을 적용하는 게 최선일 것이다.
- 프로토타입을 적용함으로써 리스트 문맥과 스칼라 문맥의 구분을 사용자가 모르는 새 강제하게 될 수 있기 때문이다
- 예:
- 함수가 오직 하나의 인자만 받아야 한다고 결정했다고 하자:
sub func ($) {
my $n = shift;
print "you gave me $n\n";
}
- 만일 누군가 기존에 이 함수를 배열이나, 리스트를 반환하는 식을 인자로 주어 호출했었다면:
func(@foo);
func( split /:/ );
- 프로토타입을 사용한 이후엔 위 코드의 인자 앞에 자동으로
scalar
가 붙게 된다.
- 원소가 하나인
@foo
가 전달되지 않고, @foo의 원소의 갯수인 1
이 전달된다.
-
split
이 스칼라 문맥에서 호출되게 되어, @_
인자리스트를 엉망으로 만들 것이다
1.2.9. 상수 함수 ( Constant Functions )
프로토타입이 ()
인 함수들은 인라인함수가 될 가능성이 있다.
- 함수를 최적화 및 상수폴딩한 후의 결과가 상수값이거나, 다른 레퍼런스가 없고 렉시컬 스코프를 갖는 스칼라인 경우,
&
를 붙이지 않은 형태의 함수호출 자리에 치환되어 사용된다.
-
&
를 붙인 형태의 호출은 인라인이 되지 않는다
- 상수를 선언하는 쉬운 방법은 constant 참조
다음 함수들은 모두 인라인될 것이다:
sub pi () { 3.14159 }
sub PI () { 4 * atan2 1, 1 }
sub ST_DEV () { 0 }
sub ST_INO () { 1 }
sub FLAG_FOO () { 1 << 8 }
sub FLAG_BAR () { 1 << 9 }
sub FLAG_MASK () { FLAG_FOO | FLAG_BAR }
sub OPT_BAZ () { not (0x1B58 & FLAG_MASK) }
sub N () { int(OPT_BAZ) / 3 }
sub FOO_SET () { 1 if FLAG_MASK & FLAG_FOO }
다음 함수들은 인라인되지 않는다. 더 안쪽 스코프가 있고, 상수폴딩의 결과가 하나의 상수로 나오지 않기 때문이다:
sub foo_set () { if (FLAG_MASK & FLAG_FOO) { 1 } }
sub baz_val () {
if (OPT_BAZ) {
return 23;
}
else {
return 42;
}
}
인라인이 될 가능성이 있는 서브루틴을 재정의할 경우, 필수적인 경고가 발생한다. (이 경고를 이용해서 특정 서브루틴이 상수로 간주되는지 여부를 확인할 수 있다)
- 미리 컴파일된 함수호출이, 함수가 재정의되기 전에 가지고 있었던 값을 얻게될 우려가 있기 때문.
- 서브루틴을 재정의해야 하는 경우, 서브루틴이 인라인이 되지 않는 것을 확실히 해 두라
-
()
프로토타입을 없애던가 (이 경우 호출 인터페이스가 달라지니 주의)
- 인라인이 되지 못하도록 방해함:
sub not_inlined () {
23 if $];
}
1.2.10. 내장 함수 오버라이드 ( Overriding Built-in Functions )
많은 내장 함수들을 오버라이드할 수 있다.
- 그러나 타당한 이유가 있을 때에만 하고, 자주 하는 것도 지양할 것
- 보통은 어떤 패키지에서 Unix 계열이 아닌 시스템에서 제대로 동작하지 않는 내장 함수의 기능을 모방하는데 사용함
오버라이딩은 컴파일 타임에 함수를 모듈로부터 임포트하는 형태로 이뤄진다 -- 단지 호출 전 미리 선언하는 것만으로는 충분하지 못하다.
-
use subs
프라그마를 쓰면 import 문법을 사용하여 서브루틴을 미리 선언하고 내장 함수를 오버라이드할 수 있다:
use subs 'chdir', 'chroot', 'chmod', 'chown';
chdir $somewhere;
sub chdir { ... }
기존 내장함수를 참조하려는 의도를 명백하게 나타내려면, 내장 함수의 이름 앞에 특수한 패키지 한정자인 CORE::
를 붙인다.
- 예: 현재 패키지에 어디선가 불러온
&open()
서브루틴이 있더라도, CORE::open()
은 언제나 내장 함수 open()
을 가리킨다.
- 이 형태는 정규적인 함수 호출처럼 보이지만 실제로는 그렇지 않다,
\&CORE::open
과 같은 식으로 레퍼런스를 얻을 수 없다.
일반적으로 라이브러리 모듈들은 open
이나 chdir
과 같은 내장 함수의 이름을 기본 @EXPORT
리스트에 포함해서 익스포트하지 말아야 한다.
- 다른 사용자의 네임스페이스에서 예상하지 못한 의미의 변경을 초래할 수 있기 때문이다.
- 대신
@EXPORT_OK
목록에 넣어서 사용자가 그 이름을 명시적으로 임포트하여 쓸 수 있게 한다.
- 다음과 같이 선언하여 명시적으로
open
을 불러와서 오버라이드할 수 있다:
use Module 'open';
- 다음과 같이 하면 오버라이드하는 함수들을 제외하고 디폴트 목록만 임포트한다:
use Module;
위의 메커니즘은 임포트를 요청한 패키지 내에서만 오버라이드가 이뤄진다. 만일 네임스페이스의 경계를 넘어서 모든 곳에서 내장함수를 오버라이드하고자 하길 원한다면, 서브루틴을 특별한 네임스페이스인 CORE::GLOBAL::
안으로 임포트한다.
-
glob
연산자를 오버라이드하여 정규표현식을 인식할 수 있도록 한 예:
package REGlob;
require Exporter;
@ISA = 'Exporter';
@EXPORT_OK = 'glob';
sub import {
my $pkg = shift;
return unless @_;
my $sym = shift;
my $where = ($sym =~ s/^GLOBAL_// ? 'CORE::GLOBAL' : caller(0));
$pkg->export($where, $sym, @_);
}
sub glob {
my $pat = shift;
my @got;
if (opendir my $d, '.') {
@got = grep /$pat/, readdir $d;
closedir $d;
}
return @got;
}
1;
package Foo;
use REGlob 'glob';
print for <^[a-z_]+\.pm\$>;
- 위 예의 첫번째 라인에서처럼
glob
을 전역적으로 오버라이드할 경우 모든 네임스페이스에서 glob의 동작이 변경되게 되며, 그 네임스페이스를 담당하는 모듈 쪽에서 그 사실을 인지하지 못 할 수 있다. 따라서 이런 오버라이드는 꼭 필요한 경우에만 극도로 주의를 기울여서 행하라.
- 위 예의
REGlob
은 perl의 glob
을 오버라이드하기 위해 필요한 모든 기능을 제공하고 있지는 않다. 내장 glob은 스칼라 또는 리스트 문맥에서 호출되었을 때 서로 다르게 동작한다. 사실 많은 perl 내장 함수들이 이렇게 문맥에 따라 다르게 동작하며, 오버라이드할 경우 이런 동작을 지원해주어야 한다. glob
을 오버라이드하며 모든 기능을 지원하는 예는 표준 라이브러리에 있는 File::DosGlob
을 살펴보라.
내장 함수를 오버라이드할 경우, 대체되는 함수도 가능하면 내장 함수의 문법을 그대로 쓸 수 있게 작성해야 한다.
- 적절한 프로토타입을 사용
- 내장 함수의 프로토타입을 알아보기 위해서는
prototype
함수에 "CORE::내장함수이름"
인자를 넣으면 된다. (perlfunc의 "prototype" 참조)
어떤 내장 함수(system
이나 chomp
등)는 사용 문법을 프로토타입으로 표현할 수 없다. 이런 함수를 오버라이드할 경우는 그 함수의 원래의 문법을 완벽하게 모방할 수 없을 것이다.
내장 do
, require
, glob
은 오버라이드 할 수는 있으나, 그 함수들의 특수한 기능들 때문에, 함수의 문법이 보존된다. 따라서 오버라이드하는 대체함수를 위한 프로토타입을 정의할 필요가 없다. (다만 do BLOCK
문법을 오버라이드하는 것을 불가능하다)
require
는 이 외에도 특별한 기능이 있다: require Foo::Bar
의 형태로 오버라이드한 함수를 호출하면, 그 함수에 @_에 담겨서 실제로 전달되는 인자는 "Foo/Bar.pm"
이다. perlfunc의 "require"를 참조하라.
위에서 보았듯이 glob
을 오버라이드하면 글롭 연산자 <*>
도 같이 오버라이드된다.
비슷하게, readline
함수를 오버라이드하면 이와 동등한 I/O연산자 <파일핸들>
도 오버라이드된다. 또한 readpipe
을 오버라이드하면 ``
와 qw//
연산자도 오버라이드된다.
끝으로, 일부 내장함수(exists
또는 grep
등)은 오버라이드할 수 없다.
1.2.11. 자동 로드 ( Autoloading )
정의되지 않은 서브루틴을 호출할 경우 곧바로 치명적 에러가 발생하며 종료된다. (메쏘드로 사용되는 서브루틴의 경우 클래스 패키지와 그 상위클래스 어디에도 그 메쏘드가 없는 경우도 마찬가지)
- 그러나
AUTOLOAD
서브루틴이 그 패키지 또는 원래 서브루틴을 검색하는 데 사용되는 패키지 중에 정의되어 있을 경우, 이 서브루틴이 호출되며 원래의 서브루틴에 넘어갈 인자가 이 서브루틴으로 넘어간다.
- 원래 서브루틴의 fully qualified된 이름은
AUTOLOAD
루틴이 있는 패키지에 속한 $AUTOLOAD
전역 변수에 담긴다.
- 예외적으로 존재하지 않는
import
또는 unimport
메쏘드를 호출하는 것은 그냥 무시된다.
많은 경우 AUTOLOAD
루틴은 eval()을 사용하여 원래 요청된 서브루틴의 정의를 로드한 후, AUTOLOAD의 스택프레임을 삭제할 수 있는 특별한 형태의 goto()를 사용하여 그 서브루틴을 실행한다. (예를 들어 AutoLoader 에 문서화되어 있는 표준 모듈의 소스를 참조하라) 하지만 단순히 원래의 서브루틴의 흉내만 내고 전혀 정의하지 않을 수도 있다.
- 정의되지 않은 함수는
system
을 호출하여 실행하도록 하는 예:
sub AUTOLOAD {
my $program = $AUTOLOAD;
$program =~ s/.*:://;
system($program, @_);
}
date();
who('am', 'i');
ls('-l');
- 각 함수를 저렇게 호출할 것을 미리 선언하면 괄호까지 생략가능하다:
use subs qw(date who ls);
date;
who "am", "i";
ls '-l';
더 복잡한 예는 표준 Shell에서 볼 수 있다, 여기서는 존재하지 않는 서브루틴 호출은 외부 프로그램을 호출하는 것으로 취급된다.
이런 자동 로드 메커니즘은 모듈 작성자가 자신의 모듈을 자동으로 로드할 수 있는 파일들로 분리하는 것을 돕기 위해 존재한다. AutoLoader, AutoSplit, SelfLoader, perlxs 등을 참조하라.
1.2.12. 서브루틴 속성 ( Subroutine Attributes )
이 섹션의 내용은 주인장도 잘 모르는 부분이라, 번역에 오류가 있을 가능성이 높습니다
서브루틴의 선언이나 정의에는 그 서브루틴과 연관된 속성을 나열해 줄 수 있다.
- 이런 속성 목록이 존재할 경우,
use attributes
프라그마를 사용한 것처럼 목록을 콜론 또는 공백을 구분자로 해서 각각을 나눈 후에 처리된다.
- 현재 지원되는 속성들에 대해서는 attributes 참조
- 구식 방법은
use attrs
와는 달리, sub : 속성리스트
형태의 문법은 꼭 서브루틴 정의 안에 있어야 되는 게 아니라 미리 선언하는 곳에 있어도 된다.
각 속성의 명칭들은 간단한 식별자로서 사용할 수 있는 형태여야 한다 ('_' 문자를 제외하고는 구두점을 쓸 수 없다). 각 속성에는 파라메터 리스트가 뒤에 붙을 수 있으며, 괄호가 정확하게 중첩되었는지만 체크된다.
올바른 문법의 예: (실제 이런 이름의 속성 자체는 존재하지 않지만)
sub fnord (&\%) : switch(10,foo(7,3)) : expensive;
sub plugh () : Ugly('\(") :Bad;
sub xyzzy : _5x5 { ... }
올바르지 않은 문법의 예:
sub fnord : switch(10,foo();
sub snoid : Ugly('(');
sub xyzzy : 5x5;
sub plugh : Y2::north;
sub snurt : foo + bar;
속성 리스트는 그 서브루틴과 속성을 연결시키는 코드에 상수 문자열 리스트 형태로 전달된다. 특히 위의 두번째 예문은 다음과 같이 해석되고 수행된다:
use attributes __PACKAGE__, \&plugh, q[Ugly('\(")], 'Bad';
속성 목록의 더 자세한 내용과 사용법에 대해서는 attributes와 Attribute::Handlers 참조
2. 다른 문서들에서
서브루틴 정의와 사용의 순서
- setup();처럼 괄호를 붙여주면 나중에 정의할 수 있다.
- &setup;처럼 쓸 수도 있다 - Perl 4에서의 형태
- setup; 과 같은 bareword 형태로 호출하기 위해서는 그 전에 서브루틴 정의가 있어야 한다.
- forward definition(pre-declaring)
sub setup;
use subs qw(setup get_input 등등);
setup;
sub setup { ... }
동일한 이름의 서브루틴이 둘 이상 있으면, 나중의 것이 적용된다.
Perl의 built-in function 과 동일한 이름의 서브루틴을 호출하는 경우는 반드시 "&"가 필요하다. 이런 이유로, Learning Perl에서는 "Perl의 모든 내장 함수의 이름을 알고 있지 않다면" 항상 "&"를 붙일 것을 권장한다.
그러나 최근 경향은 "&"를 앞에 붙이지 않는 것이다.
wantarray
- 리스트 문맥에서 수행 중이라면 true
- 스칼라 문맥이었다면 false
- void 문맥이었다면 undef
주인장이 네이버 까페에 쓴 [다양한 상황에서 컨텍스트 확인]도 참고할 것:
sub foo {
my $wa = wantarray;
if ( not defined $wa ) {
print "void context\n";
}
elsif ( $wa ) {
print "list context\n";
}
else {
print "scalar context\n";
}
return 3;
}
my $scalar;
my @array;
foo();
( foo() );
() = foo();
$scalar = foo();
print "scalar = [$scalar]\n";
( $scalar ) = foo();
print "scalar = [$scalar]\n";
$scalar = ( foo() );
print "scalar = [$scalar]\n";
$scalar = ( () = foo() );
print "scalar = [$scalar]\n";
@array = foo();
print "array = ", join(":", @array), "\n";
$array[foo()] = 10;
print "array = ", join(":", @array), "\n";
@array[foo()] = 20;
print "array = ", join(":", @array), "\n";
2.3. Prototype
파라메터의 갯수와 종류를 명시할 수 있다.
- 서브루틴을 호출하기 전에 프로토타입을 볼 수 있어야 하므로, 정의를 먼저 해 주던가 forward definition이 필요하다. 이 경우 실제 정의하는 부분과 프로토타입이 일치해야 함
sub sum_of_two_squares ($$);
print sum_of_two_squares($first, $second),"\n";
sub sum_of_two_squares ($$) {
my ($a,$b) = (shift, shift);
return $a**2+$b**2;
}
2.4. 인자로 레퍼런스 넘겨주기
호출하는 쪽에서 레퍼런스를 명시적으로 넘길 수도 있고
my $a = 5;
increment(\$a);
프로토타입에 "\"+"변수타입심볼 형태로 적어주면 자동으로 레퍼런스를 취한다
sub increment(\$);
my $a = 5;
increment($a);
sub increment(\$) {
my $reference = shift;
$$reference++;
}
2.5. 인자로 파일핸들 넘겨주기
/파일 참조
2.6. Named parameter
해쉬 형태로 인자 넘겨주기 - 어차피 받는 쪽은 @_로 받음.
- 인자의 순서를 신경쓸 필요가 없음.
- 인자의 갯수가 많고, optional한 경우에 매우 유용하다.
logon(username => $name, password => $pass, host => $hostname);
sub logon {
die "Parameters to logon should be even" if @_ % 2;
my %args = @_;
print "Logging on to host $args{hostname}\n";
...
}
2.7. 서브루틴의 레퍼런스
선언
sub something { print "Wibble!\n" }
my $ref = \&something;
my $ref = sub { print "Wibble!\n" };
호출
&{$ref};
&{$ref}(@parameters);
&$ref(@parameters);
$ref->();
$ref->(@parameters);
스코프를 벗어난 렉시컬 변수를 참조하는 서브루틴
콜백 함수의 예:
use File::Find;
my $total_size = 0;
find(sub { $total_size += -s if -f }, '.');
print $total_size, "\n";
클로져 안에서 접근하는 변수들은, 그 서브루틴의 레퍼런스가 존재하는 한 그 변수들도 계속 유지된다.
use File::Find;
my $callback;
{
my $count = 0;
$callback = sub { print ++$count, ": $File::Find::name\n" };
}
find($callback, '.');
2.9. 서브루틴을 반환하는 서브루틴
서브루틴의 레퍼런스를 반환하는 서브루틴
use File::Find;
sub create_find_callback_that_counts {
my $count = 0;
return sub { print ++$count, ": $File::Find::name\n" };
}
my $callback = create_find_callback_that_counts( );
print "my bin:\n";
find($callback, 'bin');
print "my lib:\n";
find($callback, 'lib');
my $callback1 = create_find_callback_that_counts( );
my $callback2 = create_find_callback_that_counts( );
print "my bin:\n";
find($callback1, 'bin');
print "my lib:\n";
find($callback2, 'lib');
둘 이상의 서브루틴을 반환하는 예:
use File::Find;
sub create_find_callbacks_that_sum_the_size {
my $total_size = 0;
return(sub { $total_size += -s if -f }, sub { return $total_size });
}
my %subs;
foreach my $dir (qw(bin lib man)) {
my ($callback, $getter) = create_find_callbacks_that_sum_the_size( );
$subs{$dir}{CALLBACK} = $callback;
$subs{$dir}{GETTER} = $getter;
}
for (keys %subs) {
find($subs{$_}{CALLBACK}, $_);
}
for (sort keys %subs) {
my $sum = $subs{$_}{GETTER}->( );
print "$_ has $sum bytes\n";
}
2.10. closure variable를 static local variable로 쓰기
named subroutine에서 렉시컬 변수의 레퍼런스를 유지하는 경우
{
my $count;
sub count_one { ++$count }
sub count_so_far { return $count }
}
count_one( );
count_one( );
count_one( );
print 'we have seen ', count_so_far( ), " coconuts!\n";
초기값을 따로 주어야 하는 경우는 주의
{
my $countdown = 10;
sub count_down { $countdown-- }
sub count_remaining { $countdown }
}
count_down( );
count_down( );
count_down( );
print 'we're down to ', count_remaining( ), " coconuts!\n";
아니면 해당 블록을 BEGIN 블록으로 지정
BEGIN {
my $countdown = 10;
sub count_down { $countdown-- }
sub count_remaining { $countdown }
}
2.11. 서브루틴을 인자로 넘기기
물론 서브루틴의 레퍼런스를 인자로 넘길 수도 있지만...
map, grep, sort 등과 같이 인라인 블록으로 전달하고 싶은 경우
- 서브루틴 프로토타입을 반드시 써주어야 함. ("&"을 사용)
- 예: (아래 코드의 주석은 주인장이 책의 내용을 참고하여 추가한 것이라서 틀렸을 수 있음)
use List::Util;
my $sum = reduce { $a + $b } @list;
package List::Util;
sub reduce (&@) {
my $code = shift;
no strict 'refs';
return shift unless @_ > 1;
use vars qw($a $b);
my $caller = caller;
local(*{$caller."::a"}) = \my $a;
local(*{$caller."::b"}) = \my $b;
$a = shift;
foreach (@_) {
$b = $_;
$a = &{$code}();
print "now a = $a, b = $b\n";
}
$a;
}
- 주인장 생각으로는 $a와 $b를 my 변수로 선언하고 있기 때문에, use vars 구문은 사실 없어도 상관없어 보인다. 실제로 없애고 테스트해도 잘 동작하고...
4. Comments
컴퓨터분류