perl6 правильно передает подпрограмму в переменную объекта - PullRequest
0 голосов
/ 04 января 2019

Мне нужно передать некоторый код из внешней программы в класс.

В универсальном модуле, который у меня есть (для простоты сводится к глупости)

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, то он не виден внеclass.

Поэтому мой вопрос таков: есть ли другой способ создать переменную общедоступного объектного кода, которая автоматически не добавит инвокант в качестве переменной в код?Кроме создания отдельного метода доступа.

Вот короткий скрипт 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 голосов
/ 04 января 2019

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 ) является вызовом стиля sub подпрограммы, содержащейся в атрибуте &!hl.Он получает один аргумент, $s.

Есть ли другой способ создать общедоступную переменную кода объекта, которая автоматически не добавляет инвоканта в качестве переменной в код?

Проблема не в том, что P6 автоматически добавляет инвокант.

Кроме создания отдельного метода доступа.

Нет способапрямой доступ к атрибуту вне исходного кода класса, в котором он объявлен.Единственный способ обеспечить доступ - через отдельный метод доступа.Этот ответ, надеюсь, прояснит путаницу по этому поводу.Другие ответы выложите ваши варианты.

0 голосов
/ 04 января 2019

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 )
    

    Byпереопределяя его, все остальные изменения, делающие его общедоступным атрибутом, все еще выполняются для вас.
    Поэтому не нужно создавать подметод 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'};
    
0 голосов
/ 04 января 2019

Проблема в том, что метод доступа возвращает атрибут, который, как оказалось, является Callable.Только затем вы хотите вызвать возвращаемое значение метода доступа с параметрами.По сути, это то, что вы делаете, создавая собственный аксессор.

Вам не нужно создавать собственный аксессор.Просто добавьте дополнительные скобки (указывающие, что вы вызываете метод доступа без каких-либо дополнительных аргументов), а затем скобки для значений, которые вы на самом деле хотите передать:

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

Или, если вам не нравятся скобки,рассмотрим синтаксис вызова метода, как указал Тимотимо:

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