【问题标题】:How can classes be made parametric in Perl 6?如何在 Perl 6 中使类参数化?
【发布时间】:2019-12-24 13:12:54
【问题描述】:

通常在 Perl 6 中,只允许角色参数化。在这里,我们将尝试创建一个通常不允许参数化、参数化的 kind(从这里开始称为元对象)类。

如果您尝试以幼稚的方式使类参数化,则会发生这种情况:

bastille% perl6 -e 'class Foo[::T] {}'
===SORRY!=== Error while compiling -e
Unable to parse class definition
at -e:1
------> class Foo⏏[::T] {}
    expecting any of:
        generic role

但是如果你看看NativeCall 中的CArray 类型使用什么元对象,你会发现它实际上是一个类,而不是一个角色,但它仍然是参数化的!

bastille% perl6 -MNativeCall -e 'say CArray[int32].HOW.^name'
Perl6::Metamodel::ClassHOW+{<anon>}+{<anon>}

这是怎么做到的?

【问题讨论】:

  • 只是一点元评论:“knowhow”意味着更具体的东西;它是类的一个非常简化的实现,仅用于构建类本身所用的东西;我认为将这些东西称为“元对象”和“元类”更为常见

标签: metaprogramming raku parametric-polymorphism


【解决方案1】:

使类参数化需要一点元编程来完成。一个简单的参数化容器类可以这样实现:

use v6.d;

class Container {
    my role ContainerImpl[::T] {
        has T $.value;

        method new(Container: T $value) {
            self.bless: :$value
        }

        multi method gist(Container:D: --> Str:D) {
            $!value.gist
        }
        multi method Str (Container:D: --> Str:D) {
            $!value.Str
        }
        multi method perl(Container:D: --> Str:D) {
            self.^name ~ '.new(' ~ $!value.perl ~ ')'
        }
    }

    method ^parameterize(Mu:U \this, Mu \T) {
        my $type := this.^mixin: ContainerImpl[T];
        $type.^set_name: this.^name ~ '[' ~ T.^name ~ ']';
        $type
    }
}

say Container[Int].new(1).perl;
# OUTPUT: Container[Int].new(1)

那么这是如何工作的呢?

扮演Perl6::Metamodel::MetaMethodContainer 角色的元类,例如Perl6::Metamodel::ClassHOW,可以将其他元方法与类型的专有技术(描述特定类型的类型,例如类或角色的行为方式)混合在一起。在解析类型名称时,Rakudo 的语法在任何给定类型上调用parameterize 元方法,其中参数类型和任何参数化类型作为参数。通常,参数类型应该实现参数原型,但是这里没有检查,它允许任何类型参数化,只要它实现parameterize 元方法。 p>

mixin 元方法特定于Perl6::Metamodel::Mixins 角色,Perl6::Metamodel::ClassHOW 也是如此。该方法通过重新分配类来混合角色,因此它被认为与传递给它的(在这种情况下是参数化的)角色具有相同的类型。

结合parameterizemixin 元方法允许您在角色中实现参数化行为,然后通过在混合之前对其进行参数化在类中使用它。这允许类的行为就好像它实际上是一个参数类型,即使它在技术上仍然不是一个。

