介绍 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 核心 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,还包括谓词方法和清除器。Marlin 和 Moo 在可能的情况下都使用 Class::XSAccessor,这使它们相较于其他实现拥有显著优势。Marlin 还会直接在子类的符号表中为父类方法创建别名,从而让 Perl 绕过大量常规的方法解析开销。
我原本期待 class 的表现会更好;它的读取器和写入器目前是用纯 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',
},
};
Code Example
=> ArrayRef[Str],
default => [],
handles_via => 'Array',
handles => { add_language => 'push', count_languages => 'count' },
};
这些实现的性能差异并不大,但 Marlin 略微 占优。Marlin 和 Moose 也是唯一两个开箱即用、无需额外扩展模块即可提供此功能的框架。
注意:
default => []是有意为之。你可以将空的数组引用或哈希引用设为默认值,Marlin 会把它当作default => sub { [] }来处理。它巧妙地跳过了调用代码引用(这很慢),而是在 XS 中直接创建一个指向新空数组的引用(快速)!
Combined Benchmark
| 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 的表现非常出色。
词法访问器和私有属性
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 为该属性使用内部存储(inside‑out),因此直接通过$obj->{internal_id}访问将不起作用。这实现了真正的私有属性。- 在 Perl 5.18 及更高版本中,你还可以使用普通的
my sub foo语法声明词法方法,从而同时拥有私有属性和私有方法。
常量属性
package Person {
use Marlin
name => { isa => Str, required => true },
species_name => { isa => Str, constant => "Homo sapiens" };
}
- 常量属性的声明方式与普通属性相同,但始终是只读的,且不能在构造函数中传入。
- 它们支持委托,只要被委托的方法不尝试修改该值。
Perl Version Support
虽然某些词法特性需要更新的 Perl 版本,Marlin 可在 Perl 5.8.8 及更高版本上运行。
未来方向
一些我的想法:
- 如果加载了 Moose,则为 Marlin 类和角色创建元对象协议(MOP)相关的功能,类似 Moo 所做的那样。
