Как передать метод класса в качестве аргумента другому методу класса в Perl 6

У меня есть скрипт, подобный приведенному ниже. Намерение состоит в том, чтобы иметь различные методы фильтрации для фильтрации списка.

Вот код

  2 
  3 class list_filter {
  4   has @.my_list = (1..20);
  5 
  6   method filter($l) { return True; }
  7 
  8   # filter method
  9   method filter_lt_10($l) {
 10     if ($l > 10) { return False; }
 11     return True;
 12   }
 13 
 14   # filter method
 15   method filter_gt_10($l) {
 16     if ($l < 10) { return False; }
 17     return True;
 18   }
 19 
 20   # expecting a list of (1..10) to be the output here
 21   method get_filtered_list_lt_10() {
 22     return self.get_filtered_list(&{self.filter_lt_10});
 23   }
 24 
 25   # private
 26   method get_filtered_list(&filter_method) {
 27     my @newlist = ();
 28     for @.my_list -> $l {
 29       if (&filter_method($l)) { push(@newlist, $l); }
 30     }
 31     return @newlist;
 32   }
 33 }
 34 
 35 my $listobj = list_filter.new();
 36 
 37 my @outlist = $listobj.get_filtered_list_lt_10();
 38 say @outlist;

Ожидая, что [1..10] будет выводом здесь. Но получаю следующую ошибку.

Too few positionals passed; expected 2 arguments but got 1

  in method filter_lt_10 at ./b.pl6 line 9
  in method get_filtered_list_lt_10 at ./b.pl6 line 22
  in block <unit> at ./b.pl6 line 37

Что я здесь не так делаю?

4 ответа

Решение

Передача метода в качестве параметра в Perl 6 требует либо использования методов MOP (Meta-Object Protocol), либо передачи метода по имени (что затем сделает поиск для вас во время выполнения).

Но зачем использовать methods, если вы действительно не делаете что-то с объектом в этих методах? Они также могут быть subс, который вы можете передать в качестве параметра.

Возможно, это лучше всего на примере:

class list_filter {
    has @.my_list = 1..20;  # don't need parentheses

    sub filter($ --> True) { } # don't need code, signature is enough

    # filter sub
    sub filter_lt_10($l) { not $l > 10 }

    # filter sub
    sub filter_gt_10($l) { not $l < 10 }

    # private
    method !get_filtered_list(&filter_sub) {
        @.my_list.grep(&filter_sub);
    }

    # expecting a list of (1..10) to be the output here
    method get_filtered_list_lt_10() {
        self!get_filtered_list(&filter_lt_10);
    }
}

my $listobj = list_filter.new();
my @outlist = $listobj.get_filtered_list_lt_10();
say @outlist; # [1 2 3 4 5 6 7 8 9 10]

Первый sub filter, который возвращает только постоянное значение (в этом случае True), гораздо проще представить в подписи с пустым телом.

filter_lt_10 а также filter_gt_10 подводным лодкам нужно только условие отменено, следовательно, использование not,

get_filtered_list метод должен быть закрытым, поэтому сделайте его закрытым, добавив префикс !,

в get_filtered_list_lt_10 теперь вам нужно позвонить get_filtered_list с ! вместо ., И вы передаете filter_lt_10 Sub как параметр, добавив префикс & (в противном случае это будет считаться вызовом сабвуфера без каких-либо параметров, что приведет к сбою).

Изменить get_filtered_listиспользовать встроенный grep метод: это занимает Callable блок, который принимает один параметр и который должен что-то возвращать True включить значение списка, над которым он работает. Так как sub принимая один параметр является Callableмы можем просто указать саб там напрямую.

Надеюсь, это имело смысл. Я старался держаться как можно ближе к предполагаемой семантике.

Некоторые общие замечания по программированию: мне кажется, что наименования сабов сбивают с толку: мне кажется, что они должны быть вызваны filter_le_10 а также filter_ge_10потому что это действительно то, что они делают, мне кажется. Кроме того, если вы действительно не хотите никакой специальной фильтрации, а только фильтрации из определенного набора предопределенных фильтров, вам, вероятно, будет лучше создать таблицу диспетчеризации с использованием констант или enums, и используйте его, чтобы указать, какой фильтр вы хотите, вместо того, чтобы кодировать эту информацию во имя еще одного метода для создания и обслуживания.

Надеюсь это поможет.

TL;DR Вы сказали P6, какие аргументы ожидать при вызове вашего filter метод. Тогда вы не смогли передать согласованный аргумент (ы), когда вы его вызвали. Так что P6 пожаловался от вашего имени. Чтобы решить эту проблему, либо передайте аргумент (ы), который вы сказали P6 ожидать, либо прекратите указывать P6 ожидать их.:)


