介绍 Marlin

发布: (2025年12月19日 GMT+8 05:44)
8 min read
原文: Dev.to

Source: Dev.to

Introducing Marlin 的封面图片

Toby Inkster

介绍

老实说,可能不会。
但这就是为什么你仍然可能想尝试一下 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

RateTinyPlainCoreMooseMooMarlin
Tiny545/s-48%-56%-58%-60%-64%
Plain1051/s93%-16%-19%-22%-31%
Core1249/s129%19%-4%-8%-18%
Moose1304/s139%24%4%-4%-14%
Moo1355/s148%29%8%4%-11%
Marlin1519/s179%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 所做的那样。
Back to Blog

相关文章

阅读更多 »

C 语言闭包的成本

请提供您希望翻译的具体摘录或摘要文本,我才能为您进行简体中文翻译。