[첫화면으로]Perl/컨텍스트

마지막으로 [b]

Context

1. 이것 저것

greplength
maplength
@foolength (efficiently)
keys/valueslength (efficiently)
splitlength, but @_ side-effect
 
(10, 20, 30)last element
@foo[3..5]last element
(10, 20, 30)[2, 1]last element
splicelast element
 
callerpackage name (first element of list return)
eachkey (first element)
getpwuidusername (first element)
getpwnamuser ID (third element of list return)
glob/<*>"next" item (repeat until undef)
gmtime/localtimeprintable string (instead of list of components)
readline/<HANDLE>"next" item (repeat until undef)
readpipe/``/qxone item instead of list of lines
readdir"next" item (repeat until undef)
reversestring reverse instead of list reverse
select(4-arg)$nfound (first element of list)
sortundef
statsuccess value
times$user (first element)
unpackfirst element

2. "It's all about context" 번역

최근에 슬래시닷에 올라온 기사에서는 어느 고등학생이 펄 프로그램을 만들다 저지른 작은 실수 때문에 엄청난 곤란을 겪었다는 놀라운 이야기를 다루었다. 그는 동적으로 생성되는 웹페이지에서 다음과 같은 코드를 사용하였다.

my ($f) = `fortune`;

그는 다음과 같이 했어야 했다.

my $f = `fortune`;

두 코드 다 fortune 프로그램을 실행하고 그 프로그램이 무작위로 뽑아준 텍스트를 캡처한다. 이번 경우에는 그 학교의 관리자가 학생의 웹페이지를 방문했을 때 fortune 프로그램은 윌리엄 깁슨의 소설의 한 구절을 보여주었다.

 I put the shotgun in an Adidas bag and padded it out with four pairs of tennis
 socks, not my style at all, but that was what I was aiming for:  If they think
 you're crude, go technical; if they think you're technical, go crude.  I'm a
 very technical boy.  So I decided to get as crude as possible.  These days,
 though, you have to be pretty technical before you can even aspire to 
 crudeness.
 - Johnny Mnemonic, by William Gibson

여기서, 위의 두 코드에서 각각 $f에 무엇이 담길지 모르겠다면 이 글을 계속 읽어보라. 어떻게 자신도 모르던 실수로 인해 경찰의 기록에 남게 되는지 보게 될 것이다.

이것은 컨텍스트의 문제이다(여러 가지 의미로). 펄의 연산자는 "컨텍스트에 의존한다". 연산자는 자신이 스칼라를 원하는 자리에서 사용되는지 리스트를 원하는 자리에서 사용되는지 인식하고 그에 따라 적절한 결과를 반환한다. 이번 경우에 백틱 연산자는 스칼라 컨텍스트와 리스트 컨텍스트에서 서로 다른 결과를 반환한다.

이를 이해하기 위해 먼저 컨텍스트를 어떻게 인식하는지 살펴보자. 기본적으로, 스칼라 변수에 대입하는 대입문의 우변은 스칼라 값이어야 한다.

$a = ...

우변에 뭐가 있든 그것은 스칼라 값이 되어야 한다. 스칼라 값만이 스칼라 변수에 들어맞기 때문이다.

마찬가지로, 배열에 대입하는 대입문의 우변은 어떠한 리스트 값이든 될 수 있다.

@b = ...

두 자리에 뭐든 넣어본 후 결과가 어떻게 달라지는지 보자. 여러분이 가장 친숙해할 행 입력 연산자, <파일핸들>의 경우를 보면,

$a = <STDIN>

이 "스칼라 컨텍스트"에서, 행 입력 연산자는 이번에 읽은 한 줄을 반환하거나, 입출력 에러가 난 경우(파일의 끝에 도달했든가 해서) undef을 반환한다.

그러나 똑같은 연산자가 "리스트 컨텍스트"에서는 남아 있는 모든 줄을 반환한다. 이미 파일의 끝에 도달했다면 빈 리스트를 반환한다.

@b = <STDIN>

래리 월이 비슷한 연산을 하는 두 개의 서로 다른 연산자를 만들 수도 있었을 것이다. 그러나 연산자를 "컨텍스트에 의존"하게 만듦으로써, 머리 속과 키보드의 공간을 절약하였다. 우리 인간들이 문맥을 잘 이해한다면, 언어에도 그것을 조금 떠넘기지 못할 게 뭐람?

