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;

출처: perlclass - Perl class syntax reference

역사 (History)

Perl 5 이후로 객체 지원은 참조(ref)를 패키지 이름으로 bless 하는 개념을 중심으로 돌아왔어요(perlfunc의 "bless REF,CLASSNAME" 참고). 이렇게 bless된 참조는 그 패키지(또는 그 부모 중 하나)에서 서브루틴을 호출하는 데 사용될 수 있었죠.

이 시스템은 기본에 충실했지만, 객체 지향을 위한 더 발전된 커뮤니티 주도 시스템들을 여럿 만들 수 있을 만큼 유연했어요. 자세한 내용은 perlmodperlobj를 참고하세요.

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) 문법 중 하나로 선언할 수 있어요. 블록을 쓰면 블록의 본문이 클래스의 구현을 담아요. 문장 형식을 쓰면 현재 스코프나 파일의 나머지가, 다음 classpackage 문장이 나올 때까지 사용돼요.

class 선언은 package 키워드처럼 선택적으로 버전 번호를 가질 수 있어요. 또한 선택적으로 속성(attribute)을 가질 수도 있어요. 둘 다 지정한다면 버전 번호가 속성보다 먼저 와야 해요.

classpackage 선언은 비슷하지만, 클래스는 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 기능에는 다음과 같은 해결되지 않은 버그들이 존재해요.

더 알아보기 (Learn more)