<jupyter_start><jupyter_text>Functional Programming in Python> Do you know Python and would like to learn more about functional programming? Are you wondering what the Scala, F, and Haskell developers are talking about? Let's do some functional programming in a language that you already know!This tutorial that will get you up to speed with functional programming in Python using the [Expression](https://github.com/dbrattli/Expression) functional programming library. Functional ProgrammingFunctional programming is about programming with functions, or *expressions*. An expression is a combination of functions and values that evaluates to a value.Functional programming can sometimes be seen as difficult, scary or even intimidating. Talks about functional programming are often about Applicatives, Functors, and you might have heard about Category Theory or the infamous Monad. But you don't need to know about category theory and Monads to do functional programming.Note that object-oriented programming can in the same way be just as scary with words such as Inheritance, Interfaces, Abstract Base Classes, Covariance, Contravariance and Liskovs substitution principle.Lets look at how some object oriented programming patterns maps to functional programming:*(ref: https://fsharpforfunandprofit.com/fppatterns/)*If there is one takeaway from reading this, it is that functional programming is about expressions (functions and values) that evaluates to a value. This is stuff you most likely already know, and it don't have to be more difficult than that. PythonPython is a multi-paradigm language:- **Imperative**: `assert a == 10`- **Object-oriented**: `class B: ...`- **Procedural**: `print("test")`- ... and **Functional**: `xs = map(lambda x: x*2, [1, 2, 3])`So Python is not a functional-first programming language, but it has some functional programming features that we will explore in this tutorial. FunctionsA function `f : A → B` takes some input `A` and produces some output `B`:<jupyter_code>def add(a, b):
    return a + b
add(10, 20)<jupyter_output><empty_output><jupyter_text>Lambda functionsUsing lambda we can create anonymous functions that we can assing tovariables:<jupyter_code>add = lambda a, b: a + b
add(10, 20)<jupyter_output><empty_output><jupyter_text>Pure functionsAnother aspect of functional programming is the concept of pure functions. A pure function is a function where the same input always gives the same output . Pure functions are  important for several reasons:- Pure functions are deterministic- Pure function can be unit-tested- Pure functions are thread-safe- Pure functions can be memoized- Pure functions lowers the cognitive load. I.e they can be reasoned about.- Pure functions have no side-effectsWe will explore this further when we talk about "Effects and Side-effects". Higher-Order FunctionsA function that takes another function as an argument, or returns afunction as a result, is called a **higher-order** function. In order topass functions around, they need to be what’s called **first-class**. Afirst-class function can be used the same way as any other Python value:- Assign a function or a method to a variable or object property.- Pass functions as arguments to other functions or methods.- Return functions as the result from other functions.- Create functions dynamically at runtime.  Assign a function to a variable<jupyter_code>multiply = lambda a, b: a * b
def add(a, b):
    return a + b

adding = add<jupyter_output><empty_output><jupyter_text>A function returning a function<jupyter_code>def add_n(n):
    def partial(x):
        return x+n
    return partial
lambda n: lambda x: x+n<jupyter_output><empty_output><jupyter_text>A function that takes a function as an argument<jupyter_code>def square(x, multiply):
    return multiply(x, x)<jupyter_output><empty_output><jupyter_text>Create function dynamically at runtime*WARNING: you should normally never use eval because of security risks relatedto code injection*<jupyter_code>add_10 = eval("lambda x: x+10")
add_10(20)<jupyter_output><empty_output><jupyter_text>Currying and Partial ApplicationA function that takes two arguments is basically exactly the same as a function that takes one argument and returns a function that takes the second argument.Some languages like F supports automatic currying of functions. Python does not support automatic currying of functions, but we can define functions that return functions.Here is a function that takes two arguments:<jupyter_code>add = lambda a, b: a + b
add(10, 20)<jupyter_output><empty_output><jupyter_text>We can write this function as a function that takes one argument and returns afunction that takes the second argument:<jupyter_code>add = lambda a: lambda b: a + b
add(10)(20)<jupyter_output><empty_output><jupyter_text>We do not use currying in the core part of Expression, but the library itself supports the concept using the curried decorator:<jupyter_code>from expression.core import curried

@curried
def add(a: int, b: int) -> int:
    return a + b
assert add(3, 4) == 7
assert add()(3, 4) == 7
assert add(3)(4) == 7
assert add()(3)(4) == 7
assert add()(3)()(4) == 7
assert add()()(3)()()(4) == 7<jupyter_output><empty_output><jupyter_text>This enables partial application where a number of arguments except the last is given to a function. Partial application is used extensively in libraries such as Expression, RxPY and aioreactive.<jupyter_code>from expression.core import pipe
from expression.collections import seq

xs = seq.of(range(10))

mapping = seq.map(lambda x: x * 10)
filter = seq.filter(lambda x: x > 30)

pipe(xs,
    mapping,
    filter,
    list,
)<jupyter_output><empty_output><jupyter_text>PipeliningMaking pipelines of operations is well known from the Unix terminal and shell programming.```bashcommand_1 | command_2 | command_3 | .... | command_N```F uses the `|>` operator for piping. The definition of the pipe operator is surprisingly simple:```fsharplet (|>) x f = f x```The main idea of pipelining is simply to bring the argument to the left side of the function (instead of the right). The main reason for this is that we are teached to read from left to right. Thus the traditional way of nesting functions is not very easy to read:```pythonfunctools.reduce(lambda s, x: s + x, filter(lambda x: x > 100, map(lambda x: x * 10, xs)), 0)```Python do not have a pipe operators and overloading existing operators e.g `|` is not recommended. Expression provides a [pipe](https://github.com/dbrattli/Expression/blob/main/expression/core/pipe.py) function that we can use instead:```pythonpipe(    xs,    seq.map(lambda x: x * 10),    seq.filter(lambda x: x > 100),    seq.fold(lambda s, x: s + x, 0),)```The key thing to note is that for pipelining to work, then every function can only take a single argument. So how can we provide both e.g a mapper and an iterable for the `seq.map` function? The answer is to use currying or higher-order functions. Remember that a function that takes two arguments is the same as a function that takes the first argument and returns a function that takes the second argument. Here is how the `map` function in the sequence module is defined:```pythondef map(mapper: Callable[[TSource], TResult]) -> Callable[[Iterable[TSource]], Iterable[TResult]]:    def _map(source: Iterable[TSource]) -> Iterable[TResult]:        return (mapper(x) for x in source)    return _map```This allows us to do partial application of functions and have the last function take a single argument. The `pipe` operator in Expression is defined as follows:```pythondef pipe(value: Any, *fns: Callable[[Any], Any]) -> Any:    """Functional pipe (`|>`)    Allows the use of function argument on the left side of the function.    Example:        >>> pipe(x, fn) == fn(x)   Same as x |> fn        >>> pipe(x, fn, gn) == gn(fn(x))   Same as x |> fn |> gn        ...    """    return compose(*fns)(value)```We see that piping is a one-liner that uses a composition operator. Let's look more into function composition. CompositionIn programming we are building programs by combining smaller pieces of code into larger ones. This is very similar to building with lego bricks. Combining many small lego bricks into larger constructions, is more flexible than having a few fixed largesize pieces to start with.> *Composition is the essence of programming - <NAME>*```fsharp// Flet compose (f: 'a -> 'b) (g: 'b -> 'c) : 'a -> 'c =    f >> g``````python Pythonlet compose(f: Callable[[A], B], g: Callable[[B], C]) -> Callable[[A], C]:    lambda x: g(f(x))```<jupyter_code>def f(x):
    return x+1

def g(x):
    return x*2

# Procedural / imperative composition. The function `h` becomes hard-coded to `f` ang `g`
def h(x):
    y = f(x)
    z = g(y)
    return z
h(42)
# Functional compose. Inject all dependencies.
def compose(f, g):
    return lambda x: g(f(x))

h = compose(f, g)
h(42)<jupyter_output><empty_output><jupyter_text>Functional Composition is Associative<jupyter_code>def test(x):
    f = lambda x: x + 1
    g = lambda x: x * 2
    h = lambda x: x - 10

    right = compose(f, compose(g, h))
    left = compose(compose(f, g), h)

    assert right(x) == left(x)

test(10)<jupyter_output><empty_output><jupyter_text>The `compose` operator in Expression is defined as follows:<jupyter_code>from typing import Callable, Any

def compose(*fns: Callable[[Any], Any]) -> Callable[[Any], Any]:
    """Compose multiple functions left to right."""

    def _compose(source: Any) -> Any:
        return reduce(lambda acc, f: f(acc), fns, source)

    return _compose<jupyter_output><empty_output><jupyter_text>It is basically an iteration of the functions keeping an accumulator with the currently composed functions. Believe the TypePython is a strongly typed dynamic programming language. This means that the type of a variable is only checked at runtime. Other languages such as F, Scala, Haskell to type checking at compile time.<jupyter_code>def run():
    return "abc" + 10
run()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09044725570275924, 'fraction_numerical': 0.008267755752564996, 'mean_word_length': 4.837209302325581, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 87, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '637285', 'license': 'MIT', 'n_tokens_mistral': 2918, 'n_tokens_neox': 2744, 'n_words': 1291, 'path': 'notebooks/01 - Introduction.ipynb', 'repo_name': 'philvarner/Expression', 'size': '10049'}