ghenry has asked for the wisdom of the Perl Monks concerning the following question:
Dear all,
I have a simple subroutine. I would like to try and swap out the foreach loop and use map instead:
sub links { my ($url, @list) = @_; my @return; foreach (@list) { push @return, "$url/$_\n"; } return @return; }
Is this right way to do it?
sub links { my ($url, @list) = @_; my @return = map ("$url/$_\n") @list; return @return; }
I have never used map before and I have read the map function page on http://perldoc.perl.org/functions/map.html, but I don't seem to quite understand.
Thanks.
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: Turning foreach into map?
by tlm (Prior) on Apr 05, 2005 at 00:19 UTC | |
Once you start, you won't stop! You'll wonder how you could program without it.
As a first approximation, you can imagine map as being defined something like this: This function my_map takes a code ref as its first argument, and returns the array consisting of applying that code ref to each one of the remaining arguments. Very useful! When programming we often want all the elements of some array, but transformed or operated on in some way. my_map (just like map) abstracts the essentials of this procedure, so that the only variables left are the actual input array and the operation that you want performed on each element. You would use my_map like this: or if you don't want to bother defining a sub just to feed it to my_map, you can give it a code ref written just for the ocassion: Same thing. In fact, if you have arranged your definitions so that the compiler processes it before it processes the first call to it, you can toss the parens and write which is beginning to resemble the way one uses Perl's map. Since this is such a handy thing to do, for its map Perl provides some "syntactic sugar" to simplify the writing of such expressions, so that for example you don't need to bother writing "sub" before the code ref. Also, in Perl's map, the code ref doesn't get its argument passed through its @_ array like is the case with my_map's $sub; instead map's the code ref picks up its argument from $_. In other words, if we wanted my_map to mimic Perl's map more closely, we could define it like this: The only difference between this and the previous definition of my_map is in the first and second lines of the loop: 1) the loop variable is no longer $element, but $_; and 2) $sub is now called without arguments, since it will read its arguments from $_. Now we can write and that's starting to look a lot like the standard
Now, watch this. If you defined a function that operated on $_, e.g. you could just feed it to Perl's map as its first argument: and you could also feed it to (the second version of) my_map Notice that now the only difference that remains between the syntax of the two calls is that with my_map you need to explicitly take the reference to the sub you are passing as the first argument, hence the extra backslash. Can we get rid of this difference too? Yes, by using prototypes. I for one never use prototypes, so I'm not up on all their ins and outs, but for the sake of this illustration (Note, aside from the prototype, there is no significant difference between this definition of my_map and the previous one; I just condensed it a little bit.) Now you can write, OK, this last version of my_map does a limited impersonation of the "expression form" of map. A slight variant could be used to implement an impersonation of the "block form" of map; the only difference is in the prototype, but just to be clear, here it is in full: With this final version of my_map you can write: Note that even though we are using an anonymous code ref, the sub keyword is gone. This is syntactically identical to the "block form" of map. Alas, my_map is not as versatile as map. It can't do some things that map can do, like this: and it can't mimic both the "expression form" and the "block form" of map. But I think that by studying my_map you'll get a pretty good feel for what map is doing. That's the important thing; the other stuff with tweaking the definition of my_map so it's usage better resembles that of map is a bit of a distraction. The important thing is to understand that map takes a function as its first argument, and returns the array obtained from applying that function to the rest of its arguments. OK, since I've made this far, I might as well tell you, if not the whole story, at least all that I know about map, and the only thing that's left is that both map and my_map have the potential to change their inputs. If, for example, you did this (using the last version of my_map): ...the input array @ints has been modified by my_map. Same thing with Perl's map, so watch out. Last thing: you could play the same game with grep:
the lowliest monk | [reply] [d/l] [select] |
by ghenry (Vicar) on Apr 05, 2005 at 07:08 UTC | |
Wow! This will take me a while to properly digest, but my first question is, why is:
now:
I have seen loads of people do this. Even merlyn now suggests it in another thread that I can't remember, when it's his book I learnt it from ;-)
Walking the road to enlightenment... I found a penguin and a camel on the way..... Fancy a yourname@perl.me.uk? Just ask!!! | [reply] [d/l] [select] |
by polettix (Vicar) on Apr 05, 2005 at 09:55 UTC | |
and have When you use: you're creating copies of the input arguments, so when you modify them you don't have any side-effect outside the function. In your code, these copies are inefficient and not needed, because the code is quite simple and you don't perform in-place modifications. So, you simply grab the first parameter, and use the resulting @_ instead of @list, saving space and time: I also find this construct useful when porting functions into classes. When you transform a plain function in a class/object method, the first parameter that's passed is the class name/reference to object, so if you have a function: you can transform it at once: in a much cut-and-paste fashion. But do check the code! Flavio (perl -e "print(scalar(reverse('ti.xittelop@oivalf')))") Don't fool yourself. | [reply] [d/l] [select] |
by tlm (Prior) on Apr 05, 2005 at 09:30 UTC | |
They're both correct; it all depends on what you want to do. In the example I gave, I was re-implementing Perl's built-in map, not your links function. What my code does is to pluck out the first argument from the arguments list, and later process the remaining arguments directly from the arguments list. the lowliest monk | [reply] [d/l] [select] |
by ghenry (Vicar) on Apr 05, 2005 at 09:33 UTC | |
OK, I've read all this now, and I have to be honest, I haven't got as far as references yet, only as far as reading perlreftut, but not actually coded anything. I'll get back to you ;-)
Walking the road to enlightenment... I found a penguin and a camel on the way..... Fancy a yourname@perl.me.uk? Just ask!!! | [reply] |
by tlm (Prior) on Apr 05, 2005 at 12:10 UTC | |
References are far more important than map; if I were you I'd put map aside for a bit and focus on becoming very comfortable with refs. the lowliest monk | [reply] [d/l] [select] |
by ghenry (Vicar) on Apr 05, 2005 at 13:17 UTC | |
by tlm (Prior) on Apr 05, 2005 at 13:35 UTC | |
Re: Turning foreach into map?
by brian_d_foy (Abbot) on Apr 04, 2005 at 21:14 UTC | |
Once you decide to use map, you probably don't need the subroutine anymore: you're not hiding that much code. Previously, you had a subroutine call like this:
To replace links() with an inline map() is only a few characters longer, and it tells the programmer exactly what is happening.
However, if link() does a lot of other stuff you aren't showing us, then you may still want to use it to hide things. As a bonus, the URI module can turn relative URLs into absolute ones for you. :)
-- brian d foy <brian@stonehenge.com> | [reply] [d/l] [select] |
by ghenry (Vicar) on Apr 04, 2005 at 22:21 UTC | |
As usual, I have learned a lot from your replies. Thanks. dragonchild, I never got a chance to test it, only post this question, as I had my son on my lap ;-) He's now in bed, so I can reply. The reason I posted this, is because I am going back to my old programs after learning more, and trying to make them clean and have less code. The program in question was written for my wife, as she always asks me how to resize images and upload them to ebay. I wrote this, called ebaypics, which is then saved in ~/.gnome2/nautilus-scripts she then right clicks in the folder she has the photos in, and that's it.
Needs a bit of work ;-)
Walking the road to enlightenment... I found a penguin and a camel on the way..... Fancy a yourname@perl.me.uk? Just ask!!! | [reply] [d/l] [select] |
Re: Turning foreach into map?
by Roy Johnson (Monsignor) on Apr 04, 2005 at 20:40 UTC | |
Watch out if your expression is surrounded by parentheses, though, because map will think they enclose all of its arguments. You either need two sets of parens (one around your expression, and one around all of map's args), or you don't need any. In your case, you don't need any.
Caution: Contents may have been coded under pressure. | [reply] [d/l] |
by tlm (Prior) on Apr 04, 2005 at 21:18 UTC | |
You can also get away with the single set of parens if you disambiguate it with +: I find myself using this trick all over the place, not only with map and grep but with, e.g. print: Without the + above, the last expression would result in the printing of only the value of the expression in the parens. (Although, if warnings are on, perl will say something about interpreting print (...) as a function.) the lowliest monk | [reply] [d/l] [select] |
Re: Turning foreach into map?
by jZed (Prior) on Apr 04, 2005 at 20:23 UTC | |
update note the curly braces instead of parens Foreach can be faster than map, so depending on how many times that sub gets called, you may not want to make the change. | [reply] |
by jhourcle (Prior) on Apr 05, 2005 at 01:39 UTC | |
Foreach is faster than map if you're modifying in place. If you're using foreach to push into an array, like what's happening here, then map is going to be faster. (which is why there's the recommendation not to use map if you're not doing anything with the list generated -- you might as well have just done foreach). Update: benchmarks to show the speed differences between map, map reassigning to the original list, map in void context, for, and foreach. (if 'recent' is before 5.8.6, I don't think the optimizations worked)
Read more... (1129 Bytes) | [reply] [d/l] [select] |
by doom (Deacon) on Apr 05, 2005 at 22:32 UTC | |
Foreach is faster than map if you're modifying in place. If you're using foreach to push into an array, like what's happening here, then map is going to be faster. (which is why there's the recommendation not to use map if you're not doing anything with the list generated -- you might as well have just done foreach).This is no longer the case for recent versions of perl. Now map has been optimized in void context, so if you don't do anything with the return values, it doesn't bother generating them. In theory, the only difference between map and for/foreach now is a matter of taste/style. (I say "in theory", because I haven't benchmarked it, and sometimes the perl porters *think* they've done, isn't quite what they've done.)
| [reply] |
by Roy Johnson (Monsignor) on Apr 05, 2005 at 22:59 UTC | |
by Anonymous Monk on Apr 06, 2005 at 08:48 UTC | |
by dragonchild (Archbishop) on Apr 04, 2005 at 20:30 UTC | |
Proof? Caveats? Situations? You can't just throw that statement out there and expect it to stand on its merits ... | [reply] |
by ikegami (Patriarch) on Apr 04, 2005 at 20:36 UTC | |
At least for small lists, t_for and t_map are the same, and t_map2 is much faster. Passing by ref vs passing the array doesn't matter if you use @_ directly. | [reply] [d/l] |
by ysth (Canon) on Apr 04, 2005 at 20:47 UTC | |
by tilly (Archbishop) on Apr 05, 2005 at 00:14 UTC | |
| |
by demerphq (Chancellor) on Apr 04, 2005 at 20:58 UTC | |
by ikegami (Patriarch) on Apr 04, 2005 at 22:07 UTC | |
by Anonymous Monk on Apr 05, 2005 at 09:07 UTC | |
Re: Turning foreach into map?
by dragonchild (Archbishop) on Apr 04, 2005 at 20:24 UTC | |
FYI, I would have written that as so:
| [reply] [d/l] |
Re: Turning foreach into map?
by thor (Priest) on Apr 04, 2005 at 23:16 UTC | |
I would like to try and swap out the foreach loop and use map instead.Do you have a reason other than "map is cooler than foreach" for doing this? Keep in mind, learning is a valid reason to do something...just not in something that someone is paying you for. thor
Feel the white light, the light within | [reply] |
by ghenry (Vicar) on Apr 04, 2005 at 23:20 UTC | |
Yes. I am learning and wanted to try a different way of doing a foreach. That's all.
Walking the road to enlightenment... I found a penguin and a camel on the way..... Fancy a yourname@perl.me.uk? Just ask!!! | [reply] |
A hint about using map
by doom (Deacon) on Apr 05, 2005 at 23:17 UTC | |
At some point you're guaranteed to try something like this, but it won't work: This is because s/// returns the *count* of the changes, rather than the changed string (most people agree this is a DWIM violation: this is an area where Larry blew it). What actually works is something like this:
There's still another gotcha here though, in that if you look at the original @list after doing this, you'll find that it was transformed in exactly the same way as the new list. Each item in the @list array is aliased to $_, so it can be changed by running map on it. (Of course, you knew this. But you'll still get bitten by it on occasion.) Weirdly enough, the m// operator doesn't have the same problem as s/// does: m// does pretty much what you'd expect in list context:
Trivia: perl is well known for having ripped off features form shell and awk, but map was lifted from lisp. | [reply] [d/l] [select] |
by merlyn (Sage) on Apr 05, 2005 at 23:29 UTC | |
most people agree this is a DWIM violation: this is an area where Larry blew itIt may not be a DWIM for you, but it's a DWLM (do what Larry means), and that's the only meaning that matters. In fact, I don't think it would look right for it to return the changed string. I like that I can use it as: So, it certainly DWIMs for me. Apparently, I'm not one of "most" in your sentence. However, a lot of people have learned from my books, so I suspect the number who understand it the the way I do would be a large portion of your minority. -- Randal L. Schwartz, Perl hacker
| [reply] [d/l] |
by doom (Deacon) on Apr 05, 2005 at 23:37 UTC | |
Right, that's a point, but the way I look at it it would still work even if s/// was returning the changed string (except, of course, when the returned string was something like zero or an empty string).most people agree this is a DWIM violation: this is an area where Larry blew itIt may not be a DWIM for you, but it's a DWLM (do what Larry means), and that's the only meaning that matters. In fact, I don't think it would look right for it to return the changed string. I like that I can use it as:So, it certainly DWIMs for me. So the actual behavior does a slightly better job of DWIM in one case, and a worse job in another case... (and now that I think of it, why not return the string in list context and the count in scalar. Wouldn't that cover both bases?). Update: actually, there's another problem I haven't thought about -- if you're doing a map{ s///; $_ } you've got the string passed through map even if there's no match. If you had s/// return the string *only* if it were changed, then you could do an if(s///) with it (usually) but you'd probably end up a sticking a $_ on the end of your map blocks anyway, just to get the string passed through in the no match condition. s/where Larry blew it/where Larry's brilliance is more difficult to perceive/ | [reply] [d/l] |
by Anonymous Monk on Apr 06, 2005 at 08:54 UTC | |
by doom (Deacon) on Apr 08, 2005 at 20:31 UTC | |
by jdporter (Paladin) on Apr 06, 2005 at 03:34 UTC | |
One of the things you always want to do with map is to use it to do a transformation on a list of strings.Huh? Here I'm slinging just references and numbers. And using the map to do a greppy thing at the same time. | [reply] [d/l] |