0

Lets say we have a bunch of functions like these:

def init(value):
    return {'number': value}


def set_type(value):
    if isinstance(value['number'],  float):
        value['type'] = 'float'
    else:
        value['type'] = 'integer'
    return value


def set_is_positive(value):
    if value['number'] < 0:
        value['is_positive'] = False
    else:
        value['is_positive'] = True
    return value

and compose function like this:

def compose(*funcs):
    def compose_2_funcs(f1, f2):
        return lambda arg: f1(f2(arg))

    return reduce(compose_2_funcs, funcs)

The result of each function is passed as the argument of the next. For example composed = compose(f, g, h); result = composed(arg) is the same as result = f(g(h(arg))). This allows us to have:

description_builder = compose(set_type, set_is_positive, init)
description_builder(5)
# Result: {'number': 5, 'type': 'integer', 'is_positive': True}

This is nice and flexible and I like to use it. But one thing bothers me. It's looks like a bit counter-intuitive that functions are specified in reversed order (especially for those, who don't know what is "function composition")

So I can define a function like this:

def compose_reversed(*funcs):
    def composed(arg):
        result = arg
        for f in funcs:
            result = f(result)
        return result

    return composed

It's even more readable(IMHO) and allows to specify functions in order they are called, so description_builder = compose_reversed(init, set_is_positive, set_type).

Finally, here are my questions :)

  1. Does this thing has a special name? Proposed in comments: pipe, compose_right (along with compose_left).
  2. What are pros and cons comparing to classic function composition?

p.s. I believe this question is applicable for any language with higher-order functions, not only python.

Fly
  • 173
  • 1
  • 1
  • 7
  • 1
    See http://stackoverflow.com/questions/28252585/functional-pipes-in-python-like-from-dplyr – kennytm Apr 10 '17 at 12:49
  • 1
    `f ∘ g` the ring operator is evaluated right to left, see [function composition](https://en.wikipedia.org/wiki/Function_composition). If you were to write this out in full you get `set_type(set_is_positive(init(x)))`, so `set_type ∘ set_is_positive ∘ init` does not seem out of order. – AChampion Apr 10 '17 at 12:56
  • 1
    I usually see `compose_left` for `compose(set_type, set_is_positive, init)` and `compose_right` for `compose(init, set_is_positive, set_type)`. – Brian Rodriguez Apr 10 '17 at 12:57
  • You can also reverse the order of compose by changing your compose2 function to: `return lambda arg: f2(f1(arg))` – Brian Rodriguez Apr 10 '17 at 13:04
  • @kennytm thanks, how can I forget about pipes. @AChampion, yeah, I know. That is exactly what I wrote after `compose` declaration :) The thing is that I'm ok with this order, but I often hear complaints from other developers. @Brian, thanks. I know how to reverse (we have `reversed` function, right? ;). That's not the subject of the question. `compose_right` seems to be fine too. Updated Q1. Q2 still does not have answers. – Fly Apr 10 '17 at 13:27

1 Answers1

0

So, it seems that we don't have more opinions on this.

Just to summarize:

  1. proposed names are pipe and compose_right (along with compose_left)
  2. Not to much here. It's looks like matter of personal tastes and particular case. compose_left is more like function composition in mathematics, while compose_right may be considered more readable since functions are listed in the same order as applied.

Marking this as answer, until somebody wants to make more detailed.

Fly
  • 173
  • 1
  • 1
  • 7