perl6正确地将例程传递给对象变量

时间:2019-01-04 05:29:57

标签: class perl6

我需要将一些代码从外部程序传递到类中。

在通用模块中,我(为了简化而变得愚蠢)

class A {
   has &.hl;
   submethod BUILD( :&!hl ) {}
}

我在程序的其他地方

use A;
my &hl = -> $st { 
   my $p = shell "hl $st", :in,:out;
   $p.out.slurp
};
my $statement = 'my $perl6-variable = "Helloooo";'
my $first = &hl($statement);
my A $a .= new(:&hl);
my $second = $a.hl( $statement );

$first将被处理并包含预期结果。

$second,我会收到运行时错误

Too many positionals passed; expected 1 argument but got 2

很显然,该类中的例程既是引发者又是参数$s的提供者。

重写该类以提供自定义访问器:

class A {
   has &!hl;
   submethod BUILD( :&!hl ) {}
   method process-it( Str $s --> Str ) { &!hl( $s ) }
}
# elsewhere
my $second = $a.process-it( $statement );

然后$first$second都运行无误,并且将包含相同的结果。

当在班级内部访问hl时,不会添加任何发起人,但是如果未将其声明为&.hl,则在班级外部不可见。

因此,我的问题是:是否存在另一种创建公共对象代码变量的方法,该变量不会自动将倡导者作为变量添加到代码中?除了创建单独的访问器方法之外。

以下是简短的bash脚本hl供说明

#! /bin/bash
echo '<div class="statement">'$1'</div>'

这是完整的Perl 6程序

use v6.c;

class A {
    has &!highlighter; # also tried with has &highlighter
    submethod BUILD( :&!highlighter ) {}
    method process-it( Str $s --> Str ) {
       &!highlighter( $s )
    }
}

sub MAIN() {
    my @strings = 'my $v = "Hello World";', 'my $w = $v.perl;';
    my $proc;
    my $proc-supply;
    my &highlighter = -> $s {
        my $p = shell "./hl '$s' ", :in,:out;
        $p.out.slurp
    }

    for @strings {
        say .&highlighter
    }
    my A $a .= new(:&highlighter);
    for @strings { say $a.highlighter($_) }
    # own accessor
    for @strings { say $a.process-it($_) }
}

3 个答案:

答案 0 :(得分:8)

TL; DR 无法直接在声明该属性的类的源代码之外直接访问该属性。提供访问的唯一方法是通过单独的公共访问器方法。该答案有望消除对此的混淆。其他答案列出了您的选择。

为什么会收到Too many positionals passed;错误消息

代码has &!hl; 声明一个属性&!hl

代码has &.hl;执行相同的操作,但是生成方法.hl公共访问器具有相同名称的属性。像所有此类生成的访问器一样,它期望一个参数,即发起者,而没有其他参数。

my $second = $a.hl( $statement )

此代码调用 方法 hl。 P6将点($a)左侧的值作为第一个参数(倡导者)传递。但是,您还添加了一个$statement参数。所以它也通过了。

出现错误消息:

Too many positionals passed; expected 1 argument but got 2
  

在班级内访问hl时,不会添加任何倡导者

不是因为可以在类内部访问它。这是因为您没有将其称为方法:

method process-it( Str $s --> Str ) { &!hl( $s ) }

&!hl( $s )代码是&!hl属性中包含的例程的 sub 样式调用。它得到一个一个参数$s

  

还有另一种方法来创建不会自动将倡导者作为变量添加到代码中的公共目标代码变量吗?

问题不是P6自动添加了一个引发剂。

  

除了创建单独的访问器方法之外。

无法在声明其的类的源代码之外直接访问该属性。提供访问的唯一方法是通过单独的公共访问器方法。该答案有望消除对此的混淆。其他答案列出了您的选择。

答案 1 :(得分:8)

has $!hl声明一个私有属性。 has $.hl声明一个公共属性。

公开表示我创建了一个同名的方法并将其返回,并将其添加到BUILD / gist / perl / Capture [sub ]方法。

class A {
   has &.hl;
}

这实际上与以下内容相同:

class A {
  has &!hl;

  submethod BUILD ( :&!hl ){}

  method hl (){ &!hl } # return the code object

  method perl (){
    "A.new(hl => $!hl.perl())"
  }
  method gist (){ self.perl }

  method Capture () {
    \( :&!hl )
  }
}

因此,当您调用A.hl时,它将返回存储在&!hl中的代码对象。


您可以通过几种方式处理此问题。

  1. 只需将其称为“两次”即可。

    $a.hl()(42)
    $a.hl().(42)
    $a.hl.(42)
    
  2. 还有使用它的其他方法。

    method call-it ( |C ){
      &!hl( |C )
    }
    
    $a.call-it( 42 )
    my &hl = $a.hl;
    

    请注意,我使用|C来避免完全处理签名。
    对于您来说,拥有签名并像对待它一样处理它可能很有意义。

  3. 通过自己添加来覆盖自动生成的方法。

    method hl ( |C ){
      &!hl( |C )
    }
    

    $a.hl( 42 )
    

    通过覆盖它,使它成为公共属性的所有其他更改仍将为您完成。
    因此,无需创建BUILD子方法。


覆盖它时,这意味着is rw无效。这也意味着 外部代码无法检索代码对象本身。

如果需要的话,有多种方法可以解决。
如果您不需要返回&!hl中的值,则只需像上面一样保留它即可。

  1. 如果从未使用零位置参数调用代码对象。

    multi method hl (){ &!hl }
    multi method hl ( |C ){
      &!hl( |C )
    }
    
    $a.hl;        # returns the value in $!hl
    $a.hl();      # returns the value in $!hl
    
    $a.hl( 42 );  # calls &!hl(42)
    

    请注意,没有一种方法可以区分.hl.hl()

  2. 您还可以使用命名参数。

    multi method hl ( :code($)! ){ &!hl }
    multi method hl ( |C ){
      &hl( |C )
    }
    
    $a.hl(:code); # returns the value in &!hl
    
    $a.hl;        # calls &!hl()
    $a.hl();      # calls &!hl()
    $a.hl( 42 );  # calls &!hl(42)
    
  3. 您无能为力,更轻松地获取代码对象,只是让他们使用子签名解析来获取属性。
    (这就是为您创建Capture方法的原因)

    class A {
      has &.hl;
    
      method hl ( |C ){
        &!hl( |C )
      }
    }
    
    sub get-hl ( A $ ( :&hl ) ){ &hl }
    
    my &hl = get-hl($a);
    
    
    my &hl = -> A $ ( :&hl ){ &hl }( $a );
    
    my &hl = $a.Capture{'hl'};
    

答案 2 :(得分:6)

问题在于访问器返回的属性恰好是Callable。您只想 then 调用带参数的访问器的返回值。本质上,这就是创建自己的访问器的目的。

您不必实际创建自己的访问器。只需添加一个额外的括号(表明您正在调用访问器而没有任何额外的参数),然后在括号中添加您实际想要传递的值:

class A {
    has &.a = *.say;  # quick way to make a Callable: { .say }
}
A.new.a()(42);        # 42

或者如果您不太喜欢括号,请考虑timotimo指出的方法调用语法:

A.new.a.(42);         # 42