Skip to main content

Algebraic Data Types for Python

Project description

algae

Algebraic Data Types for Python

option

This repository contains an implementation from scratch of simple algebraic data types in Python.

The following types are implemented and tested:

  • Either
  • Option
  • Try

This library is inspired by Scala's implementation of these structures.

Feel free to open an issue or send me an email in case you'd like to contribute or if you see something that can be improved.

Installation

This project is published on PyPi as algae so you can easily install it with pip as:

pip install algae

or with poetry as:

poetry add algae

Setup

Poetry

This project uses poetry to manage its dependencies. Please refer to poetry's official doc for more info.

Usage Examples

Either

Either represents a value that can assume one of two types.

Concrete instances are of type Left or Right.

As an example, let's consider the case of making HTTP calls which might return a status code representing an error as the url is user-defined. If a call is successful, we want to return the JSON from the response, but if it's not we'll map it to an internal error message.

The examples use this example server.

import requests
from algae.either import Left, Right, Either
from typing import Dict, Any

def map_response_to_msg(response: requests.models.Response):
    return f"The {response.request.method} request to {response.url} couldn't be completed " \
    f"and returned a {response.status_code} status_code"

def call_and_check(url: str) -> Either[str, Dict[Any, Any]]:
    response = requests.get(url)
    return Right(response.json()) if response.ok else Left(map_response_to_msg(response))

Users of this method will then be able to further chain operations which can result in 2 different results easily, keeping track of the error message identifying the step that returned something unexpected in the chain.

base_url = "https://jsonplaceholder.typicode.com"
users_json = call_and_check(f"{base_url}/users")
posts = users_json.flat_map(lambda json: call_and_check(f"{base_url}/posts?userId={json[0]['id']}"))

Lastly, we'll log the content of the Eitherat the appropriate level in each case; the contained string in the Left case at warn, or the msg field of the JSON dictionary in the Right case at info.

from logging import getLogger

logger = getLogger()

posts.fold(logger.warning, lambda x: logger.info(x[0]["title"]))

The above example enters the Right branch of the Either, change the base_url to $base_url/pizza to get a Left at the first stage.

Please note that this is different from the case where an Exception is raised, which better fits the Try structure described below.

Option

Option represents an optional value, its concrete instances are of type Nothing or Some.

As an example, let's consider the case of checking for a variable in a dictionary. Normally, a default value of None is returned if the request key is not present in the dictionary, however this requires the user of method returning such a value to check explicitly the content of the return variable.

Further, multiple calls of this type cannot be chained together, and the value needs to be checked every time. Using Option we can instead reason using the type directly, and demanding to it the checking steps.

from algae.option import Option

d = {"food": "Pizza"}

result = Option.apply(d.get("another_key"))

awesomize = lambda x: x + "is awesome" 

msg = result.map(awesomize)

This way we didn't need to check whether the key was present in the dictionary or not. Finally, we can get a default value to go from an Option to a str.

msg.fold("Pizza is incredible anyways!", lamdba x: x + ", but fries are good too!")

The final msg will be Pizza is incredible anyways!.

If instead we had looked for the food key, msg would have been Pizza is awesome, but fries are good too!

Try

Try represents a computation that can either fail (raising an Exception) or return the resulting value.

Concrete instances are of type Failure or Success.

As an example, let's see the case of a function that can raise an Exception:

import math

def unsafe_computation(value: int):
    return math.log(value)  # this throws an Exception if value is <= 0

Upon calling this function with value <= 0 we'll see:

unsafe_computation(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: math domain error

To make this computation safe, even for value <= 0, we'll wrap its execution with Try:

from algae.try_ import Try

safe_result = Try.apply(unsafe_computation, 0)

safe_result will be of type Failure, containing the Exception. In case it was called on proper input:

safe_result = Try.apply(unsafe_computation, 1)

safe_result will be of type Success and it will contain the proper result.

Please notice that you need to pass the function and any function arguments, named and not, as arguments to Try.apply() rather than passing f(args).

Alternatively, you can use this syntax:

safe_result = Try.apply(lambda: unsafe_computation(0))

Using Try, an appropriate return type can be used for methods that might fail and raise an Exeception, leaving the user in charge of easily dealing with the subsequent behavior, for example:

Try.apply(unsafe_computation, 1).map(lambda x: x + 1)

Special thanks to David Cuthbert for letting me have the "algae" name on PyPI.

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

algae-1.1.0.tar.gz (7.0 kB view details)

Uploaded Source

Built Distribution

algae-1.1.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file algae-1.1.0.tar.gz.

File metadata

  • Download URL: algae-1.1.0.tar.gz
  • Upload date:
  • Size: 7.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.11 CPython/3.10.4 Darwin/21.6.0

File hashes

Hashes for algae-1.1.0.tar.gz
Algorithm Hash digest
SHA256 a4e75dde2b5c290d6226c6358d58ebcb5a0cc5177326acf362d58d9f87dacd67
MD5 b0a18c6e6a0f6f9f31932aacbff15efe
BLAKE2b-256 e62449c3e6fe37dd9431d65914be3efaac31df36595b74621b9ed3c8b385f2d5

See more details on using hashes here.

File details

Details for the file algae-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: algae-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.11 CPython/3.10.4 Darwin/21.6.0

File hashes

Hashes for algae-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 287ed39f9b650d8de1056a95ee57ace560086df19ff8087eefe7e2db6b133c30
MD5 efc4464a3e922fd2b49acc905a5ec18d
BLAKE2b-256 bf1f0a03e4f53677ad94e48d887e8e1020b3765e843fd6f19c7131177cc42621

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