Over the last few days I’ve been involved in a discussion on LinkedIn[1]. It has been interesting as it shows how many people still misunderstand many of the intricacies of context and, in particular, how it ties in with the values returned from subroutines.
The original question asked why these two pieces of code acted differently:
1 2 3 |
my ($index) = grep { $array[$_] == $variable } 0 .. $#array; |
1 2 3 |
my $index = grep { $array[$_] == $variable } 0 .. $#array; |
The first one gives the first index where the element equals $variable, the second one gives the number of indexes where the element equals $variable.
The answer, of course, comes down to context. And most people who answered seemed to understand that, but many of their explanations were still way off the mark. Here’s the first answer:
grep returns an array – so the first one will return the value of the first match, but 2nd one in scalar context will return the size of the array, the number of matches. I believe.
The first statement here – “grep returns an array” – is wrong, so any explanation built on that fact is going to be fundamentally flawed.
After a couple of similar answers, I jumped in and pointed out that the only way to know how grep will work in different contexts is to read the documentation; which says:
returns the list value consisting of those elements for which the expression evaluated to true. In scalar context, returns the number of times the expression was true.
At that point it got a bit weird. People started telling me all sorts of strange things in order to show that the earlier answers were better than mine. I was told that lists and arrays are the same thing, that there was something called “array context” and that it was possible for function to return arrays.
I think I’ve worked out what most of the misconceptions in the discussion were. Here’s a list.
1. Arrays are not lists
I know this is a very common misunderstanding. I come across it all the time. People use the terms “list” and “array” interchangeably and end up thinking that they are the same thing. They aren’t. A list is a data value and an array is a variable. They act the same way a lot of the time but unless you understand the difference, you will make mistakes.
Mike Friedman wrote a great blog post that explains the difference in considerable detail. But the core difference is this – arrays are persistent (at least while they are in scope) data structures; lists are ephemeral.
On training courses, I tell people that if they understand the difference between an array and a list they’ll be in the top 20% of Perl programmers. In my experience, that’s pretty close to the truth.
2. Subroutines return lists
A subroutine can only ever return a list. Never an array. It can be an empty list. It can be a list with only one item. But it’s always a list.
There’s one small “gotcha” here. I know this bit me a few times in the past. If you read the documentation for return, it says this:
Evaluation of EXPR may be in list, scalar, or void context, depending on how the return value will be used
This means that when you have code like return @array, it’s @array that is evaluated in the context of the subroutine call. So, depending on the context of the call, this will return either a list consisting of all the elements in @array, or a list with one element which is the number of elements in @array.
3. There is no “array context”
When you confuse lists and arrays, then it’s not surprising that you might also decide that you can also invent a new context called “array context”. There are only list context, scalar context and void context. Ok, actually there are a few more specialised contexts that you can use (see the Want module for details) but most of the time you’ll only be dealing with those three.
Of course, the name of the wantarray function doesn’t help at all here. But it’s worth noting that the documentation for wantarray ends by saying:
This function should have been named wantlist() instead.
4. You can’t guess contextual behaviour
One common argument I got when pointing this out on LinkedIn ran along the lines of “but grep acts like it returns an array, so it’s a useful mental model – it helps people to understand context”.
The first part of this is true – grep does act like it returns an array. It returns a list (which you might often store in an array) in list context and it returns the length of that list in scalar context. I can see how you would mistake that for returning an array. But see my point 2 above. Subroutines do not ever return arrays.
But is it a useful mental model? Does it help people understand how subroutines work in different contexts?
No. It helps people to understand how this particular function works. And there are several other Perl functions that work the same way (keys is one example). But there are plenty of other functions that don’t work that way. The canonical example is localtime. In list context, it returns a list of nine values; in scalar context it returns a single value (which isn’t the number 9). Another good example is caller. In list context, it returns a list of items (which can contain either three or eleven elements); in scalar context it returns the first item of that list. There are many more examples.
So the problem with a mental model that assumes that functions work as though they return arrays is that it only works for a subset of functions. And you’d need to waste effort remembering which functions it works for and which ones it doesn’t work for. You’d be far better off (in my opinion) realising that there is no rule that works for all functions and just remembering how each function works (or, more practically, remembering to check the documentation whenever you need to know).
If you’ve seen me giving a lightning talk at a conference this year, you’ll know that I’ve been encouraging more people from the Perl community to get involved in discussions in places outside the echo chamber, places like LinkedIn. It’s interesting because you see how people outside of the community see Perl. You see the misunderstandings that they work with and you might see how we can improve the Perl documentation to help them get around those misunderstandings.
This morning, this comment was posted to the LinkedIn discussion that I’ve been talking about:
This stuff should be included in a default perl tutorial to avoid common mistake.
And, you know, I think he’s probably right.
[1] Because of the way LinkedIn works, you won’t be able to see this discussion unless you’re a member of LinkedIn and, probably, a member of the Perl group as well.
For the idea of acting as if it returns as array, the easy retort is that you can’t use the returned value as an argument to the array operators such as shift.
An array is a variable. A list is data. An array stores a list.
You can mutate an array. You can’t mutate a list.
“An array is a variable. A list is data. An array stores a list.”
I think every explanation of this subject should start with this. It’s an analogy that’s easy to understand for anyone who grasps variables, and once you get it, everything else becomes more or less obvious.
I think there would have been fewer posts if your comment had said something along the lines of:
Perhaps. But I think you underestimate the ability of LinkedIn discussions to drag on and on even when definitive answers have been given.
Last week I gave a talk on perl at work. It covered context. Although I explained it correctly, I could have explained in more concisely and coherently had I read this first. Thanks for the great post!
This misunderstanding (or rather, unwillingness to actually read the documentation) isn’t just happening outside the echo chamber. I’ve had many of those discussion, with similar arguments on Perlmonks and Usenet.
Did anyone bring up the “default behaviour of a list in scalar context” argument? Quite a popular theory on Perlmonks, even among the regulars.
Thanks for plugging my Arrays vs. Lists post. That was the culmination of about a decade’s worth of thinking I understood stuff until I realized I didn’t.