마찬가지로, 패턴 일치 연산자는 스칼라 컨텍스트에서 성공 여부를 반환한다.

$a = /(\w+) (\d+)/

$a의 값은 저 정규식$_의 값에 일치되면 참, 그렇지 않으면 거짓이다. 결과가 참이라면 $1의 값을 검사하여 단어 부분을, $2의 값을 검사하여 숫자 부분을 얻을 수 있다. 같은 일을 하는 더 짧은 방법은 동일한 정규식을 리스트 컨텍스트에서 쓰는 것이다.

@b = /(\w+) (\d+)/

이제는 정규식 일치 연산자가 참/거짓을 반환하는 게 아니라, 두 개의 아이템(즉 두 메모리 공간의 값)으로 이뤄진 리스트를 반환하거나, 일치하지 않을 경우 빈 리스트를 반환한다. $b[0]은 단어 부분을, $b[1]은 숫자 부분을 담게 된다.

이 두 연산자 모두, 스칼라로 해석하느냐 리스트로 해석하느냐에 따라 다르게 동작하지만, 각 컨텍스트에서 어떻게 동작할지 미리 알 수 있는 공식 같은 건 없다. 일반적으로는 다 그런 식이다. 공통적으로 적용되는 규칙은 없다. 이것은 래리가 가장 실용적이고 유용하며 가장 덜 놀랄(음, 래리 입장에서) 일이라고 생각한 방식으로 정해진 것이다

예제를 몇 개 더 본 후 컨텍스트를 인식하는 방법을 더 알아보자.

gmtime은 스칼라 컨텍스트에서는 인자로 받은 GMT 시각(유닉스 에포크 기준의 정수값, 인자가 없으면 현재 시각)을 사람이 읽을 수 있는 형식의 문자열로 변환하여 반환한다. 리스트 컨텍스트에서는 시,분,초 등의 원소 아홉 개로 구성된 리스트를 반환한다.

readdir 연산자는 readline과 비슷하게, 스칼라 컨텍스트에서는 디렉토리에 있는 "다음" 항목의 이름을 반환하고 리스트 컨텍스트에서는 남아 있는 모든 항목의 이름들을 반환한다.

마지막으로, 매우 흔한 연산으로 배열의 이름을 두 컨텍스트에서 사용하는 경우를 보자. @x라는 "연산자"는 리스트 컨텍스트에서는 @x 배열의 현재 원소들의 리스트를 반환한다. 그러나 스칼라 컨텍스트에서는 이 "연산자"는 배열의 원소의 개수("배열의 길이"라고도 불리지만 혼동의 여지가 있으므로 여기서는 이 표현은 쓰지 않겠다)를 반환한다.

마지막 예제에서, 펄이 스칼라 컨텍스트에서 배열 원소의 개수를 세기 위해 일단 원소들 모두를 추출하거나 하지는 않는다. 처음부터 펄은 @x 연산이 스칼라 컨텍스트에서 수행된다는 것을 알고, "스칼라 버전"의 연산을 수행한다.

바꿔 말하면, 리스트를 스칼라로 "강제"하거나 "변환"하는 방법은 없다. 애초에 그런 일은 일어날 수 없기 때문이다. 비록 상업용 펄 문서들 중 일부가 그런 식으로 표현하고 있지만 말이다.

그러면 컨텍스트는 어떤 곳에서 발생하는가? 모든 곳에서이다! 설명하기 편하도록, 표현식의 어느 부분이 스칼라 컨텍스트에서 평가된다면 SCALAR라고 표시하기로 하자.

$a = SCALAR;

마찬가지로 리스트 컨텍스트는 LIST로 쓰겠다.

@x = LIST;

이제 자주 보이는 여러 가지 경우를 살펴보자. 배열의 원소에 대입하는 구문은 다음과 같다.

$w[SCALAR] = SCALAR;

배열 첨자를 나타내는 표현식도 스칼라 컨텍스트에서 평가되는 것에 유의하라. 이 말은 왼쪽에 배열 이름, 오른쪽에 readline 연산자가 있을 경우 둘 다 스칼라 컨텍스트에서 사용된다는 뜻이다.

$w[@x] = <STDIN>;

위 구문은 한 줄(또는 undef)을 배열 @w의, 현재 @x 배열의 원소의 개수에 해당하는 첨자의 원소에 대입한다. 그리고 이 평가는 언제나 대입하기 전에 이뤄진다. 따라서

