3

How to get the length of an anonymous list?

perl -E 'say scalar ("a", "b");' # => b

I expected scalar to return the list in a scalar context - its length.

Why it returns the second (last) element?

It works for an array:

perl -E 'my @lst = ("a", "b"); say scalar @lst;' # => 2
Miroslav Popov
  • 3,294
  • 4
  • 32
  • 55
  • 2
    Re "*I expected scalar to return the list in a scalar context, it's length.*", A list in scalar context evaluates to its last item (which is also evaluated in scalar context). In other words, `a,b,c` in scalar context is comparable to `do { a; b; c; }`. – ikegami Aug 03 '19 at 05:54
  • @ikegami, I made this example in order to find when the brackets form a list and when they are used for sorting out precedence. However, from your comment, I see something. `my $foo = 2 * (3 + 1);`. Do the brackets make a list of a single element `(4)` and return its last element in a scalar context - `4`? – Miroslav Popov Aug 03 '19 at 06:58
  • 3
    Parens don't create lists; they merely change precedence. There is no list operator, and there's nothing evaluated in list context (except may the assignment depending on surrounding code). – ikegami Aug 03 '19 at 07:00
  • 1
    I reverted the changes you made to the question because you removed the list from your question about the length of lists. – ikegami Aug 03 '19 at 07:05
  • 1
    I think you might be conflating two different definitions of "list". An instance of the comma operator (e.g. `"a", "b"`, but not `qw( a b )`) could be called a list, and one says that an operator evaluates to a list and a sub returns a list as a shorthand for "0 or more scalars" (e.g. `"a", "b"` evaluates to a list in your second snippet, but not the first.) – ikegami Aug 03 '19 at 07:09
  • Re "*return the list in a scalar context,*", This is impossible. A list (i.e. 0 or more scalars) can't be returned in scalar context; only exactly one scalar can be returned in scalar context. – ikegami Aug 03 '19 at 07:11

3 Answers3

6

You can use

my $n = () = f();

As applied to your case, it's

say scalar( () = ("a", "b") );

or

say 0+( () = ("a", "b") );

First, let's clear up a misconception.

You appear to believe that some operators evaluate to some kind of data structure called a list regardless of context, and that this list returns its length when coerced into scalar context.

All of that is incorrect.

An operator must evaluate to exactly one scalar in scalar context, and a sub must return exactly one scalar in scalar context. In list context, operators can evaluate to any number of scalars, and subs can return any number of scalars. So when we say an operator evaluates to a list, and when we say a sub returns a list, we aren't referring to some data structure; we are simply using "list" as a shorthand for "zero or more scalars".

Since there's no such thing as a list data structure, it can't be coerced into a scalar. Context isn't a coercion; context is something operators check to determine to what they evaluate in the first place. They literally let context determine their behaviour and what they return. It's up to each operator to decide what they return in scalar and list context, and there's a lot of variance.

As you've noted,

  • The @a operator in scalar context evaluates to a single scalar: the length of the array.
  • The comma operator in scalar context evaluates to a single scalar: the same value as its last operand.
  • The qw operator in scalar context evaluates to a single scalar: the last value it would normally return.

On to your question.

To determine to how many scalars an operator would evaluate when evaluated in list context, we need to evaluate the operator in list context. An operator always evaluates to a single scalar in scalar context, so your attempts to impose a scalar context are ill-founded (unless the operator happens to evaluate to the length of what it would have returned in list context, as is the case for @a, but not for many other operators).

The solution is to use

my $n = () = f();

