Marlin 소개
Source: Dev.to

소개
솔직히 말해서, 아마도 아닐 겁니다.
하지만 Marlin을 시도해 볼 만한 이유는 다음과 같습니다.
- 대부분의 생성자와 접근자는 XS로 구현되어 매우, 매우 빠릅니다.
- “속성은 보통 읽기 전용이어야 한다”와 같은 몇 가지 기본 원칙을 받아들인다면, 클래스와 그 속성을 선언하는 것이 정말, 정말 간결해집니다.
예시
use v5.20.0;
use experimental qw(signatures);
# Import useful constants, types, etc.
use Marlin::Util -all, -lexical;
use Types::Common -all, -lexical;
package Person {
use Marlin
'given_name!' => NonEmptyStr,
'family_name!' => NonEmptyStr,
'name_style' => { enum => [qw/western eastern/], default => 'western' },
'full_name' => { is => lazy, builder => true },
'birth_date?';
sub _build_full_name ( $self ) {
return sprintf( '%s %s', uc($self->family_name), $self->given_name )
if $self->name_style eq 'eastern';
return sprintf( '%s %s', $self->given_name, $self->family_name );
}
}
package Payable {
use Marlin::Role
-requires => [ 'bank_details' ];
sub make_payment ( $self ) {
...;
}
}
package Employee {
use Marlin
-extends => [ 'Person' ],
-with => [ 'Payable' ],
'bank_details!' => HashRef,
'employee_id!' => Int,
'manager?' => { isa => 'Employee' };
}
my $manager = Employee->new(
given_name => 'Simon',
family_name => 'Lee',
name_style => 'eastern',
employee_id => 1,
bank_details => {},
);
my $staff = Employee->new(
given_name => 'Lea',
family_name => 'Simons',
employee_id => 2,
bank_details => {},
manager => $manager,
);
printf(
"%s's manager is %s.\n",
$staff->full_name,
$staff->manager->full_name,
) if $staff->has_manager;
눈에 띄는 몇 가지 사항
- Moose의 대부분 기능을 지원합니다… 혹은 실제로 사용하는 대부분의 기능을 지원합니다.
use Marlin라인에 속성 이름을 적는 것만으로도 속성을 선언할 수 있습니다.- 옵션을 뒤에 붙일 수 있지만, Marlin의 기본값(읽기 전용 속성)으로 만족한다면 옵션이 필요 없습니다.
!를 사용하면{ required => true }대신 속성을 빠르게 필수로 표시할 수 있습니다.?를 사용하면{ predicate => true }대신 프레디케이트 메서드를 요청할 수 있습니다.
벤치마크
초기 벤치마크 결과 Marlin이 빠르다는 것을 보여줍니다.
생성자
Rate Tiny Plain Moo Moose Marlin Core
Tiny 1317/s -- -2% -48% -53% -54% -72%
Plain 1340/s 2% -- -47% -53% -53% -72%
Moo 2527/s 92% 89% -- -11% -12% -47%
Moose 2828/s 115% 111% 12% -- -2% -40%
Marlin 2873/s 118% 114% 14% 2% -- -39%
Core 4727/s 259% 253% 87% 67% 65% --
새로운 Perl core class 키워드만이 Marlin보다 빠른 생성자를 만들며, 그 차이는 상당히 큽니다. 하지만 객체 생성은 여러분이 필요로 할 작업의 일부에 불과합니다.
접근자
Rate Tiny Moose Plain Core Moo Marlin
Tiny 17345/s -- -1% -3% -7% -36% -45%
Moose 17602/s 1% -- -2% -6% -35% -44%
Plain 17893/s 3% 2% -- -4% -34% -44%
Core 18732/s 8% 6% 5% -- -31% -41%
Moo 27226/s 57% 55% 52% 45% -- -14%
Marlin 31688/s 83% 80% 77% 69% 16% --
여기서 “접근자”란 단순히 표준 getter와 setter뿐 아니라 predicate 메서드와 clearers까지 포함합니다. Marlin과 Moo는 가능한 경우 Class::XSAccessor를 사용해 다른 구현체보다 크게 앞서 있습니다. Marlin은 또한 부모 클래스 메서드에 대한 별칭을 자식 클래스의 심볼 테이블에 직접 생성함으로써 Perl이 일반적인 메서드 해석 오버헤드를 많이 생략하도록 합니다.
class가 더 나은 성능을 보일 것으로 기대했지만, 현재 해당 키워드의 reader와 writer는 순수 Perl로 구현되어 있어 향후 릴리스에서 개선 여지가 있습니다.
네이티브 트레이트 / Handles‑Via / 위임
Rate Tiny Core Plain Moose Moo Marlin
Tiny 675/s -- -56% -57% -59% -61% -61%
Core 1518/s 125% -- -4% -8% -13% -13%
Plain 1581/s 134% 4% -- -4% -9% -10%
Moose 1642/s 143% 8% 4% -- -5% -6%
Moo 1736/s 157% 14% 10% 6% -- -1%
Marlin 1752/s 160% 15% 11% 7% 1% --
네이티브 트레이트가 무엇인지 모른다면, 다음과 같은 작은 헬퍼 메서드를 만들 수 있는 능력을 말합니다:
sub add_language ( $self, $lang ) {
push $self->languages->@*, $lang;
}
속성 정의의 일부로 사용하기
use Marlin
languages => {
is => 'ro',
isa => ArrayRef[Str],
default => sub { [] },
handles => {
add_language => 'push',
all_languages => 'elements',
},
};
Source: …
코드 예시
=> ArrayRef[Str],
default => [],
handles_via => 'Array',
handles => { add_language => 'push', count_languages => 'count' },
};
대부분의 경우 성능 차이는 크게 나지 않지만 Marlin이 조금 앞섭니다. Marlin과 Moose는 확장 모듈 없이도 기본적으로 이 기능을 제공하는 유일한 프레임워크입니다.
Note:
default => []는 의도된 설정입니다. 빈 배열 레퍼런스나 해시 레퍼런스를 기본값으로 지정할 수 있으며, Marlin은 이를default => sub { [] }와 동일하게 처리합니다. 코드를 호출하는(느린) 대신 XS에서 새로운 빈 배열에 대한 레퍼런스를 빠르게 생성해 줍니다!
결합 벤치마크
| Rate | Tiny | Plain | Core | Moose | Moo | Marlin | |
|---|---|---|---|---|---|---|---|
| Tiny | 545/s | — | -48% | -56% | -58% | -60% | -64% |
| Plain | 1051/s | 93% | — | -16% | -19% | -22% | -31% |
| Core | 1249/s | 129% | 19% | — | -4% | -8% | -18% |
| Moose | 1304/s | 139% | 24% | 4% | — | -4% | -14% |
| Moo | 1355/s | 148% | 29% | 8% | 4% | — | -11% |
| Marlin | 1519/s | 179% | 45% | 22% | 17% | 12% | — |
객체를 생성하고 여러 접근자와 위임을 호출하는 현실적인 코드 스니펫에서도 Marlin은 매우 좋은 성능을 보여줍니다.
Lexical Accessors and Private Attributes
Marlin은 렉시컬 메서드에 대한 일류 지원을 제공합니다!
use v5.42.0;
package Widget {
use Marlin
name => { isa => Str },
internal_id => { reader => 'my internal_id', storage => 'PRIVATE' };
...
printf "%d: %s\n", $w->&internal_id, $w->name;
}
# dies because internal_id is lexically scoped
Widget->new->&internal_id;
->&연산자는 Perl 5.42에서 추가되었습니다. 오래된 Perl(5.12 이상)에서도 렉시컬 메서드를 지원하지만 함수 호출 구문으로 호출해야 합니다. 예:internal_id($w).storage => "PRIVATE"은 Marlin에게 해당 속성에 대해 인사이드‑아웃 저장 방식을 사용하도록 지시합니다. 따라서$obj->{internal_id}와 같이 직접 접근할 수 없습니다. 이는 진정한 프라이빗 속성을 제공합니다.- Perl 5.18 이상에서는 일반적인
my sub foo구문으로도 렉시컬 메서드를 선언할 수 있으며, 이를 통해 프라이빗 속성뿐 아니라 프라이빗 메서드도 사용할 수 있습니다.
Constant Attributes
package Person {
use Marlin
name => { isa => Str, required => true },
species_name => { isa => Str, constant => "Homo sapiens" };
}
- 상수 속성은 일반 속성처럼 선언되지만 항상 읽기 전용이며 생성자에 전달할 수 없습니다.
- 위임을 지원하지만, 위임된 메서드가 값을 변경하려고 하지 않을 경우에만 가능합니다.
Perl 버전 지원
일부 렉시컬 기능은 최신 Perl 버전을 필요로 하지만, Marlin은 Perl 5.8.8 이상에서 실행됩니다.
향후 방향
제가 생각한 몇 가지 아이디어:
- Moose가 로드된 경우, Moo가 하는 것과 유사하게 Marlin 클래스와 역할에 대한 메타 객체 프로토콜을 생성합니다.