$w[@w] = <STDIN>;

위 구문은 @w 배열의 끝에 다음 줄을 추가하게 된다.

슬라이스는 리스트 컨텍스트에서 평가된다. 첨자에 쓰인 값이 하나뿐일 때도 마찬가지이다.

@w[LIST] = LIST;
@w[3] = LIST;

해시 슬라이스의 경우도 동일하다.

@h{LIST} = LIST;

스칼라의 리스트는 언제나 리스트이다. 좌변에 값이 하나 뿐일 때(또는 값이 없을 때)도 마찬가지이다.

($a, $b, $c) = LIST;
($a) = LIST;
() = LIST;

다음은 자주 사용되는 여러 연산의 컨텍스트이다.

foreach (LIST) { ... }
if (SCALAR) { ... }
while (SCALAR) { ... }
@w = map { LIST } LIST;
@x = grep { SCALAR } LIST;

한 가지 유용한 규칙은, 참/거짓 값으로 평가되는 것들은 모두 스칼라라는 점이다. 위의 if, while, grep의 경우에서 볼 수 있다.

서브루틴은 "멀리서" 보아야 한다. 서브루틴의 반환값은 그 서브루틴을 실행한 쪽의 컨텍스트에 맞춰 평가된다. 기본적인 형태는 다음과 같다.

$a = &fred(LIST); sub fred { ....; return SCALAR; }
@b = &barney(LIST); sub barney { ....; return LIST; }

만일 fred를 두 곳에 다 쓴다면 어떻게 되는가? 컨텍스트가 전달되므로 서로 다른 컨텍스트에서 실행된다. 머리가 복잡해졌다면, 제대로 이해가 될 때까지는 그렇게 사용하지 않도록 하라.

서브루틴에 넘어온 인자나 임시 값을 저장하기 위해 렉시컬 변수(my-변수)를 쓰는 것은 흔한 일이다.

sub marine {
    my ($a) = @_;
    ...
}

여기서 괄호가 있으므로 리스트 컨텍스트가 된다(my가 없다고 상상해보라). @_의 원소 모두가 반환되지만 그 중 첫 번째 것만 $a에 저장된다 (나머지는 무시된다).

하지만 괄호가 없으면 대입문 우변에 스칼라 컨텍스트가 적용된다.

my $a = @_;

이것은 @_(인자 리스트) 배열의 원소의 개수를 얻게 된다. 이것은 원한 게 아닐 것이다. 이 차이를 알고 올바른 것을 사용해야 한다.

이제 제일 처음 적었던 문제로 돌아오자. 두 코드의 차이는 무엇인가? 백틱 연산자는 스칼라 컨텍스트에서는 전체 값을 하나의 문자열로 만든다.

my $f = `fortune`;

그러나 동일한 표현식이 리스트 컨텍스트에서는 원소들의 리스트(파일을 읽을 때처럼, 한 원소당 한 줄)를 반환하고, 그 중 첫 번째 원소만이 좌변의 스칼라 변수에 들어갈 수 있다.

my ($f) = `fortune`;

따라서 $f에는 fortune의 출력 중 첫 줄만 들어간다. 보통은 별 해가 되지 않지만, 교직원이 학생의 홈페이지를 봤는데 거기에 다음과 같이 쓰였다면 교내 총격 사고를 떠올리게 된다.

나는 샷건을 아디다스 가방에 넣고 테니스 양말 네 켤레를 채워넣어 모양을 잡았다.

저건 단지 무작위로 뽑아낸 문구이고 페이지를 새로고침 했으면 다른 문구가 떴을 것이라는 점은 신경쓰지 말자.

경찰이 호출되었고, 그 학생은 심문을 받았으며, 괄호를 잘못 넣은 것 때문에 경찰 기록에 등재되었다. 기소되지는 않았으나 이런 곤란은 불명히 달갑지 않은 일이다.

그리고 이런 곤란은 프로그래밍을 할 때 조금 더 주의를 기울이고 테스트를 했다면 피할 수 있는 일이다. 그러니 펄을 가지고 놀면서 컨텍스트가 궁금할 때는 이 내용을 바르게 이해하여 죄수가 되지 않도록 하자.

이름:  
Homepage:
내용:
 

컴퓨터분류

마지막 편집일: 2016-8-4 2:01 pm (변경사항 [d])
1178 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기