Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

understanding toolz use cases

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?

like image 585
Malik A. Rumi Avatar asked May 24 '17 05:05

Malik A. Rumi


People also ask

What is the purpose of targeted use cases?

Use cases describe the functional requirements of a system from the end user's perspective, creating a goal-focused sequence of events that is easy for users and developers to follow.

What is the difference between a scenario and a use case?

Difference between scenario and use cases- A scenario is a situation in which more than one actor is involved in performing a specific task. A use case is the description of the scenario which involves describing the functionality of the scenario.

How do you identify a use case in a system?

The most comprehensive technique for identifying use cases is the event decomposition technique. The event decomposition technique begins by identifying all the business events that will cause the information system to respond, and each event leads to a use case.


1 Answers

  • compose vs. thread_* and pipe

    compose is 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 so compose(f, g, h)(x) is f(g(h(x))) (same as (f ∘ g)(x) is f(g(x))).

    thread_* and pipe are 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 so pipe(x, f, g, h) is h(g(f(x))).

  • compose vs thread_*.

    compose doesn't allow for additional arguments, while thread_* does. Without currying compose can 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 compose you'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_first vs. thread_last.

    thread_first puts piped argument at the first position for the function.

    thread_last puts 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.

like image 58
Alper t. Turker Avatar answered Oct 23 '22 22:10

Alper t. Turker