Functional programming in Python

Work in progress.

Typing in Python

There are two types of functions in Python:

  • anonymous functions: also called lambda expressions, these functions cannot be explicitly typed
  • functions defined with the keyword def, these can be fully typed.

Enforcing types

Types are by default not enforced in Python. To enforce typing, you need to use type hints. It is recommended to use type hints, because they prevent errors by imposing constraints on the way your function can be used. One way to enforce type hints, is by using linters.

The default combination that should be used in every project should be:

  1. pylint: this checks for very basic errors, it already contains a lot of checks on functions such as using the wrong number of arguments
  2. mypy: this checks for errors in function composition and more advanced problems with types.

More advanced typing in Python is done using the typing module. It can be used for generic programming.

Partial Application

Currying is a functional programming concept where a function with multiple arguments is transformed into a chain of functions, each with a single argument. So instead of a function f: (A,B) -> C you split it up in a chain of functions g: (A -> (B -> C)).

The name “currying” is a misnomer, since it was known to mathematicians in the 19th centure, years before Curry invented lambda calculus. However, since Curry contributed a famous theory, his name stuck to the concept.

In the context of biology, we can think of a simple example such as calculating the Body Mass Index (BMI), which depends on both weight and height.

Below is an example of how you might implement curried functions to calculate BMI in Python:

# First, we define a curried function that takes the person's height.
def bmi_for_height(height_in_meters):
    # The inner function takes the weight, using the height from the outer function.
    def bmi_for_weight_and_height(weight_in_kilograms):
        return weight_in_kilograms / (height_in_meters ** 2)
    return bmi_for_weight_and_height

# Example usage:
# We have a person with a height of 1.75 meters.
calculate_bmi_for_person = bmi_for_height(1.75)

# Now we can use this function to find the BMI for any weight of this person.
print(f"BMI for 70kg: {calculate_bmi_for_person(70)}")
print(f"BMI for 80kg: {calculate_bmi_for_person(80)}")

In this example, the bmi_for_height function takes one argument (the height), and returns another function (bmi_for_weight_and_height) that takes the next argument (the weight). This way, if you often need to calculate BMI for a person of a certain height, you can ‘curry’ their height and get a custom function to calculate BMIs just for them based on various weights.