Skip to main content

Function programming in python

Project description

UFO 🛸

uniform functional orchestrators

Delicious monads and functional programming patterns in python!

Check out the api docs for reference

Coverage Badge Static Badge

Project Goals

This library is meant to implement a simple tools for functional programming in python in a way that:

  • Can be intergrated quickly/simply with existing code
  • Maintains type safety
  • Depends only on the standard library

How to install

pip install ufo-tools

Quick Start

UFO is a library to help with functional programming patterns in python.

It's designed to be very small and get out your way rather than requiring use everywhere.

The library contains two modules: wrappers and containers. The wrappers are the most simple, so we can start with them.

Wrappers

Say you have a function that mutates its arguments:

def maximum_of_list_and_also_seven(some_list: list[int]):
    """
    Gets the biggest number from a list of ints, but with
    seven also included for consideration since its such
    a good number.
    """
    some_list.append(7)
    return max(some_list)

The function is simple enough to follow but it's doing something potentially annoying and mutating the some_list variables.

For instance:

my_list = [1, 4]

max_item = maximum_of_list_and_also_seven(my_list)

print(max_item)  # prints out 7

print(my_list)  # [1, 4, 7]

Oh no! Our list changed under our feet, that's rude! A lot of the time we don't want this to happen since it can lead to confusing errors. So you could work around it in a bunch of different ways. ufo_tools gives you an easy one:

from ufo_tools.wrappers import mutation_free

@mutation_free
def maximum_of_list_and_also_seven(some_list: list[int]):
    """
    Gets the biggest number from a list of ints, but with
    seven also included for consideration since its such
    a good number.
    """
    some_list.append(7)
    return max(some_list)

Phew! Now that function will get passed down deep copies of the some_list variable, instead of some_list itself, and we can all sleep easy at night.

Even better, the @mutation_free wrapper also makes pretty quick reading to know that the function can't mutate it's arguments.

See the documentation for some more examples (like adding in retry logic, error handling and deprecation warnings) but hopefully you get the idea. The wrappers are there as some drop in tools to help you on your way to some nice guarantees when working with python.

Containers

Containers are a common pattern for functional programming, they let us chain together values nicely.

Say we're building up some kind on string:

def make_exciting(string):
  return string + "!!!"

def make_loud(string):
  return string.upper()

def say_hello(name):
  return f"hello {name}"

name = "Sam"
greeting = say_hello(name)
loud_greeting = make_loud(greeting)
exciting_loud_greeting = make_exciting(loud_greeting)

print(exciting_loud_greeting)  # HELLO SAM!!!

There's a lot of variables, which in practice aren't ever used, but they could get used any time, so we have to keep thinking about them. We can cut down on unnecesary thinking by chaining functions in a big row:

message = make_exciting(make_loud(say_hello("Sam")))
print(message)

Phew! Except there's enough brackets to make a lisp programmer cry, and also we're reading in the opposite order of the functions being called. make_exciting happens last, but we're reading it first which means we're back to extra thinking again! And that's what we were trying to avoid.

Fortunately containers is here to save you:

from ufo.containers import Container

message = (
  Container("Sam")
  .then(say_hello)
  .then(make_loud)
  .then(make_exciting)
  .unwrap()
)
print(message)

That's a little easier to follow. The unwrap message probably looks a little strange at the end to you - all it's doing is taking the string back out of the container, so we have just a string to print.

Even better than that, we can actually use containers to do a bunch of busy work for us, say we have a whole bunch of names:

names = ["Lisa", "Bart", "Homer", "Maggie", "Marge"]
greetings = [say_hello(i) for i in names]
loud_greetings = [make_loud(i) for i in greetings]
exciting_loud_greetings = [make_exciting(i) for i in loud_greetings]

Oh boy! I'm tired just from typing that example.

Instead, we can use an Array container to iterate over things:

from ufo.containers import Array
messages = (
  Array("Lisa", "Bart", "Homer", "Maggie", "Marge")
  .then(say_hello)
  .then(make_loud)
  .then(make_exciting)
)

This makes things a little easier. The Array container also comes with some additional helpers for working with lists like filter and reduce.

There's containers for handling errors and Nones too - check out the API docs for full details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

funland-0.0.0a0.tar.gz (7.4 kB view details)

Uploaded Source

Built Distribution

funland-0.0.0a0-py3-none-any.whl (8.1 kB view details)

Uploaded Python 3

File details

Details for the file funland-0.0.0a0.tar.gz.

File metadata

  • Download URL: funland-0.0.0a0.tar.gz
  • Upload date:
  • Size: 7.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.12.0

File hashes

Hashes for funland-0.0.0a0.tar.gz
Algorithm Hash digest
SHA256 1ab998eef4d89211df30ae1bebf96ee7fc899b7cbcfa87cd863602c8aac5706c
MD5 f9bf19e330f2d642ee2df160d2b82e6a
BLAKE2b-256 a7941e9f43d542b235bfc436074a23db49c84db52d61ab02e10f1d6ad954b0bb

See more details on using hashes here.

File details

Details for the file funland-0.0.0a0-py3-none-any.whl.

File metadata

  • Download URL: funland-0.0.0a0-py3-none-any.whl
  • Upload date:
  • Size: 8.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.12.0

File hashes

Hashes for funland-0.0.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 2ea5b47148384124095ef9779cf27755e91f3831a9df31bf4bcdc5cafcec85ee
MD5 3cb44a5a301ac5452d47f5611f79bd92
BLAKE2b-256 fe627405cdd92f0807c888a368be1d93f9fd9f60fa2a19b4ce2a34ddbdf730a7

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page