Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions toolz/curried/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
comp,
complement,
compose,
compose_left,
concat,
concatv,
count,
Expand Down
26 changes: 24 additions & 2 deletions toolz/functoolz.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@


__all__ = ('identity', 'apply', 'thread_first', 'thread_last', 'memoize',
'compose', 'pipe', 'complement', 'juxt', 'do', 'curry', 'flip',
'excepts')
'compose', 'compose_left', 'pipe', 'complement', 'juxt', 'do',
'curry', 'flip', 'excepts')


def identity(x):
Expand Down Expand Up @@ -537,6 +537,7 @@ def compose(*funcs):
'4'

See Also:
compose_left
pipe
"""
if not funcs:
Expand All @@ -547,6 +548,27 @@ def compose(*funcs):
return Compose(funcs)


def compose_left(*funcs):
""" Compose functions to operate in series.

Returns a function that applies other functions in sequence.

Functions are applied from left to right so that
``compose_left(f, g, h)(x, y)`` is the same as ``h(g(f(x, y)))``.

If no arguments are provided, the identity function (f(x) = x) is returned.

>>> inc = lambda i: i + 1
>>> compose_left(inc, str)(3)
'4'

See Also:
compose
pipe
"""
return compose(*reversed(funcs))


def pipe(data, *funcs):
""" Pipe a value through a sequence of functions

Expand Down
75 changes: 66 additions & 9 deletions toolz/tests/test_functoolz.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import platform

from toolz.functoolz import (thread_first, thread_last, memoize, curry,
compose, pipe, complement, do, juxt, flip, excepts, apply)
compose, compose_left, pipe, complement, do, juxt,
flip, excepts, apply)
from operator import add, mul, itemgetter
from toolz.utils import raises
from functools import partial
Expand Down Expand Up @@ -505,17 +506,54 @@ def _should_curry(self, args, kwargs, exc=None):
"""


def test_compose():
assert compose()(0) == 0
assert compose(inc)(0) == 1
assert compose(double, inc)(0) == 2
assert compose(str, iseven, inc, double)(3) == "False"
assert compose(str, add)(1, 2) == '3'
def generate_compose_test_cases():
"""
Generate test cases for parametrized tests of the compose function.
"""

def f(a, b, c=10):
def add_then_multiply(a, b, c=10):
return (a + b) * c

assert compose(str, inc, f)(1, 2, c=3) == '10'
return (
(
(), # arguments to compose()
(0,), {}, # positional and keyword args to the Composed object
0 # expected result
),
(
(inc,),
(0,), {},
1
),
(
(double, inc),
(0,), {},
2
),
(
(str, iseven, inc, double),
(3,), {},
"False"
),
(
(str, add),
(1, 2), {},
'3'
),
(
(str, inc, add_then_multiply),
(1, 2), {"c": 3},
'10'
),
)


def test_compose():
for (compose_args, args, kw, expected) in generate_compose_test_cases():
assert compose(*compose_args)(*args, **kw) == expected


def test_compose_metadata():

# Define two functions with different names
def f(a):
Expand All @@ -536,6 +574,25 @@ def g(a):
assert composed.__doc__ == 'A composition of functions'


def generate_compose_left_test_cases():
"""
Generate test cases for parametrized tests of the compose function.

These are based on, and equivalent to, those produced by
enerate_compose_test_cases().
"""
return tuple(
(tuple(reversed(compose_args)), args, kwargs, expected)
for (compose_args, args, kwargs, expected)
in generate_compose_test_cases()
)


def test_compose_left():
for (compose_left_args, args, kw, expected) in generate_compose_left_test_cases():
assert compose_left(*compose_left_args)(*args, **kw) == expected


def test_pipe():
assert pipe(1, inc) == 2
assert pipe(1, inc, inc) == 3
Expand Down