Perl 클래스 문법 레퍼런스
Perl 클래스 문법 레퍼런스 (perlclass)
이 문서는 Perl의 class 기능 문법을 설명해요. class 기능은 객체 지향 프로그래밍에 쓰는 네이티브 키워드를 제공하는 기능이에요.
use v5.38;
use feature 'class';
class My::Example 1.234 {
field $x;
ADJUST {
$x = "Hello, world";
}
method print_message {
say $x;
}
}
My::Example->new->print_message;
역사 (History)
Perl 5 이후로 객체 지원은 참조(ref)를 패키지 이름으로 bless 하는 개념을 중심으로 돌아왔어요(perlfunc의 "bless REF,CLASSNAME" 참고). 이렇게 bless된 참조는 그 패키지(또는 그 부모 중 하나)에서 서브루틴을 호출하는 데 사용될 수 있었죠.
이 시스템은 기본에 충실했지만, 객체 지향을 위한 더 발전된 커뮤니티 주도 시스템들을 여럿 만들 수 있을 만큼 유연했어요. 자세한 내용은 perlmod와 perlobj를 참고하세요.
class 기능은 다른 프로그래밍 언어에서 볼 수 있는 것과 비슷한 클래스 문법의 코어 구현이에요. bless를 감싼 래퍼가 아니라, perl 인터프리터에 바로 내장된 완전히 새로운 시스템이에요.
키워드 (KEYWORDS)
class 기능을 활성화하면 현재 어휘 스코프(lexical scope)에서 다음의 새 키워드들을 사용할 수 있어요.
class
class NAME BLOCK
class NAME VERSION BLOCK
class NAME VERSION : ATTRIBUTES... BLOCK
class NAME;
class NAME VERSION;
class NAME VERSION : ATTRIBUTES...;
class 키워드는 클래스로 쓰려는 새 패키지를 선언해요(perlmod의 "Packages" 참고). class 기능의 다른 모든 키워드는 이 선언의 스코프 안에서 사용해야 해요.
class WithVersion 1.000 {
# 클래스 정의가 여기 들어감
}
클래스는 블록 문법이나 문장(statement) 문법 중 하나로 선언할 수 있어요. 블록을 쓰면 블록의 본문이 클래스의 구현을 담아요. 문장 형식을 쓰면 현재 스코프나 파일의 나머지가, 다음 class나 package 문장이 나올 때까지 사용돼요.
class 선언은 package 키워드처럼 선택적으로 버전 번호를 가질 수 있어요. 또한 선택적으로 속성(attribute)을 가질 수도 있어요. 둘 다 지정한다면 버전 번호가 속성보다 먼저 와야 해요.
class와 package 선언은 비슷하지만, 클래스는 new라는 이름의 생성자를 자동으로 얻어요. 직접 작성할 필요가 없고(작성해서도 안 되고)요. 추가로 클래스 BLOCK 안에서는 필드(field)와 메서드(method)를 선언할 수 있어요.
field
field VARIABLE_NAME;
field VARIABLE_NAME = EXPR;
field VARIABLE_NAME : ATTRIBUTES;
field VARIABLE_NAME : ATTRIBUTES = EXPR;
필드는 클래스의 스코프 안에서, 더 정확히는 "method"와 ADJUST 블록 안에서 볼 수 있는 변수예요. 각 클래스 인스턴스는 다른 인스턴스와 독립적으로 필드 저장 공간을 가져요.
필드는 일반적인 어휘 스코프 변수처럼 동작해요. 시길(sigil)을 가지며 클래스에 사적(private)이에요(다만 접근자 메서드를 만들면 외부에서 접근할 수 있게 돼요). 핵심 차이는, 서로 다른 인스턴스가 같은 스코프에서 서로 다른 값에 접근한다는 점이에요.
class WithFields {
field $scalar = 42;
field @array = qw(this is just an array);
field %hash = (species => 'Martian', planet => 'Mars');
}
필드는 선택적으로 초기화 표현식을 가질 수 있어요. 있으면 그 표현식이 각 객체 인스턴스의 생성자 안에서 평가돼요. 평가할 때마다 그 이전에 설정된 필드의 값과 스코프 안의 다른 변수들을 사용할 수 있어요.
class WithACounter {
my $next_count = 1;
field $count = $next_count++;
}
:param 필드 속성과 함께 쓰면, 기본값 표현식은 = //= ||= 연산자 중 하나를 사용할 수 있어요. =을 쓰는 표현식은 호출자가 해당 파라미터를 생성자에 아예 전달하지 않을 때마다 적용돼요. //=을 쓰는 표현식은 호출자가 파라미터를 전달했지만 값이 undef일 때도 적용되고, ||=을 쓰는 표현식은 값이 거짓(false)일 때 적용돼요.
필드 초기화 표현식이 평가되는 동안에는 인스턴스가 아직 생성되지 않아서 $self 어휘 변수를 사용할 수 없어요. 하지만 특별한 __CLASS__ 토큰을 사용해 지금 생성 중인 클래스의 이름을 얻을 수 있어요. 이를테면 그 클래스에 클래스 메서드를 호출해 필드 값을 구성하는 데 도움을 받을 수 있죠.
class WithCustomField {
use constant DEFAULT_X => 10;
field $x = __CLASS__->DEFAULT_X;
}
이렇게 하면 하위 클래스가 다른 동작으로 메서드를 오버라이드할 수 있어요.
class DifferentCustomField :isa(WithCustomField) {
sub DEFAULT_X { rand > 0.5 ? 20 : 30 }
}
DifferentCustomField 인스턴스가 생성되면, 베이스 클래스의 __CLASS__ 표현식이 올바른 클래스 이름을 만들어 내서, 오버라이드된 이 메서드가 대신 호출돼요.
method
method METHOD_NAME SIGNATURE BLOCK
method METHOD_NAME BLOCK
method SIGNATURE BLOCK
method BLOCK
메서드는 클래스 객체의 맥락에서 호출되도록 만들어진 서브루틴이에요.
현재 객체 인스턴스로 채워진 $self라는 변수가 method의 어휘 스코프 안에 자동으로 만들어져요.
메서드는 항상 use feature 'signatures'가 적용된 것처럼 동작하지만, 시그니처 관점에서는 $self가 인자 목록에 나타나지 않아요.
class WithMethods {
field $greetings;
ADJUST {
$greetings = "Hello";
}
method greet($name = "someone") {
say "$greetings, $name";
}
}
일반 서브루틴처럼 메서드도 익명(anonymous)으로 만들 수 있어요:
class AnonMethodFactory {
method get_anon_method {
return method {
return 'this is an anonymous method';
};
}
}
메서드는 my 접두사를 붙여 어휘 서브루틴(lexical subroutine)으로도 선언할 수 있어요. 이렇게 하면 현재 스코프 안에서 어휘적으로 보이지만 심볼 테이블에는 나타나지 않는 서브루틴이 만들어져요. 그 결과는 사적(private) 메서드예요. 클래스 자신의 코드 안에서는 호출할 수 있지만, 외부에서는 호출할 수 없어요.
이런 어휘 서브루틴을 메서드로 호출하려면 ->& 연산자를 쓰는 게 가장 좋아요. 이 연산자는 이름에 의한 메서드 조회를 우회해서, 어휘 서브루틴을 마치 메서드인 것처럼 직접 호출해요.
class LexicalMethod {
my method abc ($x, $y) {
say "Internal method abc invoked with x=$x y=$y";
}
method xyz {
$self->&abc("x", "y");
}
}
# `abc` 메서드는 여기서는 보이지 않음
속성 (ATTRIBUTES)
앞서 언급한 키워드의 특정 측면은 속성(attribute) 으로 관리돼요. 속성은 모두 콜론으로 시작하며, 항목 이름 뒤에 공백으로 구분해 하나 이상 붙일 수 있어요.
클래스 속성 (Class attributes)
:isa
클래스는 :isa 클래스 속성을 사용해 하나의 슈퍼클래스에서 상속받을 수 있어요.
class Example::Base { ... }
class Example::Subclass :isa(Example::Base) { ... }
상속된 메서드는 보이고 호출될 수 있어요. 필드는 항상 어휘적이라 상속으로는 보이지 않아요.
:isa 속성은 베이스 클래스의 최소 버전을 요구할 수 있어요. use MODULE VERSION처럼, 실제 베이스 클래스 버전이 너무 낮으면 컴파일이 실패해요.
class Example::Subclass :isa(Example::Base 2.345) { ... }
:isa 속성은 이름이 붙은 모듈이 아직 로드되지 않았다면 require하려 시도해요.
필드 속성 (Field attributes)
:param
:param 속성을 가진 스칼라 필드는 생성자에 전달된 이름 있는 파라미터(named parameter)에서 값을 가져와요. 기본적으로 파라미터 이름은 필드 이름(맨앞의 $ 시길을 뺀 것)과 같지만, 속성 안에서 다른 이름을 지정할 수 있어요.
field $x :param;
field $y :param(the_y_value);
기본값 표현식이 없다면 파라미터는 생성자가 요구하는 필수 항목이 돼요. 호출자가 전달해야 하며, 그렇지 않으면 예외가 던져져요. 기본값 표현식이 있으면 선택 사항이 돼요.
:reader
:reader 속성을 가진 필드는 읽기 접근자 메서드(reader accessor method)를 자동으로 생성해요. 생성된 메서드는 빈(즉 인자가 없는) 시그니처를 가지며, 본문은 단순히 필드 변수의 값을 반환해요.
field $s :reader;
# 아래와 동일
field $s;
method s () { return $s; }
기본적으로 접근자 메서드는 필드 이름(맨앞의 시길을 뺀 것)과 같은 이름을 가지지만, 속성 값에서 다른 이름을 지정할 수 있어요.
field $x :reader(get_x);
# 메서드를 생성함
method get_x () { return $x; }
리더 메서드는 비-스칼라 필드에도 적용할 수 있어요. 리스트 맥락에서 호출하면 필드의 내용을 내놓고, 스칼라 맥락에서는 마치 필드 변수가 스칼라 맥락에 놓인 것처럼 요소의 개수를 내놓아요.
field @users :reader;
...
scalar $instance->users;
:writer
:writer 속성을 가진 필드는 쓰기 접근자 메서드(writer accessor method)를 자동으로 생성해요. 생성된 메서드는 정확히 하나의 인자를 소비하는 시그니처를 가지며, 본문은 그 스칼라 인자를 필드에 할당하고 호출 객체(invocant) 자신을 반환해요.
field $s :writer;
# 아래와 동일
field $s;
method set_s($new) { $s = $new; return $self; }
기본적으로 접근자 메서드는 필드 이름에서 맨앞의 시길을 뺀 것에 set_ 문자열을 앞에 붙인 이름을 가지지만, 속성 값에서 다른 이름을 지정할 수 있어요.
field $x :writer(write_x);
# 메서드를 생성함
method write_x ($new) { ... }
현재 쓰기 접근자는 스칼라 필드에만 적용할 수 있어요. 비-스칼라 필드에 이 속성을 적용하려 하면 컴파일 시점에 치명적인 예외가 발생해요. 앞으로의 버전에서 배열이나 해시 필드에 대한 라이터를 허용하도록 완화될 수 있어요. 지금으로서는 직접 만들어야 합니다.
메서드 속성 (Method attributes)
아직 없어요.
객체 수명주기 (OBJECT LIFECYCLE)
생성 (Construction)
각 객체는 생성자 호출로 수명을 시작해요. 생성자는 항상 new라고 이름 붙고, 클래스 이름에 대한 메서드 호출처럼 호출돼요:
my $object = My::Class->new(%arguments);
객체 생성 중에 클래스 필드는 %arguments 해시에서 찾아져 가능한 곳에 채워져요.
조정 (Adjustment)
객체 조정은 객체 생성 중에 임의의 사용자 정의 코드를 실행하는 방법이에요. ADJUST 블록에 코드를 넣어서 이뤄져요. 객체가 생성될 때마다 그 ADJUST 블록들이 (선언된 순서대로) 실행돼요.
class WellAdjusted {
field $x :param;
ADJUST {
say "Hello!";
}
ADJUST {
say "x = $x";
}
}
my $object = WellAdjusted->new(x => 42);
# 출력:
# Hello!
# x = 42
ADJUST 블록은 문법적으로 한 번만 실행되는 BEGIN이나 INIT 블록과 비슷해요. 하지만 ADJUST 블록은 메서드처럼 $self(생성 중인 객체를 담고 있는 어휘 변수)와 그 시점까지 만들어진 모든 객체 필드에 접근할 수 있어요.
수명 (Lifetime)
생성 단계가 끝나면 객체는 사용할 준비가 돼요.
객체에 blessed(Scalar::Util::blessed 또는 builtin::blessed)를 쓰면 클래스 이름을 반환하고, reftype(Scalar::Util::reftype 또는 builtin::reftype)은 'OBJECT' 문자열을 반환해요.
파괴 (Destruction)
객체는 Perl의 다른 데이터 구조처럼, 마지막 참조가 사라질 때 파괴돼요.
TODO
이 기능은 아직 실험적이고 매우 불완전해요. 다음 목록은 아직 추가되거나 바뀌어야 할 기능들의 개요예요:
- 역할(Roles): 역할을 선언하는 문법(아마
role키워드)과 역할을 클래스에 소비(consume)하는 문법(아마:does()속성)이 필요해요. - ADJUST 블록에 대한 파라미터:
ADJUST블록이 이름 있는 파라미터를 소비하도록 선언하는 문법이 필요해요. 이 파라미터들은 클래스 생성자 API의 일부가 돼요. 이는 서브루틴 시그니처에 이름 있는 인자를 추가하려는 비슷한 계획에서 영감을 받았을 수 있어요. - 메타프로그래밍(Metaprogramming): 클래스, 메서드, 필드, ADJUST 블록 등 클래스 관련 세부사항에 대한 지식을 추가하는 메타프로그래밍 API의 확장(현재 PPC0022에서 제안 중).
- 확장 커스터마이징(Extension Customisation): 코어 밖의 모듈이 새 클래스나 필드 속성을 제공하는 능력 등, 클래스 시스템과 상호작용할 수 있는 방법.
알려진 버그 (KNOWN BUGS)
실험적인 class 기능에는 다음과 같은 해결되지 않은 버그들이 존재해요.