I am learning a little functional programming and looking at toolz. The differences between compose, pipe, thread_first, and thread_last seem very subtle or non-existent to me. What is the intended different use cases for these functions?
1 Answers
composevs.thread_*andpipecomposeis a essentially a function compostion (∘). It's main goal is to combine different functions into reusable blocks. Order of applications is reversed compared to order of arguments socompose(f, g, h)(x)isf(g(h(x)))(same as (f ∘ g)(x) is f(g(x))).thread_*andpipeare about using reusable blocks to create a single data flow. Execution can be deferred only with lazy operations, but blocks are fixed. Order of application is the same as order of arguments sopipe(x, f, g, h)ish(g(f(x))).composevsthread_*.composedoesn't allow for additional arguments, whilethread_*does. Without curryingcomposecan be used only with unary functions.Compared to that
thread_can be used with functions of higher arity, including commonly used higher order functions:thread_last( range(10), (map, lambda x: x + 1), (filter, lambda x: x % 2 == 0) )To the same thing with
composeyou'd need currying:pipe( range(10), lambda xs: map(lambda x: x + 1, xs), lambda xs: filter(lambda x: x % 2 == 0, xs) )or
from toolz import curried pipe( range(10), curried.map(lambda x: x + 1), curried.filter(lambda x: x % 2 == 0) )thread_firstvs.thread_last.thread_firstputs piped argument at the first position for the function.thread_lastputs piped argument at the last position for the function.For example
>>> from operator import pow >>> thread_last(3, (pow, 2)) # pow(2, 3) 8 >>> thread_first(3, (pow, 2)) # pow(3, 2) 9
In practice (ignoring some formalism) these functions are typically interchangeable, especially when combined with functools.partial / toolz.curry and some lambda expressions, but depending on the context, it is just more convenient to use one over another.
For example with built-in higher-order functions, like map or functools.reduce, thread_last is a natural choice. If you want to reuse a piece of code in multiple place, it is better to use compose(h, g, f) than adding function wrapper def fgh(x) pipe(x, f, g, h). And so on.
- 34,230
- 9
- 83
- 115