The explanation is complicated.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • what is the benefit of that goatse-thing? (in this case). It doesn't seem to be more efficient than `perl -le 'sub lcount(@) { @_/1 }; print lcount qw(foo bar baz)'`. –  Aug 03 '19 at 08:17
  • @mosvy It avoids having to write an unnecessary sub. And technically, it is more efficient (fewer ops, and avoids one of the most expensive ones, the sub call). – ikegami Aug 03 '19 at 08:21
  • @mosvy "_doesn't seem to_" -- Did you benchmark it? That would trade a simple list-flattening of sorts (`() = f()` throws away all elements due to apparent assignment to an empty list) for a sub-call with prototype and all. Not to mention coding simplicity? – zdim Aug 03 '19 at 08:22
  • @zdim I didn't benchmarked, I did `perl -MO=Terse`d it. Where's the sub call? –  Aug 03 '19 at 08:23
  • @mosvy The second instance of `lcount` is a sub call. – ikegami Aug 03 '19 at 08:28
  • right, still not clear how much more efficient it is. –  Aug 03 '19 at 08:30
  • @zdim, The prototype doesn't matter. It has no rub-time effect – ikegami Aug 03 '19 at 08:30
  • @mosvy, Not much. The only reason I mentioned efficiency is to correct your false claim. The code you posted is virtually a superset of the code I posted, so it will necessarily be slower. You claimed otherwise. – ikegami Aug 03 '19 at 08:32
  • There is still a way to get an anonymous array length. Please see [my reply](https://stackoverflow.com/a/68509426/3191958) to this question. – Yuriy Ershov Jul 24 '21 at 11:14
  • @Yuriy Ershov, yeah, lots of ways to determine to how many scalars an operator would evaluate when evaluated in list context, but the one I suggested is simple to read, idiomatic, and the fastest. Yours builds an array (which involves copying each scalar) on top of all the work the one I posted does, so it's going to be slower (especially if any of the values are magic). – ikegami Jul 24 '21 at 21:47
  • @ikegami You are right. I've read your explanation more thoroughly - I find it really good. My first impression was that your point was that it's impossible to do, you also didn't give an explicit answer to the author's question. I was wrong. Your solution is more efficient, although it might be less readable in some cases. I just took the liberty to improve your answer so that it explicitly answers the author's question by adding `print(scalar(()=("a", "b")))` example. I hope that's ok. The trick here is that print expects the arguments in "list" context and in needs a conversion. – Yuriy Ershov Jul 26 '21 at 02:20
  • @YuriyErshov Added a tl,dr version at the top. – ikegami Jul 26 '21 at 02:40
5

One way

perl -wE'$len = () = qw(a b c); say $len'   #--> 3

The = () = "operator" is a play on context. It forces list context on the "operator" on the right side and assigns the length of the returned list. See this post about list vs scalar assignments and this page for some thoughts on all this.

If this need be used in a list context then the LHS context can also be forced by scalar, like

say scalar( () = qw(a b c) );

Or by yet other ways (0+...), but scalar is in this case actually suitable, and clearest.


In your honest attempt the scalar imposes the scalar context on its operand -- where there's an expression in this case, which is thus evaluated, by the comma operator. Thereby one after another term is discarded, until the last one which is returned.

You'd get to know about that with warnings on, as it would emit

Useless use of a constant ("a") in void context at -e line 1

Warnings can always be enabled in one-liners as well, with -w flag. I recommend that.


I'd like to also comment on the notion of a "list" in Perl, often misunderstood.

In programming text a "list" is merely a syntax device, that code can use; a number of scalars, perhaps submitted to a function, or assigned to an array variable, or so. It is often identified by parenthesis but those really only decide precedence and don't "make" anything nor give a "list" any sort of individuality, like a variable has; a list is just a grouping of scalars.

Internally that's how data is moved around; a "list" is a fleeting bunch of scalars on a stack, returned somewhere and then gone.

A list is not -- not -- any kind of a data structure or a data type; that would be an array. See for instance a perlfaq4 item and this related page.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • Thanks for mentioning `-w`. I did not notice it raises a warning. – Miroslav Popov Aug 03 '19 at 06:20
  • 1
    @MiroslavPopov Yes, extremely useful that `-w` is. I'd say that we want that in one-liners just as bad (as in production programs), since they're mostly used precisely to try things out. For me it's in the fingers -- I don't think, my one-liners _always_ have that `w` flag. – zdim Aug 03 '19 at 06:52
  • There is a way to do that [inline](https://stackoverflow.com/a/68509426/3191958). – Yuriy Ershov Jul 24 '21 at 11:15
  • @YuriyErshov Do what "_inline_"? It does it in one expression (what isn't any criterion for code worth btw). If you mean to also print it : (1) that's not the point (2) I _don't want to_ cram things into unclear acrobatics. Btw, there is usually more than one way to do a thing (in any language...) – zdim Jul 26 '21 at 07:25
-2

Perl references are hard. I'm not sending you to read prelref since it's not something that anyone can just read and start using.

Long story short, use this pattern to get an anonymous array size: 0+@{[ <...your array expression...> ]}

Example:

print 0+@{[ ("a", "b") ]};
Yuriy Ershov
  • 354
  • 2
  • 8
  • Note that this needlessly builds an array (which involves copying each scalar), so it's not just more complex (to read) than the solution in the earlier two answers, it's slower. – ikegami Jul 24 '21 at 21:47
  • "_Long story short, use this pattern to get an anonymous array size:_" --- in short, I say "don't do that" (unless it's for play and obfuscation) as it is a convoluted way to do something fairly simple. Clarity usually comes first, and while it may compete with efficiency this is also slower. – zdim Jul 26 '21 at 07:22