I want to be able to run a function with a variable number of parameters in Perl6, but after reading through https://docs.perl6.org/language/functions#Arguments I don't see how it can be done. I see numerous links for other languages, and a warning that "Questions with similar titles have frequently been downvoted" but I don't see this anywhere in the documentation or StackOverflow.
I want to do something like this:
some-test($v1, $v2)
or
some-test($v3)
but not have a separate function using multi for each
How can I construct a Perl6 subroutine which will accept a variable number of strings?
TL;DR You're asking about variadic functions.1 Simple use is simple. Some P6 features, most notably positional and named arguments, and variadic destructuring, add some wrinkles. Also, see the other answers which are very helpful too.
variable number of arguments
Simple use of a simple variadic function:
sub variadic (|args) { say args .elems }
variadic(); # 0
variadic('1'); # 1
variadic('1', '2'); # 2
A |foo parameter slurps up all remaining arguments into a Capture bound to sigilless identifier foo:
sub variadic ($a, #b, %c, |others) { say others[0] }
variadic 1, [2,3], (:foo), 4, [5,6], (:bar); # 4
In the above example the others parameter "slurps"1 up the last three listed arguments starting with the 4.
Variadic variations
Things aren't always simple:
variadic(1, 2); # 2 -- works but args are Ints
variadic(:foo,:bar); # 0 -- where did :foo, :bar go?
variadic((:foo)); # 1 -- works; arg is Pair (:foo)
variadic((1, 2)); # 1 -- works; arg is List (1,2)
In the rest of this answer I explain:
Constraining arguments, eg ensuring they're all strings.
Positional vs named arguments; **#foo and *%foo
Variadic positionals destructuring; +#foo and *#foo
Constraining arguments
variable number of strings
You can impose any constraints you want on slurped arguments by using a where clause. (Which you can in turn package into a subset if you want.)
For example, to constrain all the arguments to be of type Str:
sub variadic (|args where .all ~~ Str) { say args .elems }
variadic(); # 0
variadic('1'); # 1
variadic('1', '2'); # 2
variadic(1); # Constraint type check failed
Positional vs named arguments; **#foo and *%foo
P6 supports both positional and named arguments.
Using |foo in a signature always captures all remaining arguments, both positional and named:
sub slurps-into-WHAT (|args) { say WHAT args }
slurps-into-WHAT(); # (Capture)
A Capture stores positional arguments in an internal list accessible via positional subscripting (i.e. args[...]) and named arguments in a hash accessible via associative subscripting (i.e. args<...> or args{...}):
sub variadic (|args) { say " {args[1]} {args<named2>}" }
variadic('pos0', 'pos1', named1 => 42, named2 => 99); # pos1 99
Sometimes it's preferable to collect just named args, or just positional ones, or to collect both but in separate parameters.
To collect named args, use a parameter of the form *%foo (one asterisk prefix and a hash arg):
sub variadic-for-nameds (*%nameds) { say %nameds }
variadic-for-nameds(:foo, :bar); # {bar => True, foo => True}
(Note that all methods collect named args in this way even if their signature doesn't explicitly say so.2)
To collect positional args, use a parameter of the form **#foo (two asterisk prefix immediately followed by an array arg):
sub variadic-for-positionals (**#positionals) { say #positionals }
variadic-for-positionals(1, 2, 3); # [1 2 3]
Variadic positionals destructuring; +#foo and *#foo
P6 provides a range of non-variadic argument destructuring features.
The first variadic positionals destructuring parameter form is +#foo. This has exactly the same effect as **#foo except in one case; if the variadic parameter gets just a single argument, and that argument is a list or array, then the parameter is bound to the content of that list or array, stripping away the list/array container:
sub variadic-plus (+#positionals) { say #positionals }
variadic-plus(1,2,3); # says same as for **#positionals
variadic-plus(1); # says same as for **#positionals
variadic-plus([1]); # [1] -- instead of [[1]]
variadic-plus((1,2,3)); # [1 2 3] -- instead of [(1 2 3)]
The +#foo form was introduced to support the "single arg rule". It's used by core devs writing built ins. Users may wish to use it when they want the same behavior.
The other variadic positionals destructuring form is *#foo. It does the same thing as +#foo in that it extracts the content from list or array container args and throws the container away. But it's much more aggressive:
It does this for all arguments.
If an argument is a list rather than an array ((...) rather than [...]) then it descends into that list and recursively repeats the exercise if an element of the list is itself another inner list or array.
Thus:
sub variadic-star (*#positionals) { say #positionals }
variadic-star((1,2),[3,4]); # [1 2 3 4]
variadic-star((1,2),(3,4,(5,6,(7,8)))); # [1 2 3 4 5 6 7 8]
variadic-star((1,2),(3,4,[5,6,(7,8)])); # [1 2 3 4 5 6 (7 8)]
(Note how it stripped the container from the [5,6,(7,8)] array but did not descend into it.)
One final thing; are there variadic named destructuring parameters? You tell me.3
Bonus section: foo(...) vs foo (...)
(I included this bonus section in the hope it heads off confusion. If this section is itself confusing, just ignore it.)
Routine calls can be written with or without parentheses around their list of arguments and they mean the same thing. The opening parenthesis must follow the routine name immediately, without intervening whitespace:
sub foo (|args) { say args[0] }
foo 'a', 'b'; # a
foo('a', 'b'); # a
(This rule only applies to the routine call, between the routine name and its arguments. It does not apply to the routine declaration, between the routine name and its parameters. For the latter, the declaration, you can leave no space or use space as I have above with sub foo (|args) and it makes no difference.)
If you insert whitespace between the foo and the opening parenthesis in a call you're writing something different:
foo ('a', 'b'); # (a b)
That calls foo with one argument, the one list ('a', 'b') in contrast to foo('a', 'b') which calls foo with two arguments, the two values inside the parentheses.
The following calls foo with two arguments, both of which are lists:
foo ('a', 'b', 'c'), ('d', 'e', 'f'); # (a b c)
You may nest parentheses:
foo( ('a', 'b', 'c'), ('d', 'e', 'f') ) ; # (a b c)
foo (('a', 'b', 'c'), ('d', 'e', 'f')) ; # ((a b c) (d e f))
foo( (('a', 'b', 'c'), ('d', 'e', 'f')) ) ; # ((a b c) (d e f))
The latter two foo calls get one argument, the one list ( ('a', 'b', 'c'), ('d', 'e', 'f') ) (that happens to contain two inner lists).
Footnotes
1 The standard industry term for this is a variadic function. At the time of writing this answer, the Rakudo P6 compiler uses the industry standard term ("variadic") in error messages but the official P6 doc tends to use the word "slurpy" instead of "variadic" and talks of "slurping up arguments".
2 Methods always have an implicit variadic named parameter called %_ if one is not explicitly specified:
say .signature given my method foo {} # (Mu: *%_)
3 The P6 language and/or the Rakudo P6 compiler currently allows parameters to be written in the form +%foo and **%foo. It doesn't really make sense to have variadic nameds destructuring. Perhaps that explains why both these forms do insane things:
**%foo appears to be indistinguishable from %foo.
+%foo appears to be indistinguishable from **#foo except for using the identifier %foo instead of #foo. The object bound to %foo is an Array!
If you just want to be able to take 1 or 2 values then the easiest way is to mark the second value as optional :
sub some-test( $v1, $v2? ) { ... }
Or you can define a default value :
sub some-test( $v1, $v2="default" ) { ... }
Or if you want any number of values (1 or more) you can use a slurpy with a where clause :
sub some-test( *#v where *.elems > 0 ) { ... }
You can use signature destructuring
sub some-test(*#all [$first, *#rest]) { ... } # must have 1 or more parameters
Related
I want to generate the sequence of
1, 1/2, 1/3, 1/4 ... *
using functional programming approach in raku, in my head it's should be look like:
(1,{1/$_} ...*)[0..5]
but the the output is: 1,1,1,1,1
The idea is simple, but seems enough powerful for me to using to generate for other complex list and work with it.
Others things that i tried are using a lazy list to call inside other lazy list, it doesn't work either, because the output is a repetitive sequence: 1, 0.5, 1, 0.5 ...
my list = 0 ... *;
(1, {1/#list[$_]} ...*)[0..5]
See #wamba's wonderful answer for solutions to the question in your title. They showcase a wide range of applicable Raku constructs.
This answer focuses on Raku's sequence operator (...), and the details in the body of your question, explaining what went wrong in your attempts, and explaining some working sequences.
TL;DR
The value of the Nth term is 1 / N.
# Generator ignoring prior terms, incrementing an N stored in the generator:
{ 1 / ++$ } ... * # idiomatic
{ state $N; $N++; 1 / $N } ... * # longhand
# Generator extracting denominator from prior term and adding 1 to get N:
1/1, 1/2, 1/3, 1/(*.denominator+1) ... * # idiomatic (#jjmerelo++)
1/1, 1/2, 1/3, {1/(.denominator+1)} ... * # longhand (#user0721090601++)
What's wrong with {1/$_}?
1, 1/2, 1/3, 1/4 ... *
What is the value of the Nth term? It's 1/N.
1, {1/$_} ...*
What is the value of the Nth term? It's 1/$_.
$_ is a generic parameter/argument/operand analogous to the English pronoun "it".
Is it set to N?
No.
So your generator (lambda/function) doesn't encode the sequence you're trying to reproduce.
What is $_ set to?
Within a function, $_ is bound either to (Any), or to an argument passed to the function.
If a function explicitly specifies its parameters (a "parameter" specifies an argument that a function expects to receive; this is distinct from the argument that a function actually ends up getting for any given call), then $_ is bound, or not bound, per that specification.
If a function does not explicitly specify its parameters -- and yours doesn't -- then $_ is bound to the argument, if any, that is passed as part of the call of the function.
For a generator function, any value(s) passed as arguments are values of preceding terms in the sequence.
Given that your generator doesn't explicitly specify its parameters, the immediately prior term, if any, is passed and bound to $_.
In the first call of your generator, when 1/$_ gets evaluated, the $_ is bound to the 1 from the first term. So the second term is 1/1, i.e. 1.
Thus the second call, producing the third term, has the same result. So you get an infinite sequence of 1s.
What's wrong with {1/#list[$_+1]}?
For your last example you presumably meant:
my #list = 0 ... *;
(1, {1/#list[$_+1]} ...*)[0..5]
In this case the first call of the generator returns 1/#list[1+1] which is 1/2 (0.5).
So the second call is 1/#list[0.5+1]. This specifies a fractional index into #list, asking for the 1.5th element. Indexes into standard Positionals are rounded down to the nearest integer. So 1.5 is rounded down to 1. And #list[1] evaluates to 1. So the value returned by the second call of the generator is back to 1.
Thus the sequence alternates between 1 and 0.5.
What arguments are passed to a generator?
Raku passes the value of zero or more prior terms in the sequence as the arguments to the generator.
How many? Well, a generator is an ordinary Raku lambda/function. Raku uses the implicit or explicit declaration of parameters to determine how many arguments to pass.
For example, in:
{42} ... * # 42 42 42 ...
the lambda doesn't declare what parameters it has. For such functions Raku presumes a signature including $_?, and thus passes the prior term, if any. (The above lambda ignores it.)
Which arguments do you need/want your generator to be passed?
One could argue that, for the sequence you're aiming to generate, you don't need/want to pass any of the prior terms. Because, arguably, none of them really matter.
From this perspective all that matters is that the Nth term computes 1/N. That is, its value is independent of the values of prior terms and just dependent on counting the number of calls.
State solutions such as {1/++$}
One way to compute this is something like:
{ state $N; $N++; 1/$N } ... *
The lambda ignores the previous term. The net result is just the desired 1 1/2 1/3 ....
(Except that you'll have to fiddle with the stringification because by default it'll use gist which will turn the 1/3 into 0.333333 or similar.)
Or, more succinctly/idiomatically:
{ 1 / ++$ } ... *
(An anonymous $ in a statement/expression is a simultaneous declaration and use of an anonymous state scalar variable.)
Solutions using the prior term
As #user0721090601++ notes in a comment below, one can write a generator that makes use of the prior value:
1/1, 1/2, 1/3, {1/(.denominator+1)} ... *
For a generator that doesn't explicitly specify its parameters, Raku passes the value of the prior term in the sequence as the argument, binding it to the "it" argument $_.
And given that there's no explicit invocant for .denominator, Raku presumes you mean to call the method on $_.
As #jjmerelo++ notes, an idiomatic way to express many lambdas is to use the explicit pronoun "whatever" instead of "it" (implicit or explicit) to form a WhateverCode lambda:
1/1, 1/2, 1/3, 1/(*.denominator+1) ... *
You drop the braces for this form, which is one of its advantages. (You can also use multiple "whatevers" in a single expression rather than just one "it", another part of this construct's charm.)
This construct typically takes some getting used to; perhaps the biggest hurdle is that a * must be combined with a "WhateverCodeable" operator/function for it to form a WhateverCode lambda.
TIMTOWTDI
routine map
(1..*).map: 1/*
List repetition operator xx
1/++$ xx *
The cross metaoperator, X or the zip metaoperator Z
1 X/ 1..*
1 xx * Z/ 1..*
(Control flow) control flow gather take
gather for 1..* { take 1/$_ }
(Seq) method from-loop
Seq.from-loop: { 1/++$ }
(Operators) infix ...
1, 1/(1+1/*) ... *
{1/++$} ... *
There are both placeholder variables and topic variables in Perl 6. For example, the following two statements are the same
say ( $_ * 2 for 3, 9 ); # use topic variables
say ( { $^i * 2 } for 3, 9 ); # use placeholder variables
It seems to me, the only benefit one gets from topic variables is saving some keyboard strokes.
My question is: Is there a use case, where a topic variable can be much more convenient than placeholder variables?
The topic can have method calls on it:
say ( .rand for 3,9);
Compared to a placeholder:
say ( {$^i.rand} for 3,9);
Saves on typing a variable name and the curly braces for the block.
Also the topic variable is the whole point of the given block to my understanding:
my #anArrayWithALongName=[1,2,3];
#anArrayWithALongName[1].say;
#anArrayWithALongName[1].sqrt;
#OR
given #anArrayWithALongName[1] {
.say;
.sqrt;
}
That's a lot less typing when there are a lot of operations on the same variable.
There are several topic variables, one for each sigil: $, #, %_ and even &_ (yep, routines are first-class citizens in Perl6). To a certain point, you can also use Whatever (*) and create a WhateverCode in a expression, saving even more typing (look, ma! No curly braces!).
You can use the array form for several variables:
my &block = { sum #_ }; say block( 2,3 )
But the main problem they have is that they are single variables, unable to reflect the complexity of block calls. The code above can be rewritten using placeholder variables like this:
my &block = { $^a + $^b }; say block( 2,3 )
But imagine you've got some non-commutative thing in your hands. Like here:
my &block = { #_[1] %% #_[0] }; say block( 3, 9 )
That becomes clumsy and less expressive than
my &block = { $^divi %% $^divd }; say block( 3, 9 ); # OUTPUT: «True»
The trick being here that the placeholder variables get assigned in alphabetical order, with divd being before divi, and divi being short for divisible and divd for divided (which you chould have used if you wanted).
At the end of the day, there are many ways to do it. You can use whichever you want.
What's going on here?
Why are %a{3} and %a{3}.Array different if %a has Array values and %a{3} is an Array?
> my Array %a
{}
> %a{3}.push("foo")
[foo]
> %a{3}.push("bar")
[foo bar]
> %a{3}.push("baz")
[foo bar baz]
> .say for %a{3}
[foo bar baz]
> %a{3}.WHAT
(Array)
> .say for %a{3}.Array
foo
bar
baz
The difference being observed here is the same as with:
my $a = [1,2,3];
.say for $a; # [1 2 3]
.say for $a.Array; # 1\n2\n3\n
The $ sigil can be thought of as meaning "a single item". Thus, when given to for, it will see that and say "aha, a single item" and run the loop once. This behavior is consistent across for and operators and routines. For example, here's the zip operator given arrays and them itemized arrays:
say [1, 2, 3] Z [4, 5, 6]; # ((1 4) (2 5) (3 6))
say $[1, 2, 3] Z $[4, 5, 6]; # (([1 2 3] [4 5 6]))
By contrast, method calls and indexing operations will always be called on what is inside of the Scalar container. The call to .Array is actually a no-op since it's being called on an Array already, and its interesting work is actually in the act of the method call itself, which is unwrapping the Scalar container. The .WHAT is like a method call, and is telling you about what's inside of any Scalar container.
The values of an array and a hash are - by default - Scalar containers which in turn hold the value. However, the .WHAT used to look at the value was hiding that, since it is about what's inside the Scalar. By contrast, .perl [1] makes it clear that there's a single item:
my Array %a;
%a{3}.push("foo");
%a{3}.push("bar");
say %a{3}.perl; $["foo", "bar"]
There are various ways to remove the itemization:
%a{3}.Array # Identity minus the container
%a{3}.list # Also identity minus the container for Array
#(%a{3}) # Short for %a{3}.cache, which is same as .list for Array
%a{3}<> # The most explicit solution, using the de-itemize op
|%a{3} # Short for `%a{3}.Slip`; actually makes a Slip
I'd probably use for %a{3}<> { } in this case; it's both shorter than the method calls and makes clear that we're doing this purely to remove the itemization rather than a coercion.
While for |%a{3} { } also works fine and is visually nice, it is the only one that doesn't optimize down to simply removing something from its Scalar container, and instead makes an intermediate Slip object, which is liable to slow the iteration down a bit (though depending on how much work is being done by the loop, that could well be noise).
[1] Based on what I wrote, one may wonder why .perl can recover the fact that something was itemized. A method call $foo.bar is really doing something like $foo<>.^find_method('bar')($foo). Then, in a method bar() { self }, the self is bound to the thing the method was invoked on, removed from its container. However, it's possible to write method bar(\raw-self:) { } to recover it exactly as it was provided.
The issue is Scalar containers do DWIM indirection.
%a{3} is bound to a Scalar container.
By default, if you refer to the value or type of a Scalar container, you actually access the value, or type of the value, contained in the container.
In contrast, when you refer to an Array container as a single entity, you do indeed access that Array container, no sleight of hand.
To see what you're really dealing with, use .VAR which shows what a variable (or element of a composite variable) is bound to rather than allowing any container it's bound to to pretend it's not there.
say %a{3}.VAR ; # $["foo", "bar", "baz"]
say %a{3}.Array.VAR ; # [foo bar baz]
This is a hurried explanation. I'm actually working on a post specifically focusing on containers.
Given a single argument, the Array constructor flattens it. This causes problems:
my %hash = (a => 1; b => 2);
my #array = [ %hash ]; # result: [a => 1 b => 2], expected [{ a => 1, b => 2 }]
The List constructor doesn't have this quirk (the single-argument rule), but unfortunately there's no short syntax for creating a one-element list:
List.new(%hash); # result is ({a => 1, b => 2}), as expected
Workaround: if your argument is a scalar, it won't auto-flatten:
my $hash = %hash;
my #array = [ $%hash ]; # or my #array = [ $%hash ], or just my #array = $%hash
# result: [{a => 1, b => 2}], as expected
Another workaround is to add a comma at the end of the element list:
my #array = [ %hash, ];
The real problem is when we write out data literally. Representing a JSON-like nested structure in Perl 6 is a real problem if 1-element lists are flattened, but other lists are not. The data ends up being wrong. I had to write out a lot of data when using MongoDB, since MongoDB API arguments must be formatted as nested lists/arrays. It was nearly impossible. So I ask, what is the motivation for flattening a single array element?
The motivation for flattening a single array element, is to consistently apply the single argument rule. The array constructor [ ] also follows the single argument rule. Maybe it helps to think of [%h] as circumfix:<[ ]>(%h), which it actually is. If you do not want flattening, you can either itemize it (prefix a $), or, as you've shown, add a comma to make it a List. This then follows the same logic as ($a) being the same as $a, but ($a,) being a List with one element $a.
my %h = a => 42, b => 666;
dd [%h]; # [:a(42), :b(666)]
dd [%h,%h]; # [{:a(42), :b(666)}, {:a(42), :b(666)}]
dd [%h,] # [{:a(42), :b(666)},] make it a List first
dd [$%h] # [{:a(42), :b(666)},] itemize the Hash
Motivation for the array construction operator using the 1-arg rule:
Because it's really just a circumfix operator, not special syntax.
Other operators/keywords that expect a potentially-nested list as input, use the single-argument rule - so for consistency, this one does too.
Motivation for list-transforming operators/keywords in general using the 1-arg rule:
To facilitate the common case where the whole input list is already stored in one variable.
To avoid further special-casing literal commas.
Operators don't have argument lists like functions; they accept just one object as their argument (unary/circumfix operators), or one on each side (binary operators).
The expression #a, constructs a one-element List, so when passing the result of that expression to a list-transforming operator, it treats that List just like it would treat any other non-containerized Iterable passed to it: It iterates it, and operates on its element(s).
The fact that the argument doesn't get iterated when it is wrapped in an item container (i.e. a $ variable), is meant to preserve the singular-plural distinction of the sigils, as #p6steve's answer explains.
Here's the aforementioned consistency, demonstrated using one keyword, one binary operator, and one circumfix operator:
for #a { ... } # n iterations
for #a, { ... } # 1 iteration
for #a, #b { ... } # 2 iterations
for $a { ... } # 1 iteration
1..Inf Z #a # new Seq of n elements
1..Inf Z #a, # new Seq of 1 element
1..Inf Z #a, #b # new Seq of 2 elements
1..Inf Z $a # new Seq of 1 element
[ #a ] # new Array with n elements
[ #a, ] # new Array with 1 element
[ #a, #b ] # new Array with 2 elements
[ $a ] # new Array with 1 element
What about subroutines/methods?
These do have argument lists, so the single-argument rule doesn't come as naturally to them as it does to operators.
Note that in the top-level scope of an argument list, commas don't create Lists – they separate arguments.
Subroutines/methods that are considered "list transformation" routines which expect potentially-nested lists as input, still participate in the single-arument rule though, by checking whether they got one argument or multiple, and if just one, whether it is wrapped in an item container:
map {...}, #a; # n iterations
map {...}, (#a,); # 1 iteration (Parens needed to get a List-constructing comma.)
map {...}, #a, #b; # 2 iterations
map {...}, $a; # 1 iteration
User-defined routines can easily get this behavior by using a +# signature.
The # sigil in Perl indicates "these", while $ indicates "the". This kind of plural/single distinction shows up in various places in the language, and much convenience in Perl comes from it. Flattening is the idea that an #-like thing will, in certain contexts, have its values automatically incorporated into the surrounding list. Traditionally this has been a source of both great power and great confusion in Perl. Perl 6 has been through a number of models relating to flattening during its evolution, before settling on a straightforward one known as the "single argument rule".
The single argument rule is best understood by considering the number of iterations that a for loop will do. The thing to iterate over is always treated as a single argument to the for loop, thus the name of the rule.
for 1, 2, 3 { } # List of 3 things; 3 iterations
for (1, 2, 3) { } # List of 3 things; 3 iterations
for [1, 2, 3] { } # Array of 3 things (put in Scalars); 3 iterations
for #a, #b { } # List of 2 things; 2 iterations
for (#a,) { } # List of 1 thing; 1 iteration
for (#a) { } # List of #a.elems things; #a.elems iterations
for #a { } # List of #a.elems things; #a.elems iterations
from Synopsis7 https://design.raku.org/S07.html#The_single_argument_rule
There are a few implementations of a hash or dictionary class in the Mathworks File Exchange repository. All that I have looked at use parentheses overloading for key referencing, e.g.
d = Dict;
d('foo') = 'bar';
y = d('foo');
which seems a reasonable interface. It would be preferable, though, if you want to easily have dictionaries which contain other dictionaries, to use braces {} instead of parentheses, as this allows you to get around MATLAB's (arbitrary, it seems) syntax limitation that multiple parentheses are not allowed but multiple braces are allowed, i.e.
t{1}{2}{3} % is legal MATLAB
t(1)(2)(3) % is not legal MATLAB
So if you want to easily be able to nest dictionaries within dictionaries,
dict{'key1'}{'key2'}{'key3'}
as is a common idiom in Perl and is possible and frequently useful in other languages including Python, then unless you want to use n-1 intermediate variables to extract a dictionary entry n layers deep, this seems a good choice. And it would seem easy to rewrite the class's subsref and subsasgn operations to do the same thing for {} as they previously did for (), and everything should work.
Except it doesn't when I try it.
Here's my code. (I've reduced it to a minimal case. No actual dictionary is implemented here, each object has one key and one value, but this is enough to demonstrate the problem.)
classdef TestBraces < handle
properties
% not a full hash table implementation, obviously
key
value
end
methods(Access = public)
function val = subsref(obj, ref)
% Re-implement dot referencing for methods.
if strcmp(ref(1).type, '.')
% User trying to access a method
% Methods access
if ismember(ref(1).subs, methods(obj))
if length(ref) > 1
% Call with args
val = obj.(ref(1).subs)(ref(2).subs{:});
else
% No args
val = obj.(ref.subs);
end
return;
end
% User trying to access something else.
error(['Reference to non-existant property or method ''' ref.subs '''']);
end
switch ref.type
case '()'
error('() indexing not supported.');
case '{}'
theKey = ref.subs{1};
if isequal(obj.key, theKey)
val = obj.value;
else
error('key %s not found', theKey);
end
otherwise
error('Should never happen')
end
end
function obj = subsasgn(obj, ref, value)
%Dict/SUBSASGN Subscript assignment for Dict objects.
%
% See also: Dict
%
if ~strcmp(ref.type,'{}')
error('() and dot indexing for assignment not supported.');
end
% Vectorized calls not supported
if length(ref.subs) > 1
error('Dict only supports storing key/value pairs one at a time.');
end
theKey = ref.subs{1};
obj.key = theKey;
obj.value = value;
end % subsasgn
end
end
Using this code, I can assign as expected:
t = TestBraces;
t{'foo'} = 'bar'
(And it is clear that the assignment work from the default display output for t.) So subsasgn appears to work correctly.
But I can't retrieve the value (subsref doesn't work):
t{'foo'}
??? Error using ==> subsref
Too many output arguments.
The error message makes no sense to me, and a breakpoint at the first executable line of my subsref handler is never hit, so at least superficially this looks like a MATLAB issue, not a bug in my code.
Clearly string arguments to () parenthesis subscripts are allowed, since this works fine if you change the code to work with () instead of {}. (Except then you can't nest subscript operations, which is the object of the exercise.)
Either insight into what I'm doing wrong in my code, any limitations that make what I'm doing unfeasible, or alternative implementations of nested dictionaries would be appreciated.
Short answer, add this method to your class:
function n = numel(obj, varargin)
n = 1;
end
EDIT: The long answer.
Despite the way that subsref's function signature appears in the documentation, it's actually a varargout function - it can produce a variable number of output arguments. Both brace and dot indexing can produce multiple outputs, as shown here:
>> c = {1,2,3,4,5};
>> [a,b,c] = c{[1 3 5]}
a =
1
b =
3
c =
5
The number of outputs expected from subsref is determined based on the size of the indexing array. In this case, the indexing array is size 3, so there's three outputs.
Now, look again at:
t{'foo'}
What's the size of the indexing array? Also 3. MATLAB doesn't care that you intend to interpret this as a string instead of an array. It just sees that the input is size 3 and your subsref can only output 1 thing at a time. So, the arguments mismatch. Fortunately, we can correct things by changing the way that MATLAB determines how many outputs are expected by overloading numel. Quoted from the doc link:
It is important to note the significance of numel with regards to the
overloaded subsref and subsasgn functions. In the case of the
overloaded subsref function for brace and dot indexing (as described
in the last paragraph), numel is used to compute the number of
expected outputs (nargout) returned from subsref. For the overloaded
subsasgn function, numel is used to compute the number of expected
inputs (nargin) to be assigned using subsasgn. The nargin value for
the overloaded subsasgn function is the value returned by numel plus 2
(one for the variable being assigned to, and one for the structure
array of subscripts).
As a class designer, you must ensure that the value of n returned by
the built-in numel function is consistent with the class design for
that object. If n is different from either the nargout for the
overloaded subsref function or the nargin for the overloaded subsasgn
function, then you need to overload numel to return a value of n that
is consistent with the class' subsref and subsasgn functions.
Otherwise, MATLAB produces errors when calling these functions.
And there you have it.