Perl 문법
Perl 문법 (perlsyn)
Perl 프로그램은 위에서 아래로 실행되는 선언과 문장의 연속이에요. 루프·서브루틴·기타 제어 구조로 코드 안을 자유롭게 오갈 수 있죠. 이번 장에서는 선언·문장·주석을 중심으로 Perl 문법이 어떻게 돌아가는지 차근차근 살펴볼게요.
Perl 문법의 성격 (DESCRIPTION)
Perl은 자유 형식(free-form) 언어예요. 형식을 어떻게 잡고 들여쓰기를 어떻게 하든 상관없어요. 공백은 대개 토큰을 구분하는 데만 쓰이는데, 문법 자체의 중요한 부분인 Python이나 무의미한 Fortran과는 다른 점이에요.
Perl의 문법 요소 중 상당수는 선택적이에요. 모든 함수 호출에 괄호를 강제하고 모든 변수를 선언하라고 요구하는 대신, 그런 명시적 요소를 생략해도 Perl이 여러분이 무엇을 뜻하는지 알아서 파악해 줘요. 이걸 Do What I Mean, 줄여서 DWIM이라고 불러요. 프로그래머가 게으를 수 있고, 자기에게 편한 스타일로 코딩하게 해주는 거죠.
Perl은 awk·sed·C·Bourne Shell·Smalltalk·Lisp, 심지어 영어에서까지 문법과 개념을 차용했어요. 반대로 다른 언어들이 Perl에서, 특히 정규식 확장에서 문법을 빌려 가기도 했고요. 그래서 다른 언어로 프로그래밍해 본 적이 있다면 Perl에서 익숙한 조각들을 보게 될 거예요. 대개는 같은 방식으로 동작하지만, 어떻게 다른지에 대한 정보는 perltrap을 참고하세요.
선언 (Declarations)
Perl에서 선언해야 하는 것은 리포트 형식과 서브루틴뿐이에요(서브루틴조차 안 해도 되는 때가 있어요). 스칼라 변수는 정의된 값(undef가 아닌 어떤 값)이 할당되기 전까지는 정의되지 않은 값(C<undef>)을 담고 있어요. undef를 숫자로 쓰면 0으로, 문자열로 쓰면 빈 문자열 ""로 취급되고, 할당되지 않은 참조로 쓰면 오류로 취급돼요. 경고(warnings)를 켜면 undef를 문자열이나 숫자로 취급할 때마다 초기화되지 않은 값이 있다는 알림을 받게 돼요. 물론, 대개는요. 다음처럼 불리언 문맥은:
if ($x) {}
경고에서 면제돼요(정의됨보다 진리값에 관심이 있으니까요). ++, --, +=, -=, .=처럼 정의되지 않은 변수에 동작하는 연산자들:
undef $x;
$x++;
이것들도 항상 그런 경고에서 면제돼요.
선언은 문장이 올 수 있는 어디에나 둘 수 있지만, 기본 문장 시퀀스의 실행에는 영향이 없어요. 선언은 모두 컴파일 타임에 효력이 생기거든요. 모든 선언은 대개 스크립트의 시작이나 끝에 두죠. 다만 my(), state(), our()로 만든 어휘 범위(lexically-scoped) 비공개 변수를 사용한다면, 그 비공개 변수에 접근하려면 형식이나 서브루틴 정의가 my()와 같은 블록 범위 안에 있어야 해요.
서브루틴을 선언하면 그 시점부터 프로그램에서 그 이름을 리스트 연산자처럼 쓸 수 있어요. 정의 없이 선언만 하려면 sub name이라고 쓰면 돼요:
sub myname;
$me = myname $0 or die "can't get myname";
이런 단순 선언은 그 함수를 단항 연산자가 아니라 리스트 연산자로 선언하는 거라, 괄호(또는 || 대신 or)를 조심해서 써야 해요. || 연산자는 리스트 연산자 뒤에서 쓰기엔 너무 강하게 결합해서, 마지막 요소의 일부가 되어 버려요. 리스트 연산자 인자 주위에 괄호를 쓰면 리스트 연산자를 함수 호출처럼 동작하는 것으로 되돌릴 수 있어요. 또는 프로토타입 ($)를 써서 서브루틴을 단항 연산자로 만들 수도 있어요:
sub myname ($);
$me = myname $0 || die "can't get myname";
이제 기대한 대로 파싱되지만, 그 상황에서도 괄호를 쓰는 습관을 들이는 게 좋아요. 프로토타입에 대한 자세한 내용은 perlsub을 참고하세요.
서브루틴 선언은 require 문으로 불러올 수도 있고, use 문으로 불러오면서 동시에 네임스페이스로 가져올 수도 있어요. 자세한 내용은 perlmod를 참고하세요.
문장 시퀀스에는 어휘 범위 변수의 선언이 들어갈 수 있는데, 변수 이름을 선언하는 것 외에는 그 선언이 평범한 문장처럼 동작하고, 문장 시퀀스 안에서 평범한 문장처럼 처리돼요. 즉 실제로 컴파일 타임과 런타임 효과를 모두 가진다는 뜻이에요.
주석 (Comments)
"#" 문자부터 줄 끝까지의 텍스트는 주석으로 무시돼요. 단, 문자열이나 정규식 안의 "#"은 예외예요.
단순 문장 (Simple Statements)
유일한 종류의 단순 문장은 부수 효과(side-effects)를 위해 평가되는 표현식이에요. 모든 단순 문장은 세미콜론으로 끝나야 해요. 단, 블록 안의 마지막 문장이면 세미콜론이 선택 사항이죠. 그래도 블록이 한 줄 이상이면 세미콜론을 붙여 두세요. 나중에 줄을 하나 더 추가할 수도 있으니까요. eval {}, sub {}, do {} 같은 연산자들은 복합 문장처럼 보이지만 실제로는 아니고 — 표현식 안의 TERM일 뿐이라 — 문장의 마지막 항목으로 쓸 때는 명시적으로 종료해 줘야 해요.
문장 수식어 (Statement Modifiers)
모든 단순 문장은 선택적으로 끝나는 세미콜론(또는 블록 끝) 바로 앞에 하나의 수식어를 붙일 수 있어요. 가능한 수식어는 다음과 같아요:
if EXPR
unless EXPR
while EXPR
until EXPR
for LIST
foreach LIST
when EXPR
수식어 뒤의 EXPR을 "조건(condition)"이라고 불러요. 그 진리값이 수식어가 어떻게 동작할지 결정하죠.
if는 조건이 참일 때만 그 문장을 한 번 실행해요. unless는 반대로, 조건이 참이 아닐 때(즉 조건이 거짓일 때) 실행하죠. 참과 거짓의 정의는 perldata의 "Scalar values"를 참고하세요.
print "Basset hounds got long ears" if length $ear >= 10;
go_outside() and play() unless $is_raining;
for(each) 수식어는 반복자예요. LIST의 각 항목에 대해(그때그때 $_에 각 항목을 별칭으로 걸며) 문장을 한 번씩 실행하죠. 이 형태에서는 C 스타일 for 루프나 어휘 범위 반복 변수를 지정할 문법이 없어요.
print "Hello $_!\n" for qw(world Dolly nurse);
while은 조건이 참인 동안 그 문장을 반복해요. 후위(포스트픽스) while은 어떤 종류의 조건에는 전위(프리픽스) while과 같은 마법적 처리를 해줘요. until은 반대로, 조건이 참이 될 때까지(즉 조건이 거짓인 동안) 반복하죠:
# Both of these count from 0 to 10.
print $i++ while $i <= 10;
print $j++ until $j > 10;
while과 until 수식어는 일반적인 "while 루프" 의미론(조건을 먼저 평가)을 갖지만, do-BLOCK에 적용되면 블록이 조건을 평가하기 전에 한 번 실행돼요.
그래서 이런 루프를 쓸 수 있는 거예요:
do {
$line = <STDIN>;
...
} until !defined($line) || $line eq ".\n"
perlfunc의 do를 참고하세요. 수식어는 루프 라벨을 받지 않기 때문에, 아래에서 설명할 루프 제어 문장은 이 구조에서는 동작하지 않아요. 그런 일을 하려면 블록 안에(next/redo용) 또는 블록 주위에(last용) 다른 블록을 넣으면 돼요.
next나 redo는 중괄호를 두 겹으로:
do {{
next if $x == $y;
# do something here
}} until $x++ > $z;
last는 더 정교하게, 중괄호로 감싸야 해요:
{
do {
last if $x == $y**2;
# do something here
} while $x++ <= $z;
}
next와 last가 둘 다 필요하면 둘 다 하고 루프 라벨도 써야 해요:
LOOP: {
do {{
next if $x == $y;
last LOOP if $x == $y**2;
# do something here
}} until $x++ > $z;
}
NOTE: 조건부나 루프 구조로 수식된
my,state,our(예:my $x if ...)의 동작은 정의되어 있지 않아요.my변수의 값은undef일 수도, 이전에 할당된 어떤 값일 수도, 어쩌면 다른 무엇일 수도 있어요. 그걸 믿지 마세요. 미래의 perl 버전은 여러분이 시험해 본 버전과 다르게 동작할 수도 있어요. 여기엔 용이 있어요.
when 수식어는 Perl 5.14에서 처음 등장한 실험적 기능이에요. 쓰려면 use v5.14 선언을 포함해야 해요. (엄밀히 말하면 switch 기능만 필요한데, 그 기능은 5.14 이전엔 없었어요.) foreach 루프나 given 블록 안에서만 동작하며, 스마트매치 $_ ~~ EXPR이 참일 때만 그 문장을 실행해요. 문장이 실행되면 foreach 안에서는 next가, given 안에서는 break가 뒤따라요. 현재 구현에서 foreach 루프는 when 수식어의 동적 범위 어디에나 있을 수 있지만, given 블록의 어휘 범위 안에는 있어야 해요. 이 제한은 향후 릴리스에서 완화될 수 있어요.
복합 문장 (Compound Statements)
Perl에서 범위(scope)를 정의하는 문장 시퀀스를 **블록(block)**이라고 불러요. 때로는 블록을 담고 있는 파일(required 파일이거나 프로그램 전체)이 블록을 경계 짓기도 하고, 때로는 문자열의 범위(evval의 경우)가 블록을 경계 짓기도 해요.
하지만 일반적으로 블록은 중괄호(curly brackets)로 경계 지어져요. 이 문법 구조를 BLOCK이라고 부를게요. 감싸는 중괄호가 해시 참조 생성자 표현식의 문법이기도 하기 때문에(참고: perlref), 열린 중괄호 바로 뒤에 ;을 넣어 Perl이 그 중괄호가 블록의 시작임을 알아차리게 해야 할 때가 가끔 있어요. 반대로 열린 중괄호 바로 앞에 +를 넣어 해시 참조 생성자 표현식으로 해석되도록 강제해야 할 일이 더 자주 생기죠. 이런 구분 메커니즘은 Perl이 잘못 추측할 때만이 아니라 아낌없이 쓰는 게 좋은 스타일로 여겨져요.
흐름을 제어하는 데 쓰는 복합 문장은 다음과 같아요:
if (EXPR) BLOCK
if (EXPR) BLOCK else BLOCK
if (EXPR) BLOCK elsif (EXPR) BLOCK ...
if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK
unless (EXPR) BLOCK
unless (EXPR) BLOCK else BLOCK
unless (EXPR) BLOCK elsif (EXPR) BLOCK ...
unless (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK
given (EXPR) BLOCK
LABEL while (EXPR) BLOCK
LABEL while (EXPR) BLOCK continue BLOCK
LABEL until (EXPR) BLOCK
LABEL until (EXPR) BLOCK continue BLOCK
LABEL for (EXPR; EXPR; EXPR) BLOCK
LABEL for VAR (LIST) BLOCK
LABEL for VAR (LIST) BLOCK continue BLOCK
LABEL foreach (EXPR; EXPR; EXPR) BLOCK
LABEL foreach VAR (LIST) BLOCK
LABEL foreach VAR (LIST) BLOCK continue BLOCK
LABEL BLOCK
LABEL BLOCK continue BLOCK
PHASE BLOCK
Perl 5.36부터 괄호 안에 어휘 변수 목록을 지정해 한 번에 여러 값을 반복할 수 있어요:
LABEL for my (VAR, VAR) (LIST) BLOCK
LABEL for my (VAR, VAR) (LIST) BLOCK continue BLOCK
LABEL foreach my (VAR, VAR) (LIST) BLOCK
LABEL foreach my (VAR, VAR) (LIST) BLOCK continue BLOCK
try 기능이 켜져 있으면 다음도 사용할 수 있어요:
try BLOCK catch (VAR) BLOCK
try BLOCK catch (VAR) BLOCK finally BLOCK
실험적인 given 문장은 자동으로 켜지지 않아요. 켜는 방법과 따라오는 주의사항은 아래의 "Switch Statements"를 참고하세요.
C나 Pascal과 달리, Perl에서는 이 모든 것들이 문장이 아니라 BLOCK으로 정의돼요. 즉 중괄호가 필수이고, 매달린 문장(dangling statement)은 허용되지 않아요. 중괄호 없이 조건문을 쓰고 싶다면 몇 가지 다른 방법이 있어요. 다음은 모두 같은 일을 해요:
if (!open(FOO)) { die "Can't open $FOO: $!" }
die "Can't open $FOO: $!" unless open(FOO);
open(FOO) || die "Can't open $FOO: $!";
open(FOO) ? () : die "Can't open $FOO: $!";
# a bit exotic, that last one
if 문장은 단순해요. BLOCK은 항상 중괄호로 경계 지어지기 때문에, 어떤 if에 어떤 else가 붙는지 애매할 일이 전혀 없어요. if 대신 unless를 쓰면 검사 의미가 뒤집혀요. if처럼 unless도 뒤에 else를 붙일 수 있고, elsif도 하나 이상 붙일 수 있어요. 다만 그 언어 구조를 쓰기 전에 두 번 생각해 보는 게 좋아요. 여러분 코드를 읽는 모든 사람이 무슨 일이 벌어지는지 이해하려면 최소한 두 번은 생각해야 하거든요.
while 문장은 표현식이 참인 한 블록을 실행해요. until 문장은 표현식이 거짓인 한 블록을 실행하죠. LABEL은 선택 사항이고, 있으면 식별자 뒤에 콜론이 붙는 형태예요. LABEL은 루프 제어 문장 next, last, redo를 위해 루프를 식별해 줘요. LABEL을 생략하면 루프 제어 문장은 가장 안쪽의 감싸는 루프를 가리켜요. 여기에는 런타임에 호출 스택을 동적으로 뒤져 LABEL을 찾는 것도 포함될 수 있어요. 그런 절박한 동작은 use warnings 프래그마나 -w 플래그를 쓰면 경고를 일으켜요. 같은 이름의 라벨이 하나 이상 있으면, 그 이름에 대한 참조는 가장 안쪽의 감싸는 루프를 라벨링하는 것을 가리켜요.
while 문장의 조건 표현식이 반복 표현식 타입 그룹 중 하나에 기반한다면 어떤 마법적 처리를 받아요. 영향을 받는 반복 표현식 타입은 readline, <FILEHANDLE> 입력 연산자, readdir, glob, <PATTERN> 글로빙 연산자, each예요. 조건 표현식이 이런 타입 중 하나면, 반복 연산자가 내놓은 값이 암묵적으로 $_에 할당돼요. 조건 표현식이 이런 타입이거나 그중 하나를 스칼라에 명시적으로 할당한 것이라면, 조건은 실제로 그 표현식 값의 **정의됨(definedness)**을 검사하지, 일반적인 진리값을 검사하지 않아요.
continue BLOCK이 있으면 조건을 다시 평가하려는 바로 직전에 항상 실행돼요. 그래서 next 문으로 루프를 계속했을 때조차 루프 변수를 증가시키는 데 쓸 수 있어요.
블록 앞에 BEGIN, END, INIT, CHECK, UNITCHECK 같은 컴파일 단계 키워드가 있으면, 그 순간에 해당 실행 단계에서만 블록이 실행돼요. 자세한 내용은 perlmod를 참고하세요.
확장 모듈은 Perl 파서에 훅을 걸어 새로운 종류의 복합 문장을 정의할 수도 있어요. 이들은 확장이 인식하는 키워드로 시작하고, 키워드 뒤의 문법은 전적으로 확장이 정의해요. 구현하는 사람이라면 perlapi의 PL_keyword_plugin 메커니즘을, 그런 모듈을 사용하는 사람이라면 모듈 문서에서 정의하는 문법을 참고하세요.
루프 제어 (Loop Control)
next 명령은 루프의 다음 반복을 시작해요:
LINE: while (<STDIN>) {
next LINE if /^#/; # discard comments
...
}
last 명령은 해당 루프를 즉시 빠져나가요. continue 블록이 있어도 실행되지 않아요:
LINE: while (<STDIN>) {
last LINE if /^$/; # exit when done with header
...
}
redo 명령은 조건을 다시 평가하지 않고 루프 블록을 다시 시작해요. continue 블록이 있어도 실행되지 않아요. 이 명령은 보통 방금 입력받은 것에 대해 자기 자신에게 거짓말을 하고 싶은 프로그램이 써요.
예를 들어 /etc/termcap 같은 파일을 처리할 때요. 입력 줄이 백슬래시로 끝나며 연속을 나타낼 수 있다면, 앞으로 건너뛰어 다음 레코드를 얻고 싶을 거예요.
while (<>) {
chomp;
if (s/\\$//) {
$_ .= <>;
redo unless eof();
}
# now process $_
}
이건 더 명시적으로 쓴 버전의 축약형이에요:
LINE: while (defined($line = <ARGV>)) {
chomp($line);
if ($line =~ s/\\$//) {
$line .= <ARGV>;
redo LINE unless eof(); # not eof(ARGV)!
}
# now process $line
}
위 코드에 continue 블록이 있었다면, redo가 continue 블록을 건너뛰므로 정규식이 버린 줄에서만 실행될 거라는 점을 기억하세요. continue 블록은 줄 카운터나 m?pat? 일회성 매치를 리셋하는 데 흔히 쓰여요:
# inspired by :1,$g/fred/s//WILMA/
while (<>) {
m?(fred)? && s//WILMA $1 WILMA/;
m?(barney)? && s//BETTY $1 BETTY/;
m?(homer)? && s//MARGE $1 MARGE/;
} continue {
print "$ARGV $.: $_";
close ARGV if eof; # reset $.
reset if eof; # reset ?pat?
}
while이라는 단어를 until로 바꾸면 검사 의미가 뒤집히지만, 조건식은 여전히 첫 반복 전에 검사돼요.
루프 제어 문장은 if나 unless에서는 동작하지 않아요. 그건 루프가 아니니까요. 그래도 중괄호를 두 겹으로 하면 루프가 돼요.
if (/pattern/) {{
last if /fred/;
next if /barney/; # same effect as "last",
# but doesn't document as well
# do something here
}}
그 이유는 블록 하나가 그 자체로 한 번 실행되는 루프처럼 동작하기 때문이에요. 아래의 "Basic BLOCKs"를 참고하세요.
Perl 4에서 쓰이던 while/if BLOCK BLOCK 형태는 더 이상 없어요. if BLOCK이 나오면 if (do BLOCK)으로 바꾸세요.
For 루프 (For Loops)
Perl의 C 스타일 for 루프는 대응하는 while 루프처럼 동작해요. 즉 이 코드:
for ($i = 1; $i < 10; $i++) {
...
}
는 이것과 같아요:
$i = 1;
while ($i < 10) {
...
} continue {
$i++;
}
사소한 차이가 하나 있어요. for의 초기화 부분에서 변수를 my로 선언하면, 그 변수의 어휘 범위는 정확히 for 루프(루프 본문과 제어 섹션)가 돼요. 예를 들면:
my $i = 'samba';
for (my $i = 1; $i <= 4; $i++) {
print "$i\n";
}
print "$i\n";
실행하면:
1
2
3
4
samba
특수한 경우로, for 루프(또는 대응하는 while 루프)의 검사식이 비어 있으면 참으로 취급돼요. 즉 다음 둘 다:
for (;;) {
...
}
while () {
...
}
무한 루프로 취급돼요.
일반적인 배열 인덱스 루프 외에도 for는 흥미로운 응용이 많아요. 인터랙티브 파일 디스크립터에서 파일 끝을 명시적으로 검사하면 프로그램이 멈춘 것처럼 보이는 문제를 피하는 예가 있어요:
$on_a_tty = -t STDIN && -t STDOUT;
sub prompt { print "yes? " if $on_a_tty }
for ( prompt(); <STDIN>; prompt() ) {
# do something
}
for 루프의 조건 표현식은 while 루프의 조건 표현식이 받는 것과 같은 readline 등의 마법적 처리를 받아요.
Foreach 루프 (Foreach Loops)
foreach 루프는 일반적인 리스트 값을 반복하며 스칼라 변수 VAR를 그때그때 리스트의 각 요소로 설정해요. 변수 앞에 my 키워드가 있으면 어휘 범위가 되어 루프 안에서만 보여요. 그렇지 않으면 변수는 루프에 암묵적으로 국한되어 루프를 빠져나오면 이전 값을 되찾죠. 변수가 이전에 my로 선언되었다면 전역 변수 대신 그 변수를 쓰지만, 여전히 루프에 국한되어요. 이 암묵적인 국한화는 C 스타일이 아닌 루프에서만 일어나요.
foreach 키워드는 사실 for 키워드의 동의어라서 둘 중 아무거나 써도 돼요. VAR을 생략하면 각 값이 $_에 설정돼요.
LIST의 어떤 요소가 lvalue라면, 루프 안에서 VAR을 수정해 그 요소를 수정할 수 있어요. 반대로 LIST의 어떤 요소가 lvalue가 아니라면 그 요소를 수정하려는 시도는 실패해요. 다시 말해, foreach 루프 인덱스 변수는 반복하는 리스트의 각 항목에 대한 암묵적 별칭이에요.
LIST가 단일 배열이라면, 루프 본문 안에서 splice나 push로 요소를 추가·제거하면 foreach는 아주 헷갈려요. 그러지 마세요.
my @arr1 = 1 .. 2;
for my $x (@arr1, ()) {
push @arr1, 'a' if $x =~ /[0-9]/;
print $x;
} # Outputs 12
my @arr2 = 1 .. 2;
for my $x (@arr2) {
push @arr2, 'b' if $x =~ /[0-9]/;
print $x;
} # Outputs 12bb
VAR이 tied나 다른 특수 변수면 foreach는 예상대로 동작하지 않을 거예요. 그것도 하지 마세요.
Perl 5.22부터 이 루프에는 VAR 앞에 백슬래시가 붙는 실험적 변형이 있어요. 그 경우 LIST의 항목은 참조여야 해요. 백슬래시된 변수는 LIST의 각 참조된 항목(올바른 타입이어야 함)에 대한 별칭이 되죠. 이 경우 변수는 스칼라일 필요가 없고, 백슬래시 뒤에 my가 올 수도 있어요. 이 형태를 쓰려면 use feature로 refaliasing 기능을 켜야 해요. (feature 참고. perlref의 "Assigning to References"도 참고.)
Perl 5.36부터 한 번에 여러 값을 반복할 수 있어요. 반복 변수는 어휘 스칼라만 쓸 수 있어요 — 리스트 할당과 달리, 원치 않는 값을 나타내는 데 undef를 쓸 수 없어요. 이건 현재 구현의 한계이고 미래에 바뀔 수 있어요.
LIST 크기가 반복 변수 개수의 정확한 배수가 아니면, 마지막 반복에서 "초과" 반복 변수들은 undef에 대한 별칭이 돼요. 마치 LIST에 길이가 정확한 배수가 될 때까지 , undef를 필요한 만큼 덧붙인 것처럼요. 이는 LIST가 리터럴이든 배열이든 마찬가지예요 — 즉 배열은 크기가 반복 크기의 배수가 아니어도 확장되지 않아요. 배열을 하나씩 반복하는 것과 일관되죠. 이 패딩 요소들은 lvalue가 아니라서 수정하려 하면 실패해요. 리터럴 undef들이 있는 리스트를 반복할 때의 동작과 일관되요. 원하는 동작이 아니라면, 루프를 시작하기 전에 배열을 정확한 배수로 명시적으로 확장하거나 예외를 명시적으로 던지세요.
예시:
for (@ary) { s/foo/bar/ }
for my $elem (@elements) {
$elem *= 2;
}
for $count (reverse(1..10), "BOOM") {
print $count, "\n";
sleep(1);
}
for (1..15) { print "Merry Christmas\n"; }
foreach $item (split(/:[\\\n:]*/, $ENV{TERMCAP})) {
print "Item: $item\n";
}
use feature "refaliasing";
no warnings "experimental::refaliasing";
foreach \my %hash (@array_of_hash_references) {
# do something with each %hash
}
foreach my ($foo, $bar, $baz) (@list) {
# do something three-at-a-time
}
foreach my ($key, $value) (%hash) {
# iterate over the hash
# The hash is immediately copied to a flat list before the loop
# starts. The list contains copies of keys but aliases of values.
# This is the same behaviour as for $var (%hash) {...}
}
C 프로그래머라면 특정 알고리즘을 이렇게 코딩할 거예요:
for (my $i = 0; $i < @ary1; $i++) {
for (my $j = 0; $j < @ary2; $j++) {
if ($ary1[$i] > $ary2[$j]) {
last; # can't go to outer :-(
}
$ary1[$i] += $ary2[$j];
}
# this is where that last takes me
}
반면 관용구에 더 익숙한 Perl 프로그래머는 이렇게 할 거예요:
OUTER: for my $wid (@ary1) {
INNER: for my $jet (@ary2) {
next OUTER if $wid > $jet;
$wid += $jet;
}
}
얼마나 쉬운지 보이죠? 더 깔끔하고, 안전하고, 빠르죠. 더 깔끔한 이유는 소음이 적기 때문이고, 안전한 이유는 나중에 안쪽과 바깥쪽 루프 사이에 코드가 추가돼도 새 코드가 실수로 실행되지 않기 때문이에요. next는 안쪽 루프를 끝내는 것만이 아니라 다른 루프를 명시적으로 반복해요. 그리고 foreach 문장이 동등한 C 스타일 for 루프보다 빠르게 실행되기 때문에 더 빠르고요.
눈치 빠른 Perl 해커들은 for 루프에 반환값이 있고, 그 값을 do 블록으로 루프를 감싸 잡을 수 있다는 걸 알아챘을 거예요. 이 발견에 대한 보상은 이 경고입니다: for 루프의 반환값은 명시되지 않았고 예고 없이 바뀔 수 있어요. 그걸 믿지 마세요.
Try-Catch 예외 처리 (Try Catch Exception Handling)
try/catch 문법은 예외 처리와 관련된 제어 흐름을 제공해요. try 키워드는 만났을 때 실행될 블록을 도입하고, catch 블록은 첫 블록이 던질 수 있는 예외를 처리할 코드를 제공하죠.
이 문법은 먼저 use feature 'try'로 켜야 해요.
use feature 'try';
try {
my $x = call_a_function();
$x < 100 or die "Too big";
send_output($x);
}
catch ($e) {
warn "Unable to output a value; $e";
}
print "Finished\n";
여기에서 첫 블록이 조건부 die를 호출하거나, 그 안에서 호출하는 함수 중 하나가 잡히지 않은 예외를 던지면 catch 블록 본문(즉 warn 문장)이 실행돼요. 그 경우 catch 블록은 $e 어휘 변수를 검사해 예외가 무엇인지 볼 수 있어요. 예외가 던져지지 않으면 catch 블록은 실행되지 않아요. 어느 쪽이든 실행은 다음 문장 — 이 예제에선 print — 에서 계속돼요.
catch 키워드 바로 뒤에는 괄호 안 변수 선언이 와야 해요. 이는 뒤따르는 블록 본문에서 보이는 새 변수를 도입해요. 블록 안에서 이 변수는 try 블록 코드가 던진 예외 값을 담아요. 이 변수를 선언할 때 my 키워드를 쓸 필요는 없어요. 암묵적이거든요(서브루틴 시그니처에서처럼).
try와 catch 블록 모두 return, goto, next/last/redo 같은 제어 흐름 표현식을 포함할 수 있어요. 모든 경우 경고 없이 예상대로 동작해요. 특히 try 블록 안의 return 표현식은 그걸 포함하는 전체 함수를 반환하게 해요 — 이는 eval 블록 안에서 그 블록만 반환하게 하는 동작과 대비되죠.
다른 제어 흐름 문법처럼, try와 catch는 함수나 do 블록의 마지막 문장으로 놓으면 마지막으로 평가된 값을 내놓아요. 이 문법으로 값을 만들 수 있게 하는 거죠. 이 경우 return 표현식을 쓰지 않도록 주의하세요. 그걸 쓰면 포함하는 함수가 반환되어 버려요.
my $value = do {
try {
get_thing(@args);
}
catch ($e) {
warn "Unable to get thing - $e";
$DEFAULT_THING;
}
};
다른 제어 흐름 문법처럼 try 블록은 caller()에는 보이지 않아요(예를 들어 while이나 foreach 루프가 그렇듯). caller 결과의 연속된 단계는 서브루틴 호출과 eval 블록을 볼 수 있어요. 왜냐하면 그것들이 return이 동작하는 방식에 영향을 주기 때문이에요. try 블록은 return을 가로채지 않으므로 caller의 관심사가 아니에요.
try와 catch 블록 뒤에는 선택적으로 finally 키워드가 도입하는 세 번째 블록이 올 수 있어요. 이 블록은 구조의 나머지가 끝난 뒤 실행돼요.
try {
call_a_function();
}
catch ($e) {
warn "Unable to call; $e";
}
finally {
print "Finished\n";
}
finally 블록은 defer 블록을 쓰는 것과 동등하며 같은 상황에서 호출돼요: try 블록이 성공적으로 끝나거나 예외를 던질 때뿐 아니라, (일반 실행 순서에서 벗어나) try나 catch 블록의 코드가 return, 루프 제어, goto로 제어를 옮기기 직전에도요.
try와 catch 블록과 달리 finally 블록은 return하거나 goto하거나 루프 제어를 쓸 수 없어요. 마지막 표현식 값은 무시되고, 함수의 마지막에 놓여 있어도 포함하는 함수의 반환값에 영향을 주지 않아요.
이 finally 블록 문법은 현재 실험적이며 experimental::try 범주에서 경고를 내보내요.
기본 BLOCK (Basic BLOCKs)
BLOCK 하나는(라벨 여부와 무관하게) 의미상 한 번 실행되는 루프와 동등해요. 그래서 그 안에서 루프 제어 문장을 써서 블록을 떠나거나 다시 시작할 수 있어요. (eval{}, sub{}, 그리고 흔한 믿음과 달리 do{} 블록에서 이건 사실이 아니라는 걸 기억하세요. 그것들은 루프로 세지 않아요.) continue 블록은 선택 사항이에요.
BLOCK 구조는 case 구조를 흉내 내는 데 쓸 수 있어요.
SWITCH: {
if (/^abc/) { $abc = 1; last SWITCH; }
if (/^def/) { $def = 1; last SWITCH; }
if (/^xyz/) { $xyz = 1; last SWITCH; }
$nothing = 1;
}
또한 foreach 루프를 토픽장치(topicalizer)와 switch로 쓰는 것도 볼 수 있어요:
SWITCH:
for ($var) {
if (/^abc/) { $abc = 1; last SWITCH; }
if (/^def/) { $def = 1; last SWITCH; }
if (/^xyz/) { $xyz = 1; last SWITCH; }
$nothing = 1;
}
이런 구조는 아주 흔히 쓰여요. 옛 Perl 버전에는 공식 switch 문이 없었고, 바로 아래에서 설명할 새 버전은 여전히 실험적이라 때로 헷갈리기 때문이에요.
defer 블록 (defer blocks)
defer 수식어가 붙은 블록은 범위를 빠져나갈 때 나중에 실행되는 코드 섹션을 제공해요.
defer 블록은 일반 블록이나 다른 문장이 허용되는 어디에나 놓을 수 있어요. 실행 흐름이 이 문장에 도달하면 블록 본문은 나중을 위해 저장되지만, 즉시 호출되지는 않아요. 제어 흐름이 어떤 이유로든 포함하는 블록을 떠나면, 이 저장된 블록이 지나가면서 실행돼요. 실행을 나중으로 미루는 수단을 제공하죠. 이는 다른 언어들이 try / finally 키워드로 제공하는 문법과 비슷하게 동작해요.
이 문법은 defer 이름 있는 기능으로 켜면 Perl 5.36부터 사용할 수 있고, 현재 실험적이에요. 실험적 경고가 켜져 있으면 사용할 때 경고를 내보내요.
use feature 'defer';
{
say "This happens first";
defer { say "This happens last"; }
say "And this happens inbetween";
}
단일 범위에 defer 블록이 여러 개 있으면 LIFO 순서로 실행돼요. 마지막에 도달한 것이 처음 실행되죠.
defer 블록이 저장한 코드는 제어가 그 포함 블록을 떠날 때 호출돼요. 일반적인 통과(fallthrough), 명시적 return, die가 던지거나 그것이 호출한 함수가 전파한 예외, goto, 혹은 루프 제어 문장 next·last·redo 중 무엇 때문이든요.
제어 흐름이 defer 문장 자체에 도달하지 못하면 그 본문은 나중 실행을 위해 저장되지 않아요. (이것은 END 페이저(phaser) 블록과 정반대인데, END 블록은 실행이 주어진 줄에 도달했는지와 무관하게 항상 컴파일러가 큐에 넣으니까요.)
use feature 'defer';
{
defer { say "This will run"; }
return;
defer { say "This will not"; }
}
defer 블록 안 코드가 던진 예외는 일반 코드가 던진 다른 예외와 같은 방식으로 호출자에게 전파돼요.
defer 블록이 던져진 예외 때문에 실행되는데 또 다른 예외를 던지면 무슨 일이 일어날지는 명시되지 않아요. 호출자가 반드시 예외를 받는다는 것만 확실해요.
예외 던지기를 제외하면, defer 블록은 주변 코드의 제어 흐름을 바꾸는 다른 일을 할 수 없어요. 특히 포함하는 함수를 return하게 만들 수 없고, 라벨로 goto할 수 없으며, next·last·redo로 포함하는 루프를 제어할 수 없어요. 다만 이런 구조들은 defer 블록 본문 안에서는 전적으로 허용돼요.
use feature 'defer';
{
defer {
foreach ( 1 .. 5 ) {
last if $_ == 3; # this is permitted
}
}
}
{
foreach ( 6 .. 10 ) {
defer {
last if $_ == 8; # this is not
}
}
}
Switch 문장 (Switch Statements)
switch 기능은 새 코드에는 권장되지 않고, 하위 호환을 위해 남아 있어요.
Perl 5.10.1(정확히는 5.10.0인데 제대로 동작하지 않았지만)부터 다음을 쓸 수 있어요:
use feature "switch";
switch 기능을 켜는 거죠. 이건 Raku 제안의 옛 버전을 대충 기반으로 했지만, 이제 Raku 구조와는 닮지 않았어요. 코드가 5.10에서 5.34 사이 버전의 Perl에서 돌기를 선언하면 switch 기능도 얻을 수 있어요. 예를 들어:
use v5.14;
"switch" 기능 아래에서 Perl은 given, when, default, continue, break 키워드를 얻어요. Perl 5.16부터는 use feature 문 없이 switch 키워드 앞에 CORE::를 붙여 그 기능에 접근할 수 있어요. given과 when은 다른 언어의 switch와 case에 해당해요 — continue는 아니지만요 — 그래서 이전 섹션의 코드를 이렇게 다시 쓸 수 있어요:
use v5.10.1;
for ($var) {
when (/^abc/) { $abc = 1 }
when (/^def/) { $def = 1 }
when (/^xyz/) { $xyz = 1 }
default { $nothing = 1 }
}
foreach는 토픽장치를 설정하는 또 다른 방법이에요. given을 쓰고 싶다면 이렇게 쓸 수 있어요:
use v5.10.1;
given ($var) {
when (/^abc/) { $abc = 1 }
when (/^def/) { $def = 1 }
when (/^xyz/) { $xyz = 1 }
default { $nothing = 1 }
}
5.14부터 이렇게도 쓸 수 있어요:
use v5.14;
for ($var) {
$abc = 1 when /^abc/;
$def = 1 when /^def/;
$xyz = 1 when /^xyz/;
default { $nothing = 1 }
}
아니면 안전하게 하기를 신경 쓰지 않는다면 이렇게:
use v5.14;
given ($var) {
$abc = 1 when /^abc/;
$def = 1 when /^def/;
$xyz = 1 when /^xyz/;
default { $nothing = 1 }
}
given과 when의 인자는 스칼라 문맥이고, given은 $_ 변수에 그 토픽 값을 할당해요.
when의 EXPR 인자가 정확히 무엇을 하는지는 정밀하게 설명하기 어려워요. 일반적으로 여러분이 원하는 일을 추측하려 한다고 볼 수 있어요. 때로는 $_ ~~ EXPR으로 해석되고, 때로는 그렇지 않아요. 또 given 블록에 어휘적으로 둘러싸였을 때와 foreach 루프에 동적으로 둘러싸였을 때 다르게 동작해요. 그 규칙은 여기서 설명하기엔 너무 복잡해요. 나중에 나오는 "Experimental Details on given and when"을 참고하세요.
Perl 5.10과 5.16 사이에서 given이 구현된 방식의 불행한 버그 때문에, 그 구현들에서는 given이 다스리는 $_ 버전이 원본의 어휘 범위 복사본일 뿐이에요. foreach나 원본·현재 Raku 언어 명세에서처럼 동적 범위 별칭이 아니었죠. 이 버그는 Perl 5.18에서 고쳐졌어요(그리고 어휘화된 $_ 자체는 Perl 5.24에서 제거됐어요).
코드가 여전히 옛 버전에서 돌아야 한다면, 토픽장치로 foreach를 고수하세요. 덜 불행해질 거예요.
Goto
심약한 사람을 위한 건 아니지만, Perl은 goto 문장을 지원해요. 세 가지 형태가 있어요: goto-LABEL, goto-EXPR, goto-&NAME. 루프의 LABEL은 실제로 goto의 유효한 대상이 아니라, 그저 루프의 이름일 뿐이에요.
goto-LABEL 형태는 LABEL로 라벨링된 문장을 찾아 거기서 실행을 재개해요. 서브루틴이나 foreach 루프처럼 초기화가 필요한 구조로 들어가는 데는 쓸 수 없어요. 또한 최적화되어 사라진 구조로 들어가는 데도 쓸 수 없어요. 동적 범위 안의 거의 다른 모든 곳 — 서브루틴 밖으로 나가는 걸 포함해서 — 으로 가는 데 쓸 수 있지만, 보통은 last나 die 같은 다른 구조를 쓰는 게 나아요. Perl 저자는 (Perl에서) 이 형태의 goto를 쓸 필요를 느낀 적이 없어요 — C는 다른 문제지만요.
goto-EXPR 형태는 라벨 이름을 기대하고, 그 범위는 동적으로 해석돼요. 이를 통해 FORTRAN처럼 계산된 goto가 가능하지만, 유지보수성을 최적화한다면 꼭 추천할 만한 건 아니에요:
goto(("FOO", "BAR", "GLARCH")[$i]);
goto-&NAME 형태는 매우 마법적이에요. 현재 실행 중인 서브루틴을 그 이름의 서브루틴 호출로 대체하죠. 이는 다른 서브루틴을 불러온 뒤, 원래부터 그 서브루틴이 호출된 것처럼 가장하고 싶은 AUTOLOAD() 서브루틴이 사용해요. (현재 서브루틴에서 @_에 대한 수정은 다른 서브루틴으로 전파된다는 점만 빼고요.) goto 후에는 caller()조차 이 루틴이 먼저 호출되었다는 것을 알 수 없어요.
이런 경우 거의 대부분, goto에 의존하기보다는 구조적 제어 흐름 메커니즘인 next, last, redo를 쓰는 게 훨씬, 훨씬 더 좋은 생각이에요. 예외 처리를 위한 eval{}과 die()의 catch-and-throw 쌍도 신중한 접근이 될 수 있어요.
생략 부호 문장 (The Ellipsis Statement)
Perl 5.12부터 Perl은 생략 부호, "..."를 아직 구현하지 않은 코드의 자리 표시자(placeholder)로 받아들여요. Perl 5.12 이상이 생략 부호 문장을 만나면 오류 없이 파싱하지만, 실제로 실행하려 하면 Unimplemented라는 텍스트로 예외를 던져요:
use v5.12;
sub unimplemented { ... }
eval { unimplemented() };
if ($@ =~ /^Unimplemented at /) {
say "I found an ellipsis!";
}
생략 부호 문장은 완전한 문장을 대신할 때만 쓸 수 있어요. 문법적으로 "...;"는 완전한 문장이지만, 다른 세미콜론으로 끝나는 문장처럼 "..."가 닫는 중괄호 바로 앞에 오면 세미콜론을 생략할 수 있어요. 이 예시들은 생략 부호가 어떻게 동작하는지 보여줘요:
use v5.12;
{ ... }
sub foo { ... }
...;
eval { ... };
sub somemeth {
my $self = shift;
...;
}
$x = do {
my $n;
...;
say "Hurrah!";
$n;
};
생략 부호 문장은 더 큰 문장의 일부인 표현식을 대신할 수는 없어요. 생략 부호를 쓰려 한 다음 예시들은 문법 오류예요:
use v5.12;
print ...;
open(my $fh, ">", "/dev/passwd") or ...;
if ($condition && ... ) { say "Howdy" };
... if $x > $y;
say "Cromulent" if ...;
$flub = 5 + ...;
Perl이 표현식과 문장을 즉시 구분하지 못하는 경우가 몇 가지 있어요. 예를 들어 블록 문법과 익명 해시 참조 생성자는 중괄호 안에 Perl에 힌트를 줄 뭔가가 없으면 똑같이 보여요. Perl이 { ... }가 블록이라고 추측하지 않으면 생략 부호는 문법 오류예요. 블록 안에서 생략 부호 앞에 ;을 써서 { ... }가 해시 참조 생성자가 아니라 블록임을 나타낼 수 있어요.
참고: 어떤 이들은 이 구두점을 구어적으로 "yada-yada"나 "triple-dot"이라고 부르지만, 실제 정식 이름은 생략 부호(ellipsis)예요.
POD: 내장 문서 (PODs: Embedded Documentation)
Perl에는 문서를 소스 코드와 섞어 넣는 메커니즘이 있어요. 컴파일러가 새 문장의 시작을 기다리다가 등호와 단어로 시작하는 줄을 만나면, 예를 들어:
=head1 Here There Be Pods!
그 텍스트와 =cut으로 시작하는 줄까지 포함한 나머지 모든 텍스트는 무시돼요. 그 사이 텍스트의 형식은 perlpod에 설명돼 있어요.
이렇게 하면 소스 코드와 문서 텍스트를 자유롭게 섞을 수 있어요:
=item snazzle($)
The snazzle() function will behave in the most spectacular
form that you can possibly imagine, not even excepting
cybernetic pyrotechnics.
=cut back to the compiler, nuff of this pod stuff!
sub snazzle($) {
my $thingie = shift;
.........
}
pod 변환기는 pod 지시어로 시작하는 문단만 봐야 한다는 점을 유의하세요(파싱이 쉬워지니까요). 컴파일러는 문단 중간에서도 pod 이스케이프를 찾을 줄 알아요. 즉 다음의 비밀 내용은 컴파일러와 변환기 둘 다에게 무시돼요.
$x=3;
=secret stuff
warn "Neither POD nor CODE!?"
=cut back
print "got $x\n";
warn()이 영원히 pod 처리되기를 기대하면 안 돼요. 모든 pod 변환기가 이 점에서 잘 행동하는 건 아니고, 컴파일러도 더 깐깐해질지 모르거든요.
pod 지시어를 써서 코드 섹션을 빠르게 주석 처리할 수도 있어요.
일반 주석? (Plain Old Comments (Not!))
Perl은 C 전처리기처럼 줄 지시어(line directive)를 처리할 수 있어요. 이를 이용해 오류나 경고 메시지에서 Perl이 보는 파일 이름과 줄 번호를 제어할 수 있어요(특히 eval()로 처리되는 문자열에서요). 이 메커니즘의 문법은 대부분의 C 전처리기와 거의 같아요. 정규식으로는:
# example: '# line 42 "new_filename.plx"'
/^# \s*
line \s+ (\d+) \s*
(?:\s("?)([^"]+)\g2)? \s*
$/x
$1은 다음 줄의 줄 번호이고, $3은 선택적 파일 이름(따옴표 유무와 무관하게 지정)이에요. 현대 C 전처리기와 달리 # 앞에 공백이 올 수 없다는 점을 유의하세요.
줄 지시어에는 꽤 분명한 함정이 있어요: 디버거와 프로파일러는 특정 파일의 특정 줄 번호에 나타난 마지막 소스 줄만 보여줘요. 나중에 디버깅할 코드에서 줄 번호 충돌을 일으키지 않도록 조심하세요.
쉘에 입력해 볼 수 있는 예시 몇 가지예요:
% perl
# line 200 "bzzzt"
# the '#' on the previous line must be the first char on line
die 'foo';
__END__
foo at bzzzt line 201.
% perl
# line 200 "bzzzt"
eval qq[\n#line 2001 ""\ndie 'foo']; print $@;
__END__
foo at - line 2001.
% perl
eval qq[\n#line 200 "foo bar"\ndie 'foo']; print $@;
__END__
foo at foo bar line 200.
% perl
# line 345 "goop"
eval "\n#line " . __LINE__ . ' "' . __FILE__ ."\"\ndie 'foo'";
print $@;
__END__
foo at goop line 345.
given과 when의 실험적 세부사항 (Experimental Details on given and when)
앞서 언급했듯이 "switch" 기능은 고도로 실험적인 것으로 여겨져요(또한 perl 5.42.0에서 제거될 예정이에요). 예고 없이 바뀔 수 있죠. 특히 when은 미래에 덜 어려워지도록 바뀔 것으로 예상되는 까다로운 동작들이 있어요. 현재의 (잘못된) 구현에 의존하지 마세요. Perl 5.18 이전의 given도 까다로운 동작이 있었는데, 코드가 옛 Perl 버전에서 돌아야 한다면 여전히 주의해야 해요.
given의 더 긴 예시는 다음과 같아요:
use feature ":5.10";
given ($foo) {
when (undef) {
say '$foo is undefined';
}
when ("foo") {
say '$foo is the string "foo"';
}
when ([1,3,5,7,9]) {
say '$foo is an odd digit';
continue; # Fall through
}
when ($_ < 100) {
say '$foo is numerically less than 100';
}
when (\&complicated_check) {
say 'a complicated check for $foo is true';
}
default {
die q(I don't know what to do with $foo);
}
}
Perl 5.18 이전에는 given(EXPR)이 EXPR 값을 foreach처럼 동적 범위 별칭이 아니라, $_의 어휘 범위 복사본(!)에 할당했어요. 그러면 다음과 비슷했죠:
do { my $_ = EXPR; ... }
성공한 when이나 명시적 break로 블록이 자동으로 빠져나온다는 점만 빼고요. 복사본일 뿐이고 어휘 범위이며 동적 범위가 아니었기 때문에, foreach 루프에서 익숙한 일들을 할 수 없었어요. 특히 $_에 접근하려 할 수 있는 임의의 함수 호출에는 동작하지 않았죠. 그런 용도에는 foreach를 고수하는 게 좋아요.
힘의 대부분은 때로 적용되는 암묵적 스마트매치에서 나와요. 대부분의 경우 when(EXPR)은 $_의 암묵적 스마트매치, 즉 $_ ~~ EXPR로 취급돼요. (스마트매치에 대한 자세한 내용은 perlop의 "Smartmatch Operator" 참고.) 하지만 EXPR이 아래에 나열된 10가지 예외 경우(또는 그것과 같은 것) 중 하나면 불리언으로 직접 사용돼요.
- 사용자 정의 서브루틴 호출이나 메서드 호출.
/REGEX/,$foo =~ /REGEX/,$foo =~ EXPR형태의 정규식 매치. 또한!/REGEX/,$foo !~ /REGEX/,$foo !~ EXPR형태의 부정 정규식 매치.EXPR ~~ EXPR처럼 명시적~~연산자를 쓰는 스마트매치. (참고:$c ~~ $_를 자주 써야 할 거예요. 기본 경우가$_ ~~ $c라서 여러분이 원하는 것과 반대인 경우가 많거든요.)$_ < 10이나$x eq "abc"같은 불리언 비교 연산자. 여기 해당하는 관계 연산자는 6개의 숫자 비교(<,>,<=,>=,==,!=)와 6개의 문자열 비교(lt,gt,le,ge,eq,ne)예요.- 최소한 내장 함수
defined(...),exists(...),eof(...)세 가지. 나중에 더 생각나면 더 추가할지도 몰라요. - 부정 표현식 —
!(EXPR)이든not(EXPR)이든 — 또는 논리 배타적 OR(EXPR1) xor (EXPR2). 비트 버전(~와^)은 포함되지 않아요. - 파일테스트 연산자. 정확히 4가지 예외가 있어요:
-s,-M,-A,-C는 숫자 값을 반환하지 불리언을 반환하지 않으니까요.-z파일테스트 연산자는 예외 목록에 포함되지 않아요. ..와...플립플롭 연산자....플립플롭 연산자는 방금 설명한...생략 부호 문장과 완전히 다르다는 점을 유의하세요.
위 8가지 경우에서 EXPR의 값은 불리언으로 직접 사용되므로 스마트매치가 일어나지 않아요. when을 "스마트-스마트매치"로 생각할 수 있어요.
게다가 Perl은 위 검사를 피연산자에 적용해 각각에 대해 스마트매치를 쓸지 결정하기 위해 논리 연산자의 피연산자도 검사해요:
- EXPR이
EXPR1 && EXPR2또는EXPR1 and EXPR2라면, 그 검사가 EXPR1과 EXPR2 둘 다에 재귀적으로 적용돼요. 양쪽 피연산자가 재귀적으로 검사를 통과할 때만 그 표현식이 불리언으로 취급돼요. 그렇지 않으면 스마트매치가 사용돼요. - EXPR이
EXPR1 || EXPR2,EXPR1 // EXPR2,EXPR1 or EXPR2라면, 검사가 EXPR1에만 재귀적으로 적용되고(그것 자체가 더 높은 우선순위 AND 연산자일 수 있어 이전 규칙의 대상이 될 수 있어요), EXPR2에는 적용되지 않아요. EXPR1이 스마트매치를 쓰게 되면, EXPR2가 무엇을 담고 있든 EXPR2도 스마트매치를 써요. 하지만 EXPR2가 스마트매치를 못 쓰게 되면 두 번째 인자도 못 써요. 이는 방금 설명한&&경우와 꽤 다르니 조심하세요.
이 규칙들은 복잡하지만, 목표는 여러분이 원하는 일을 하게 하는 거예요(왜 그러는지 잘 이해하지 못해도요). 예를 들어:
when (/^\d+$/ && $_ < 75) { ... }
규칙이 정규식 매치와 $_에 대한 명시적 검사 둘 다 불리언으로 취급하라고 하므로 불리언 매치로 취급돼요.
또한:
when ([qw(foo bar)] && /baz/) { ... }
피연산자 중 하나만 불리언이므로 스마트매치를 사용해요. 다른 하나가 스마트매치를 쓰고, 그것이 이기죠.
추가로:
when ([qw(foo bar)] || /^baz/) { ... }
스마트 매치를 사용하고(첫 피연산자만 고려됨), 반면:
when (/^baz/ || [qw(foo bar)]) { ... }
정규식만 검사해서 두 피연산자 모두 불리언으로 취급돼요. 이것은 조심하세요. arrayref는 항상 참 값이라서 사실상 중복되거든요. 좋은 생각이 아니에요.
항진명제(tautology) 논리 연산자는 여전히 최적화되어 사라질 거예요. 이렇게 쓰고 싶어지지 않게 하세요:
when ("foo" or "bar") { ... }
이건 "foo"로 최적화되어 "bar"는 결코 고려되지 않아요(규칙이 "foo"에 스마트매치를 쓰라고 해도요). 이 같은 선택(alternation)에는 arrayref가 동작해요. 그것이 스마트매치를 유발하니까요:
when ([qw(foo bar)] { ... }
이건 C 스타일 switch 문의 fallthrough 기능(Perl의 fallthrough 기능과 혼동하지 마세요 — 아래 참고)과 어느 정도 동등해요. 같은 블록을 여러 case 문에 쓰는 거죠.
또 하나 유용한 지름길은, given의 인자로 리터럴 배열이나 해시를 쓰면 참조로 바뀐다는 거예요. 예를 들어 given(@foo)는 given(\@foo)와 같아요.
default는 정확히 when(1 == 1)처럼 동작해요. 즉 항상 매치된다는 뜻이죠.
빠져나가기 (Breaking out)
break 키워드로 감싸는 given 블록에서 빠져나갈 수 있어요. 모든 when 블록은 암묵적으로 break로 끝나요.
통과 (Fall-through)
continue 키워드로 한 case에서 다음 when이나 default로 통과할 수 있어요:
given($foo) {
when (/x/) { say '$foo contains an x'; continue }
when (/y/) { say '$foo contains a y' }
default { say '$foo does not contain a y' }
}
반환값 (Return value)
given 문장이 유효한 표현식이기도 하면(예: 블록의 마지막 문장일 때), 다음 값으로 평가돼요:
- 명시적
break를 만나는 즉시 빈 리스트. - 성공한
when/default절의 마지막으로 평가된 표현식 값 (그런 게 있다면). - 어떤 조건도 참이 아니면
given블록의 마지막으로 평가된 표현식 값.
마지막 두 경우 모두, 마지막 표현식은 given 블록에 적용된 문맥에서 평가돼요.
if와 unless와 달리, 실패한 when 문장은 항상 빈 리스트로 평가된다는 점을 유의하세요.
my $price = do {
given ($item) {
when (["pear", "apple"]) { 1 }
break when "vote"; # My vote cannot be bought
1e10 when /Mona Lisa/;
"unknown";
}
};
현재 given 블록은 항상 제대로 된 표현식으로 쓸 수 있는 건 아니에요. 이건 미래의 Perl 버전에서 다뤄질 수 있어요.
루프 안에서 switch (Switching in a loop)
given() 대신 foreach() 루프를 쓸 수 있어요. 예를 들어 특정 문자열이 배열에 몇 번 나타나는지 세는 한 가지 방법이에요:
use v5.10.1;
my $count = 0;
for (@array) {
when ("foo") { ++$count }
}
print "\@array contains $count copies of 'foo'\n";
더 최신 버전에서는:
use v5.14;
my $count = 0;
for (@array) {
++$count when "foo";
}
print "\@array contains $count copies of 'foo'\n";
모든 when 블록 끝에는 암묵적 next가 있어요. 첫 매치만 관심 있다면 명시적 last로 override할 수 있어요.
이건 for $item (@array)처럼 루프 변수를 명시적으로 지정하면 동작하지 않아요. 기본 변수 $_를 써야 해요.
Raku와의 차이 (Differences from Raku)
Perl 5 스마트매치와 given/when 구조는 Raku의 대응물과 호환되지 않아요. 가장 눈에 띄면서 가장 덜 중요한 차이는, Perl 5에서는 given()과 when()의 인자에 괄호가 필수라는 점이에요(마지막 것을 문장 수식어로 쓸 때는 제외). Raku에서 괄호는 if(), while(), when() 같은 제어 구조에서 항상 선택 사항이에요. Perl 5에서는 그걸 선택 사항으로 만들 수 없어요. 많은 혼란을 일으킬 수 있거든요. Perl 5가 다음을:
given $foo {
...
}
given의 인자가 해시 %foo의 요소인 것처럼, 중괄호를 해시 요소 문법으로 해석해 버리니까요.
하지만 다른 차이도 아주 많아요. 예를 들어 이것은 Perl 5에서 동작해요:
use v5.12;
my @primary = ("red", "blue", "green");
if (@primary ~~ "red") {
say "primary smartmatches red";
}
if ("red" ~~ @primary) {
say "red smartmatches primary";
}
say "that's all, folks!";
하지만 Raku에서는 전혀 동작하지 않아요. 대신 (병렬화 가능한) any 연산자를 써야 해요:
if any(@primary) eq "red" {
say "primary smartmatches red";
}
if "red" eq any(@primary) {
say "red smartmatches primary";
}
perlop의 "Smartmatch Operator"에 있는 스마트매치 표는 Raku 명세가 제안한 것과 동일하지 않아요. 주로 Raku와 Perl 5의 데이터 모델 차이 때문이고, Perl 5가 일찍 도입하느라 급했던 이후 Raku 명세가 바뀌었기 때문이기도 해요.
Raku에서 when()은 항상 그 인자로 암묵적 스마트매치를 하지만, Perl 5에서는 위에 대략 설명한 대로 다양한 느슨하게 정의된 상황에서 이 암묵적 스마트매치를 억제하는 게 편리해요(다소 혼란스러울 수 있지만요). (그 차이는 대체로 Perl 5에 내부적으로조차 불리언 타입이 없기 때문이에요.)
더 알아보기
perldata— 데이터 구조perlop— 연산자와 우선순위perlsub— 서브루틴perlfunc— 내장 함수perltrap— 문법 차이 함정