【讨论】:

    【解决方案2】:

    TL;DR 这个答案是@Kaiepi++ 的“简化”版本。它仅涵盖从他们的答案中提取的下面显示的核心代码位。它的编写是为了使其可以作为独立的解释,或者作为他们答案的介绍或补充。

    使类参数化

    名义上的问题非常广泛。但问题的主体归结为使类参数化,这就是这个答案(和@Kaiepi's)所关注的。

    类,作为kind 类型,不支持开箱即用的参数化。但是 P6 完全是metaprogrammable。所以你可以只对一个类进行元编程来添加参数。 注意。这不是官方支持的技术!1

    (您可以在种类级别添加参数化,这样所有类或您从类派生的某种新类型都是参数化的。但我认为这需要相当大的努力。 2 与此同时,只需要六行相当简单的元编程就可以使单个类参数化。这就是我们在这个答案中要做的全部。)

    代码

    class foo {
        my role bar[::T] {}
    
        method ^parameterize(Mu:U \this, Mu \T) {
            my $type := this.^mixin: bar[T];
            $type.^set_name: this.^name ~ '[' ~ T.^name ~ ']';
            $type
        }
    }
    
    say foo[Int].new.perl;
    # OUTPUT: foo[Int].new
    

    以上代码摘自@Kaiepi 的回答,省略了我认为不必要的内容。这个答案的其余部分详细解释了代码。

    role bar[::T]

    role 像一个类一样收集属性和方法。此 SO 上下文中的主要区别在于角色是可参数化的,并且可以添加到类中,从而使类成为参数化。

    [] 之间的位是签名。 ::Ta type variable。签名可以像常规函数签名一样复杂。

    我展示的bar 角色的主体是空的。在此技术的实际应用中,您将编写要添加到foo 类的属性和方法。这些将是需要使用参数化的属性和方法,以及可以合理包含在同一角色中的其他属性和方法。

    ^some-method-name

    方法名称开头的^ 表明它将对其显式调用者的调用,而是“直到”调用者的“h更高的 order workings" 体现在 knowhow 对象中,知道 kind 类型的工作原理。 p>

    使用初始 ^ 声明方法会导致自定义包含类的 knowhow 对象以包含该方法。

    ^parameterize

    如果您在编译器需要类型的地方编写 foo[...],则编译器会调用(相当于)foo.^parameterize,这会变成对foo 的专有技术对象上的parameterize 的调用。

    并且foo 的knowhow 对象已被定制为包含我们的方法:

    method ^parameterize(Mu:U \this, Mu \T) {
        my $type := this.^mixin: bar[T];
        $type.^set_name: this.^name ~ '[' ~ T.^name ~ ']';
        $type
    }
    

    \this

    this 是什么? (\ 只是表示“削减印记”;我不是指那个方面。)

    thisfoo类型对象,即foo中不以^开头的普通方法中与self关联的同一类型对象。3

    bar 添加到foo 以便foo 被参数化

    我们现在可以生成参数化的foo

        my $type := this.^mixin: bar[T];
    

    从在this 中保存的未参数化foo 开始,我们将"mix" in bar 参数化为T 传递给^parameterize

    遵循 P6 标称类型系统的协议

    这一行确保我们的新参数化类型与系统很好地配合:

        $type.^set_name: this.^name ~ '[' ~ T.^name ~ ']';
    

    继续 @Kaiepi 的回答

    此答案是@Kaiepi 答案的简化版本。

    如果实际实现是具有参数化公共属性的类,则仅涵盖诸如确保.perl 正常工作的问题是不够的。

    脚注

    1 元模型的许多细节不是official P6 的一部分。 .^parameterize 方法不是。

    2 我非常有信心,通过适当的(学习胆量和)元编程,可以使所有类或从类派生的一种新类的行为都像roles,因为一种使用明显语法支持“开箱即用”参数化的类型:

    class foo[::T] { ... }
    

    3 我强烈同意@Kaiepi 的决定使用\self 作为^ 方法的第一个参数。这将是一个谎言和阴影通常的self。想必@Kaiepi 的想法是this 经常被用作self 的同义词,但是,如果你知道P6,显然self 相同,因为它是第一个参数但不是调用者参数。

    【讨论】:

    • 我喜欢你的详细回答@raiph。这让我可以回答我关于如何混入的问题。实际上,我不会混合 HOW,而是使用您的(未记录的)META 方法覆盖 method ^parameterize。不幸的是,这不适用于缺少Perl6::Metamodel::MetaMethodContainer 的角色。
    • 感谢您确认大量细节对您有用,并且此答案可以帮助您完成工作。 :) 您可能已经知道这一点,但到目前为止,@Kaiepi 几乎是 SO 上领先的元编程探索者。他们教会了我大部分我所知道的不在设计和用户文档中的内容。此外,我对 SO 元编程的大部分贡献只是以我自己的方式重新解释他们所说的话。而且我认为凯皮可能从布拉德吉尔伯特那里学到了一些基本的东西。所以这些人,当然还有 jnthn,是特别需要寻找元编程内容的作者。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-23
    相关资源
    最近更新 更多