HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Recursive higher-order function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
recursiveorderfunctionhigher

Problem

I have implemented a through function for myself in a project.
Since I am still quite new in python and want to learn it correctly, I would be happy about any feedback including style, variable naming...

A first question is how to name the function. Because of Mathematica's documentation I called it through. Is there a more intuitive name?

def through(list_of_functions, value, recurse_level=np.infty):
    """Calls each function in a list

    This function is passing the value as argument to each function
    in a list.
    For a one dimensional list consisting of
    only functions it is equivalent to::

    [f(value) for f in list]

    If an element is not callable and not iterable, it is not called.
    An iterable is either directly appended or will be recursed through
    depending on recurse_level.

    Args:
        list_of_functions (list):
        value (any type):
        recurse_level (int):

    Returns:
        list:
    """
    new_list = []
    for possible_function in list_of_functions:
        try:
            new_list.append(possible_function(value))
        except TypeError: # item not callable; could be iterable
            # If it is iterable and recurse_level is not negative
            # recurse through elements
            if recurse_level >= 0:
                try:
                    new_list.append(through(possible_function,
                                            value,
                                            recurse_level))
                    recurse_level = recurse_level - 1
                except TypeError: # not an iterable; append without calling
                    new_list.append(possible_function)
            else:
                new_list.append(possible_function)
    return new_list


An example:

In: test = [[1, 2], [3, (lambda x : x ** 2)], (lambda x : x ** 3)]
In: through(test, 4, recurse_level=1)
Out: [[1, 2], [3, 16], 64]
In: through(test, 4, recurse_level=0)
Out: [[1, 2], [3, >], 64]

Solution

Naming

  • In Clojure, there's a similar function named juxt. Not saying that's a better name by any means, just mentioning what it happens to be called in one other place. Upon further reflection, what you are doing is really just a recursive form of function application. So a suitable name might reflect that. Perhaps apply_rec?



  • Use the shortest name that conveys sufficient meaning. For example, list_of_functions could be shortened to functions (or even just fs). possible_function could be shortened to f. (Single-character names are not good names in general, but for generic entities that have a well-known mathematical notation, they are a good fit.)



Use Better Checks for Callable / Iterable

You check if something's callable by trying to call it and catching a TypeError exception. However, if you call a function, and that function happens to through a TypeError, then your code will incorrectly assume it's not a function. You can use the callable function instead, to determine whether or not something is callable.

A similar situation exists in the way you check for iterables. In this case, you can use the iter function as a fail-fast way to check if something's iterable.

Consider Using a List Comprehension

Reading your code, I thought it was a bit ironic that you used a list comprehension in the docstring to describe the functions behavior, but not in the code itself. Granted, due to the recursive nature, it's not quite as straight-forward. But with just a little refactoring, it should be possible. Here's how I would do it:

def apply_rec(f, x):
  """
  If f is a function, applies f to x and returns the result.
  Otherwise, if f is iterable, applies every element in f to x,
  returning a list of the results.
  If f is neither callable nor iterable, returns f.
  """

  if callable(f):
    return f(x)
  else:
    try:
      fs = iter(f)
    except:
      # Not iterable => return f
      return f

    return [apply_rec(f, x) for f in fs]


I've omitted the recursion limit, but that should be trivial to add.

Code Snippets

def apply_rec(f, x):
  """
  If f is a function, applies f to x and returns the result.
  Otherwise, if f is iterable, applies every element in f to x,
  returning a list of the results.
  If f is neither callable nor iterable, returns f.
  """

  if callable(f):
    return f(x)
  else:
    try:
      fs = iter(f)
    except:
      # Not iterable => return f
      return f

    return [apply_rec(f, x) for f in fs]

Context

StackExchange Code Review Q#147600, answer score: 5

Revisions (0)

No revisions yet.