41

While answering this question, I came to realize that I was not sure whether Perl's map can be considered a loop or not?

On one hand, it quacks/walks like a loop (does O(n) work, can be easily re-written by an equivalent loop, and sort of fits the common definition = "a sequence of instructions that is continually repeated").

On the other hand, map is not usually listed among Perl's control structures, of which loops are a subset of. E.g. http://en.wikipedia.org/wiki/Perl_control_structures#Loops

So, what I'm looking for is a formal reason to be convinced of one side vs. the other. So far, the former (it is a loop) sounds a lot more convincing to me, but I'm bothered by the fact that I never saw "map" mentioned in a list of Perl loops.

13
  • BTW, this could certainly be because I looked at the wrong lists of loops, and as an answer i'd gladly accept some sort of official loop list which actually includes "map"
    – DVK
    Commented Jun 11, 2010 at 3:03
  • 11
    "Loop" has no precise universal definition, so this is strictly semantics: the answer is that map is a loop if and only if someone you recognise as authoritative (e.g. Larry Wall, or the consensus of the Perl community) says it is. Commented Jun 11, 2010 at 4:03
  • 1
    @j_random - That's sort of what I had in mind... someone providing some "official" wording (e.g. say from O'Reilly book or perlfaq or perldoc), or a response from someone deeply involved with Perl internals (IIRC Larry Wall is not on /. but Randal Schwartz and several of perl porters are)
    – DVK
    Commented Jun 11, 2010 at 4:15
  • I see what you mean -- it's useful to know the consensus view on the meaning of a word so you can communicate without causing confusion. Unfortunately it seems there's no strong consensus in this particular case. Commented Jun 11, 2010 at 8:57
  • 1
    @Snake - see Eric's answer. In perl5 specifically, it's guaranteed. In general programming languages, not
    – DVK
    Commented Jun 11, 2010 at 13:48

15 Answers 15

31

map is a higher level concept than loops, borrowed from functional programming. It doesn't say "call this function on each of these items, one by one, from beginning to end," it says "call this function on all of these items." It might be implemented as a loop, but that's not the point -- it also might be implemented asynchronously -- it would still be map.

Additionally, it's not really a control structure in itself -- what if every perl function that used a loop in its implementation were listed under "loops?" Just because something is implemented using a loop, doesn't mean it should be considered its own type of loop.

4
  • 5
    perl considers map a control structure. it is the LOGOP mapwhile, which is a control branching op
    – Eric Strom
    Commented Jun 11, 2010 at 3:23
  • considering the result of map { $i++ } @a, I must say that the first quote applies more to Perl's "map" than the second quote (the one about "on all these items), no?
    – DVK
    Commented Jun 11, 2010 at 3:30
  • @Eric - now THAT's a strong argument... would love to see it as an actual answer!
    – DVK
    Commented Jun 11, 2010 at 3:33
  • 2
    @DVK, I suppose you could say that, but I was more referring to the difference in abstraction between loops and map. Map is a functional programming concept that says (do this to these things), while a loop is a lower level control structure that could be used to implement map. Commented Jun 11, 2010 at 3:42
27

No, it is not a loop, from my perspective.

Characteristic of (perl) loops is that they can be broken out of (last) or resumed (next, redo). map cannot:

map { last } qw(stack overflow);  # ERROR!  Can't "last" outside a loop block

The error message suggests that perl itself doesn't consider the evaluated block a loop block.

20

From an academic standpoint, a case can be made for both depending on how map is defined. If it always iterates in order, then a foreach loop could be emulated by map making the two equivalent. Some other definitions of map may allow out of order execution of the list for performance (dividing the work amongst threads or even separate computers). The same could be done with the foreach construct.

But as far as Perl 5 is concerned, map is always executed in order, making it equivalent to a loop. The internal structure of the expression map $_*2, 1, 2, 3 results in the following execution order opcodes which show that map is built internally as a while-like control structure:

OP  enter
COP nextstate
OP  pushmark
SVOP const IV 1
SVOP const IV 2
SVOP const IV 3
LISTOP mapstart
LOGOP (0x2f96150) mapwhile  <-- while still has items, shift one off into $_
    PADOP gvsv GV *_            
    SVOP const IV 2             loop body
    BINOP multiply              
    goto LOGOP (0x2f96150)  <-- jump back to the top of the loop
LISTOP leave 
12

The map function is not a loop in Perl. This can be clearly seen by the failure of next, redo, and last inside a map:

perl -le '@a = map { next if $_ %2; } 1 .. 5; print for @a'
Can't "next" outside a loop block at -e line 1.

To achieve the desired affect in a map, you must return an empty list:

