I have a script like the below. Intent is to have different filter methods to filter a list.
Here is the code.
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;
Expecting [1..10] to be the output here. But getting following error.
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
What am I doing wrong here?
Passing a method as a parameter in Perl 6 either requires you to use MOP (Meta-Object Protocol) methods, or pass the method by name (which would then do the lookup for you at runtime).
But why use method
s if you're not really doing something with the object in those methods? They might as well be sub
s then, which you can pass as a parameter.
Perhaps this is best by example:
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]
The first sub filter
, which only returns a constant value (in this case True
), can be represented much more easily in the signature with an empty body.
The filter_lt_10
and filter_gt_10
subs only need the condition negated, hence the use of the not
.
The get_filtered_list
method is supposed to be private, so make it a private method by prefixing !
.
In the get_filtered_list_lt_10
you now need to call get_filtered_list
with a !
instead of a .
. And you pass the filter_lt_10
sub as a parameter by prefixing the &
(otherwise it would be considered a call to the sub without any parameters, which would fail).
Change the get_filtered_list
to use the built-in grep
method: this takes a Callable
block that takes a single parameter and which should return something True
to include the value of the list it works upon. Since a sub
taking a single parameter is a Callable
, we can just specify the sub there directly.
Hope this made sense. I tried to stay as close as possible to the intended semantics.
Some general programming remarks: it feels to me that the naming of the subs is confusing: it feels to me that they should be called filter_le_10
and filter_ge_10
, because that's really what they do it appears to me. Also, if you really don't want any ad-hoc filtering, but only filtering from a specific set of predefined filters, you would probably be better of by creating a dispatch table using constants or enum
s, and use that to indicate which filter you want, rather than encoding this information in the name of yet another method to make and maintain.
Hope this helps.
TL;DR You told P6 what arguments to expect when calling your filter
method. Then you failed to pass the agreed argument(s) when you called it. So P6 complained on your behalf. To resolve the issue, either pass the argument(s) you told P6 to expect or stop telling P6 to expect them. :)
The message says expected 2
, got 1
, rather than expected 1
got 0
.
This is because self
is implicitly passed and added to the "expected" and "got" totals in this appended bit of message detail, bumping both up by one. (This detail is perhaps Less Than Awesome, i.e. something we should perhaps consider fixing.)
When I run your code on tio I get:
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
The method declaration method filter($l) {...}
at line 27 tells P6 to expect two arguments for each .filter
method call:
The invocant. (This will be bound to
self
.) Let's call that argument A.A positional argument. (This will be bound to the
$l
parameter). Let's call that argument B.
But in &{self.filter}
in line 12, while you provide the .filter
method call with an argument A, i.e. an invocant argument, you don't provide an argument B, i.e. a positional argument (after filter
, e.g. &{self.filter(42)}
).
Hence Too few positionals passed; expected 2 arguments but got 1
.
Found it. this is what worked for me.
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
Output:
./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]
The &{self.method}
syntax was new to me, so thanks for that. Unfortunately it doesn't work if parameters are needed. You can use sub
as other posters mentioned, but if you need to use methods, you can get a method by calling self.^lookup
, which is the use of the meta-object protocol that Elizabeth mentioned. ('^' means you're not calling a method that's part of that class, but rather part of the "shadow" class which contains the main class's guts / implementation details.)
To get a method, use run obj.^lookup(method name)
, and call it by passing in the object itself (often "self") as the first parameter, then the other parameters. To bind the object to the function so it doesn't need to be explicitly added each time, use the assuming function.
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
来源:https://stackoverflow.com/questions/48080366/how-to-pass-a-class-method-as-argument-to-another-method-of-the-class-in-perl-6