Как передать метод класса в качестве аргумента другому методу класса в 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), либо передачи метода по имени (что затем сделает поиск для вас во время выполнения).
Но зачем использовать method
s, если вы действительно не делаете что-то с объектом в этих методах? Они также могут быть 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
потому что это действительно то, что они делают, мне кажется. Кроме того, если вы действительно не хотите никакой специальной фильтрации, а только фильтрации из определенного набора предопределенных фильтров, вам, вероятно, будет лучше создать таблицу диспетчеризации с использованием констант или enum
s, и используйте его, чтобы указать, какой фильтр вы хотите, вместо того, чтобы кодировать эту информацию во имя еще одного метода для создания и обслуживания.
Надеюсь это поможет.
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