Friday, October 5, 2012

fmap(), "inverse" of Python map() function


By Vasudev Ram

fmap() is a function I created, which is a kind of inverse of the built-in Python map() function. It has probably been discovered/created by many others before (though they may have called it by different names), but I thought of it just now, based on some code I wrote recently (*), so blogging about it.

The comments in the source code below describe what fmap does. It is straightforward.

(*) That "recent code" refers to this snippet:
result = item
for processor in self._processors:
    result = processor(result)

from my blog post about the release of PipeController v0.1; that snippet basically does the same thing as fmap(), but the snippet is specific to PipeController, whereas fmap() was extracted from that, and generalized to be reusable in other programs.

fmap.py source code:

(You can also get the fmap.py code from a pastebin here, since Blogger sometimes doesn't render inline code too well (tabs show up as one space, for example.)

# fmap.py

# Author: Vasudev Ram - http://www.dancingbison.com

# fmap() is a Python function which is a kind of inverse of the 
# built-in Python map() function.
# The map() function is documented in the Python interpreter as
# follows:

"""
>>> print map.__doc__
map(function, sequence[, sequence, ...]) -> list

Return a list of the results of applying the function to the items of
the argument sequence(s).  If more than one sequence is given, the
function is called with an argument list consisting of the corresponding
item of each sequence, substituting None for missing values when not all
sequences have the same length.  If the function is None, return a list of
the items of the sequence (or a list of tuples if more than one sequence).
"""

# The fmap() function does the inverse, in a sense.
# It returns the result of applying a list of functions to a 
# given argument.
# TODO: Later extend the function to also work on a sequence of 
# arguments like map() does.

import string

def fmap(function_list, argument):
 result = argument
 for function in function_list:
  #print "calling " + function.__name__ + "(" + repr(result) + ")"
  result = function(result)
 return result

def times_two(arg):
 return arg * 2

def square(arg):
 return arg * arg

def upcase(s):
 return string.upper(s)

def delspace(s):
 return string.replace(s, ' ', '')

def main():

 print

 function_list = [ times_two, square ]
 for argument in range(5):
  fmap_result = fmap(function_list, argument)
  print "argument:", argument, ": fmap result:", fmap_result

 print

 function_list = [ upcase, delspace ]
 for argument in [ "the quick brown fox", "the lazy dog" ]:
  fmap_result = fmap(function_list, argument)
  print "argument:", argument, ": fmap result:", fmap_result

if __name__ == "__main__":
 main()

# EOF: fmap.py

Output of running a test program for fmap():
$> python fmap.py

argument: 0 : fmap result: 0
argument: 1 : fmap result: 4
argument: 2 : fmap result: 16
argument: 3 : fmap result: 36
argument: 4 : fmap result: 64

argument: the quick brown fox : fmap result: THEQUICKBROWNFOX
argument: the lazy dog : fmap result: THELAZYDOG

UPDATE:

Here are a couple of other interesting posts about functional programming in Python, which I found by doing a Google search for relevant terms:

Dhananjay Nene's two posts on the subject:

Functional Programming With Python - Part 1

Functional Programming With Python – Part 2 - Useful Python Constructs

A StackOverflow thread: Why program functionally in Python?


- Vasudev Ram - Dancing Bison Enterprises

4 comments:

Dhananjay Nene said...

I would've used the word compose.

eg. g(f(x) is equivalent to (g.f)(x) where g.f is the composition of g and f

And yes, its quite useful. One of my top three peeves with python is that it does not have a good composition operator (like the . above).

Vasudev Ram said...

Yes, I maybe could have thought of using the name compose instead of fmap, particularly since I got the idea for fmap() from my PipeController v0.1 code (see today's update in this post), and it is basically just a different way of doing functional composition (under program control, in the for loop, so to speak). But didn't think of that when I named fmap().

What languages have you used that can compose functions in the mathematical sense, like g.f? Is Scala on of them? I mean, any languages that can compose two or more functions, _before_ applying the result to an argument?

Something like (in pseudocode):

h = g.f # compose, once only

then: # apply, any number of times

h(arg1)
h(arg2)
...

In my case that is not done. The "composition" is done at run time while applying each function to the result of the previous function call.

Vasudev Ram said...


And I'm glad you found it useful.

Vasudev Ram said...


Dhananjay Nene made an interesting comment again on this post about fmap(), but it hasn't shown up in my Blogger dashboard yet (after 2 days or so), so I'm posting the comment on his behalf (after he emailed it to me), with his permission:

----------------------------------

I did respond, but again not sure if it made through blogger comments, anyways reproducing the comment below.
- Dhananjay
----------------------------
Scala does allow composition using a compose / andThen method (they apply the functions in different orders).

scala> def double(n: Int) = n * 2
double: (n: Int)Int

scala> def increment(n: Int) = n + 1
increment: (n: Int)Int

scala> val doubleThenIncrement = double _ andThen increment _
doubleThenIncrement: Int => Int =

scala> val incrementThenDouble = increment _ compose double _
incrementThenDouble: Int => Int =

scala> doubleThenIncrement(5)
res0: Int = 11

scala> incrementThenDouble(5)
res1: Int = 11

I am not really sure if the distinction between composition at runtime vs parse time is particularly relevant (although they are marginally tactically different).

To show this in python, An implementation where composition of the functions happens once, could be as follows. Though if you unwrap the nested function you would effectively get composition at runtime.

>>> def double(n):
... return n * 2
...
>>> def increment(n):
... return n + 1
...
>>> def square(n):
... return n * n
...
>>> def compose(*funcs):
... def composed(arg):
... for func in funcs :
... arg = func(arg)
... return arg
... return composed
...
>>> compose(double,increment,square)(5)
121

----------------------------------