FP in python in the real world

Javier Rojas

March, 2021

Map for this talk

Map for this talk

  • No Shared (Mutable) State
  • Functions as first-class citizens
  • Closures

No Shared (Mutable) State

No Shared (Mutable) State

  • Immutability
  • Pure Functions
  • Referential Transparency

Sorry

  • Functions can change:
    • their arguments
    • global variables
  • Python doesn’t enforce immutability

Why be sorry?

Pure functions make code easier to read

With immutability:

  • More compiler optimizations are available
  • Sharing data is easier and safer
  • Concurrency is MUCH easier to get right

Sorry Not Sorry 😅!


d = %{age: 7, height: 110}
d = %{d | age: 9}

d = %{john: %{age: 7}, meg: %{age: 20}}

# d[:john][:age] = 8
d = %{ d | john: %{ d[:john] | age: 8 } }

Functions as first-class citizens

Functions as first-class citizens

Functions as variables

Functions as arguments to functions

Functions as return values

Dynamic defaults


from uuid import uuid4

def prefixed_uuid():
    return "acc-" + uuid4().hex

class Account(models.Model):
    ...
    public_id = StringField(default=prefixed_uuid)

Decorators


def authentication(view_func):
    def authenticated_view(*args, **kwargs):
        if args[0] == 'valid_user':
            return view_func(*args, **kwargs)
        return redirect_to_login()

    return authenticated_view

@authentication
def welcome_view(username):
    print(f"hello {username}!")

welcome_view = authentication(welcome_view)

Higher-order functions: map, filter

Map: apply a function to all the elements of a list:


map(lambda x: x * x, range(5))
[0, 1, 4, 9, 16]

[x * x for x in range(5)]
[0, 1, 4, 9, 16]

Filter: Keep only some elements from a list:


filter(
  lambda x: x % 2 == 0,
  range(5)
)
[0, 2, 4]

[
  elem
  for elem in range(5)
  if elem % 2 == 0
]
[0, 2, 4]

Other higher-order functions

  • itertools
    • takewhile, dropwhile, groupby, …
  • functools
    • reduce, cache (memoize), partial (currying), …

Closures

Closures


def external_service():
    """Fetches a number from a remote endpoint"""
    return 41

def action(retries=3):
    """"Obtains number from a service, adds 1 to it.
    Retries in case of timeout
    """
    try:
        v = external_service()
    except Timeout as exc:
        if retries > 0:
            return action(retries=retries - 1)
        else:
            return 0

    return v + 1

Closures

Goal: test retries in action

how?

Mock external_service to timeout once and succeed afterwards

But, how to make the mock remember how many times it’s been called?

using a closure

(see closures.py)

What do we get from closures?

  • private reference to variables
  • reference and update that private state
  • from some component created dynamically

Classes + callable objects

A class can define a __call__ method, which will be called when instances are “called” as functions.

(see closures.py)

Things not discussed

Things not discussed

  • types
  • pattern matching
  • macros

Thanks!