perl -le '@a = map { $_ %2 ? () : $_ } 1 .. 5; print for @a'
2
4

I think transformation is better name for constructs like map. It transforms one list into another. A similar function to map is List::Util::reduce, but instead of transforming a list into another list, it transforms a list into a scalar value. By using the word transformation, we can talk about the common aspects of these two higher order functions.

That said, it works by visiting every member of the list. This means it behaves much like a loop, and depending on what your definition of "a loop" is it might qualify. Note, my definition means that there is no loop in this code either:

#!/usr/bin/perl

use strict;
use warnings;

my $i = 0;
FOO:
    print "hello world!\n";
goto FOO unless ++$i == 5;

Perl actually does define the word loop in its documentation:

   loop
       A construct that performs something repeatedly, like a roller
       coaster.

By this definition, map is a loop because it preforms its block repeatedly; however, it also defines "loop control statement" and "loop label":

   loop control statement
       Any statement within the body of a loop that can make a loop
       prematurely stop looping or skip an "iteration".  Generally you
       shouldn't try this on roller coasters.

   loop label
       A kind of key or name attached to a loop (or roller coaster) so
       that loop control statements can talk about which loop they want to
       control.

I believe it is imprecise to call map a loop because next and its kin are defined as loop control statements and they cannot control map.

This is all just playing with words though. Describing map as like-a-loop is a perfectly valid way of introducing someone to it. Even the documentation for map uses a foreach loop as part of its example:

               %hash = map { get_a_key_for($_) => $_ } @array;

           is just a funny way to write

               %hash = ();
               foreach (@array) {
                   $hash{get_a_key_for($_)} = $_;
               }

It all depends on the context though. It is useful to describe multiplication to someone as repeated addition when you are trying to get him or her to understand the concept, but you wouldn't want him or her to continue to think of it that way. You would want him or her to learn the rules of multiplication instead of always translating back to the rules of addition.

4
  • is there any (semi)-official statement necessitating next/redo/last to work in an "official" Perl loop? Or is that purely personal preference?
    – DVK
    Commented Jun 11, 2010 at 11:40
  • 2
    @DVK next, redo, and last are defined as loop control statements. This implies that anything that is a loop can be controlled by them. But the devil is in the definition here. See the extra information I am adding to the answer. Commented Jun 11, 2010 at 11:49
  • => but next, redo, and last also work on bare blocks and subroutines. neither of those is a loop.
    – Eric Strom
    Commented Jun 17, 2010 at 2:29
  • @Eric Storm, from the docs for next and friends: "Note that a block by itself is semantically identical to a loop that executes once." What makes you think they work with subroutines? If you use one of them to try to control a subroutine you get an error like Can't "next" outside a loop block. Commented Jun 17, 2010 at 3:22
11

Your question turns on the issue of classification. At least under one interpretation, asking whether map is a loop is like asking whether map is a subset of "Loop". Framed in this way, I think the answer is no. Although map and Loop have many things in common, there are important differences.

  • Loop controls: Chas. Owens makes a strong case that Perl loops are subject to loop controls like next and last, while map is not.
  • Return values: the purpose of map is its return value; with loops, not so much.

We encounter relationships like this all the time in the real world -- things that have much in common with each other, but with neither being a perfect subset of the other.

 -----------------------------------------
|Things that iterate?                     |
|                                         |
|      ------------------                 |
|     |map()             |                |
|     |                  |                |
|     |          --------|----------      |
|     |          |       |          |     |
|     |          |       |          |     |
|      ------------------           |     |
|                |                  |     |
|                |              Loop|     |
|                 ------------------      |
|                                         |
 -----------------------------------------
2
  • If I hear the word iterator, I'll be depressed for the entire day. I had enough of C++ yesterday, thankyouverymuch!
    – DVK
    Commented Jun 11, 2010 at 13:47
  • 5
    @DVK: you asked! map is indeed an iterator in most senses of the word, since it aliases list elements to $_.
    – Ether
    Commented Jun 11, 2010 at 14:46
10

map is a higher-order function. The same applies to grep. Book Higher-Order Perl explains the idea in full details.

It's sad to see that discussion moved towards implementation details, not the concept.

0
4

FM's and Dave Sherohman's answers are quite good, but let me add an additional way of looking at map.

map is a function which is guaranteed to look at every element of a structure exactly once. And it is not a control structure, as it (itself) is a pure function. In other words, the invariants that map preserves are very strong, much stronger than 'a loop'. So if you can use a map, that's great, because you then get all these invariants 'for free', while if you're using a (more general!) control structure, you'll have to establish all these invariants yourself if you want to be sure your code is right.

