0

I've been a procedural programmer for a while now, and just trying to switch my mindset to use functional programming (in Python 3 for now). So, instead of writing a for-each loop, I'm trying to grasp the interaction between map and list(map(..))

Lets say I have a simple for-in loop which does some resource heavy computation (which I'll replace with print here for the sake of simplicity):

arr = [1,2,3,4]    
for x in arr:
    print(x)

Now, when I try to do the following

map(lambda x: print(x), arr)

nothing happens, UNTIL, i wrap this in a list and it does my super heavy print function:

list(map(lambda x: print(x), arr))

Why? What am I missing? I understand map returns an iterator which is supposed to save memory instead of just holding the entire list right away. But when is my super heavy print function going to be triggered then?

Ognjen Mišić
  • 1,219
  • 17
  • 37
  • Okay, I understand those concepts, they're not foreign to me. My question is, why doesn't the function work when I'm not applying the `list` wrapper? Because I have to trigger it through the iterator? – Ognjen Mišić Feb 26 '18 at 22:10
  • 2
    Yes @OgnjenMišić. `map` is what's commonly know as being _lazy_. It won't do any work (i.e. call you function to compute any values) unless it _has to_. You can force map to compute a single value using `next`. When you cast the `map` iterator to be a `list` however, you're _forcing_ `map` to compute all of its values and thus call your function. – Christian Dean Feb 26 '18 at 22:13
  • 2
    Because it does not return you a list of values. It returns the iterator itself. Then you call `next(some_iterator)` on it to consume the next value, one by one. Until all values are consumed. Applying `list` to it will make it for all the values and returns you a list with all the iterator values. Or you can use a for loop – progmatico Feb 26 '18 at 22:16
  • Okay, tried it out, thanks guys, it's much more clear now. @ChristianDean post your response so I can accept :) – Ognjen Mišić Feb 26 '18 at 22:17
  • And to be frank, I was expecting it to work like the JS version of map, but then again now that one also makes sense. – Ognjen Mišić Feb 26 '18 at 22:18

2 Answers2

5

Why? What am I missing? I understand map returns an iterator which is supposed to save memory instead of just holding the entire list right away. But when is my super heavy print function going to be triggered then?

The map function is what's commonly known in programmer terminology as lazy. It won't do any work unless it has to. This is more broadly known as in functional programming as lazy evaluation. Rather than immediately compute any values, the map function instead returns what's known as an iterator. By doing this, it's delegating the job of compute the values it was given back to you.

A single value can be computed by the iterator using next:

>>> arr = [1, 2, 3]
>>> it = map(lambda x: print(x), arr)
>>> next(it)
1
>>> 

However, when you casted the map iterator to be a list, you were forcing map to compute all of its values and thus call your function:

>>> it = map(lambda x: print(x), arr)
>>> list(it)
1
2
3
4
[None, None, None, None]
>>> 
Christian Dean
  • 22,138
  • 7
  • 54
  • 87
3

As you said, you have to switch your mindset to use functional programming. One of the key concepts of functional programming is lazy evaluation, which is the default policy in languages such as Haskell.

The purpose of this policy is to save both time and memory, by calculating something only when it is needed. In that sense Python generators are also close to the functional paradigm.

If you want to execute it as soon as possible, you shouldn't write it in a functional style and fix it with list() if you don't care about the results. Using a loop is totally ok.

nyr1o
  • 966
  • 1
  • 9
  • 23
  • Nice answer Louis! This sums up the idea and purpose behind `map` and lazy evaluation nicely. +1 – Christian Dean Feb 26 '18 at 22:26
  • Thanks for the response. I know that using a loop is fine, but then when does one write functional and when procedural? Lets say I have some nested `for` loops, is it fine to just inline them with `list` wrapper and execute one list-map-lambda one after another or to still stick with nesting? – Ognjen Mišić Feb 26 '18 at 22:28
  • 1
    It real depends @OgnjenMišić. There both valid programming paradigms that each have pros and cons. In the case of your specific example, I'd opt to use nested `for` loops rather than nested `map` calls as it'd generally be faster. Also look into [comprehensions](http://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/) and see the question [_"OOP vs Functional Programming vs Procedural "_](https://stackoverflow.com/questions/552336/oop-vs-functional-programming-vs-procedural) – Christian Dean Feb 26 '18 at 22:33
  • 1
    @OgnjenMišić in general, list-comprehensions are preferred over `map` + `lambda` and `filter` + `lambda`, note, list/generator/set/dict comprehensions are more efficient when you want to map *and* filter at the same time. But in Python, you should only go with these constructs when readable, and don't use side-effects inside of them (... like `print`...) – juanpa.arrivillaga Feb 26 '18 at 22:36