В сообщении говорится, что ожидается 2, получил 1скорее, чем ожидалось 1 получил 0,

Это потому что self неявно передается и добавляется к "ожидаемому" и "полученному" итогам в этом добавленном бите детали сообщения, увеличивая оба на единицу. (Эта деталь, возможно, менее чем удивительная, то есть то, что мы, возможно, должны рассмотреть вопрос об исправлении.)


Когда я запускаю ваш код в tio, я получаю:

Too few positionals passed; expected 2 arguments but got 1
  in method filter at .code.tio line 27
  in method print_filtered_list at .code.tio line 12
  in block <unit> at .code.tio line 42

Объявление методаmethod filter($l) {...} в строке 27 говорит P6 ожидать два аргумента для каждого .filter вызов метода:

  • Инвокант. (Это будет связано с self.) Давайте назовем этот аргумент A.

  • Позиционный аргумент. (Это будет связано с $l параметр). Давайте назовем этот аргумент B.

Но в &{self.filter} в строке 12, пока вы предоставляете .filter Вызов метода с аргументом A, то есть аргумент инвокант, вы не предоставляете аргумент B, то есть позиционный аргумент (после filterнапример, &{self.filter(42)}).

следовательно Too few positionals passed; expected 2 arguments but got 1,

&{self.method} синтаксис был новым для меня, так что спасибо за это. К сожалению, это не работает, если нужны параметры. Ты можешь использовать sub как упоминалось в других постерах, но если вам нужно использовать методы, вы можете получить метод, вызвав self.^lookupЭто использование мета-объектного протокола, о котором упоминала Элизабет. ('^' означает, что вы вызываете не метод, являющийся частью этого класса, а скорее часть "теневого" класса, который содержит подробности реализации / реализации основного класса.)

Чтобы получить метод, используйте run obj.^lookup(method name)и вызвать его, передав сам объект (часто "я") в качестве первого параметра, а затем другие параметры. Чтобы привязать объект к функции, чтобы его не нужно было явно добавлять каждый раз, используйте функцию предполагается.

class MyClass {
  method log(Str $message) { say now ~ " $message"; }
  method get-logger() { return self.^lookup('log').assuming(self); }
}
my &log = MyClass.get-logger();
log('hello'); # output: Instant:1515047449.201730 hello

Нашел это. это то, что сработало для меня.

  3 class list_filter {
  4   has @.my_list = (1..20);
  5 
  6   # will be overriding this in derived classes
  7   method filter1($l) { return True; }
  8   method filter2($l) { return True; }
  9 
 10   # same print method I will be calling from all derived class objects
 11   method print_filtered_list($type) {
 12     my @outlist = self.get_filtered_list($type);
 13     say @outlist;
 14   }
 15 
 16   # private
 17   method get_filtered_list($type) {
 18     my @newlist = ();
 19     for @.my_list -> $l {
 20       my $f = "filter$type";
 21       if (self."$f"($l)) { push(@newlist, $l); }
 22     }
 23     return @newlist;
 24   }
 25 }
 26 
 27 class list_filter_lt_10 is list_filter {
 28   method filter1($l) {
 29     if ($l > 10) { return False; }
 30     return True;
 31   }
 32   method filter2($l) {
 33     if ($l > 10) { return False; }
 34     if ($l < 5) { return False; }
 35     return True;
 36   }
 37 }
 38 
 39 class list_filter_gt_10 is list_filter {
 40   method filter1($l) {
 41     if ($l < 10) { return False; }
 42     return True;
 43   }
 44   method filter2($l) {
 45     if ($l < 10) { return False; }
 46     if ($l > 15) { return False; }
 47     return True;
 48   }
 49 }
 50 
 51 my $listobj1 = list_filter_lt_10.new();
 52 $listobj1.print_filtered_list(1);
 53 $listobj1.print_filtered_list(2);
 54 
 55 my $listobj2 = list_filter_gt_10.new();
 56 $listobj2.print_filtered_list(1);
 57 $listobj2.print_filtered_list(2);
 58 

Выход:

./b.pl6
[1 2 3 4 5 6 7 8 9 10]
[5 6 7 8 9 10]
[10 11 12 13 14 15 16 17 18 19 20]
[10 11 12 13 14 15]

Ответ piojo выглядит так, как будто он сработает (хотя я не пробовал).

Другой подход к превращению метода в переменную - использование косвенного обращения:

class Foo {
    method bar($a) {
        $a * 2
    }
}

sub twice(&f, $x) {
    f f $x
}

my $foo = Foo.new();
say twice {$foo.bar: $^a}, 1
Другие вопросы по тегам