And that's really the beauty of a lot of these higher-order functions: you get many more invariants for free, so that you as a programmer can spend your valuable thinking time maintaining application-dependent invariants instead of worrying about low-level implementation-dependent issues.

3

map itself is generally implemented using a loop of some sort (to loop over iterators, typically), but since it is a higher-level structure, it's often not included in lists of lower-level control structures.

3

Here is a definition of map as a recurrence:

sub _map (&@) {
    my $f = shift;

    return unless @_;

    return $f->( local $_ = shift @_ ),
           _map( $f, @_ );
}

my @squares = _map { $_ ** 2 } 1..100;
4
  • 2
    But recursion is just one way to implement a loop. Not all loops are iterative. Commented Jun 11, 2010 at 11:17
  • Well, 'loop' here should really be seen as a control structure. Recursion is a mathematical term. Properly defined, map is a mathematical abstraction, not a control structure. And in any case, this particular 'loop' is tail-call recursive, and hence iterative! Commented Jun 11, 2010 at 16:06
  • It isn't iterative in Perl 5 (you need a helper function and the sub version of goto to optimize tail recursion in Perl 5). You can implement all of the control structures as functions, does this make them not control structures anymore? Is if(cond, f1, f2) somehow less of an if statement than if (cond) { f1 } else { f2 }? If map responded to the loop control statements and allowed the setting of a loop label, I would say that is a loop. Commented Jun 11, 2010 at 18:43
  • Sorry for the delay: you certainly can't implement if/else as a function, because evaluation of one of the branches can't occur. That's why they're called 'keywords' (or special forms, in Lisp). Commented Nov 4, 2010 at 3:20
2

"Loop" is more of a CS term rather than a language-specific one. You can be reasonably confident in calling something a loop if it exhibits these characteristics:

  • iterates over elements
  • does the same thing every time
  • is O(n)

map fits these pretty closely, but it's not a loop because it's a higher-level abstraction. It's okay to say it has the properties of a loop, even if it itself isn't a loop in the strictest, lowest-level sense.

8
  • A loop doesn't necessarily have to iterate over elements... Or do the same thing each time... Or be O(n) depending on what is in the loop Commented Jun 11, 2010 at 3:08
  • @Carson Myers: Can you give an example of a non-degenerate-case loop that's not iterating over anything? I can't think of any counterexample. Also, the entire point of a loop is to execute a set of instructions over and over again, so I'd also disagree with your second point. Commented Jun 11, 2010 at 3:12
  • 1
    well, I guess counter controlled loops don't really iterate over anything, and loops with branch statements won't necessarily always do the same thing. In any case, I didn't down mod you, I was just nit-picking Commented Jun 11, 2010 at 3:17
  • @John: a loop to compute the sequence involved in the Collatz conjecture starting from the number n is not O(n). Your other conditions don't say much either... if a loop contains a branch, is it "doing the same thing every time"? Are you just asserting that it's deterministic? This isn't the "formal reasoning" the OP asked for.
    – p00ya
    Commented Jun 11, 2010 at 4:11
  • @pOOya: as far as i know, the branch is part of the loop, so the loop executes the same body of instruction every time however taking different path inside this body. Commented Jun 11, 2010 at 6:05
2

I think map fits the definition of a Functor.

1

It all depends on how you look at it...

On the one hand, Perl's map can be considered a loop, if only because that's how it's implemented in (current versions of) Perl.

On the other, though, I view it as a functional map and choose to use it accordingly which, among other things, includes only making the assumption that all elements of the list will be visited, but not making any assumptions about the order in which they will be visited. Aside from the degree of functional purity this brings and giving map a reason to exist and be used instead of for, this also leaves me in good shape if some future version of Perl provides a parallelizable implementation of map. (Not that I have any expectation of that ever happening...)

1
  • +1 - I like that point of view (as well as being a realist :) )
    – DVK
    Commented Jun 11, 2010 at 11:14
1

I think of map as more akin to an operator, like multiplication. You could even think of integer multiplication as a loop of additions :). It's not a loop of course, even if it were stupidly implemented that way. I see map similarly.

1
  • What you're talking about is "reduce" concept. It was discussed in relation to map in a different asnwer to this Q.
    – DVK
    Commented Jun 11, 2010 at 16:18
0

A map in Perl is a higher order function that applies a given function to all elements of an array and returns the modified array.

Whether this is implemented using an iterative loop or by recursion or any other way is not relevant and unspecified.

So a map is not a loop, though it may be implemented using a loop.

0

Map only looks like a loop if you ignore the lvalue. You can't do this with a for loop:

print join ' ', map { $_ * $_ } (1 .. 5)
1 4 9 16 25

Not the answer you're looking for? Browse other questions tagged or ask your own question.