pythonic-fp.fptools API

FP Tools Package

Pythonic FP - Functional Programming Tools

Tools to aid with functional programming in Python yet still endeavoring to remain Pythonic.

Subtypeable Boolean

Class Bool - Subclassable Boolean

Python does not permit bool to be subclassed, but int can be subclassed. Under-the-hood a bool is just an int. The Bool class inherits from int and relies on the underlying truthiness and falsiness of 1 and 0.

The Truth(truth: str) and Lie(lie: str) subclass constructors produce singletons based on their input parameters. When using type hints, declare variables of these types as type Bool. Best practices when used with these subclasses are:

  • use == or != for pure Boolean comparisons

  • use is or not is if the type of truth matters

  • only use Bool() as a type, never as a constructor

  • when using Python shortcut logic remember

    • an instance of Truth is truthy

    • an instance of Lie is falsy

    • shortcut logic is lazy

      • the last truthy thing evaluated is returned

      • and is not converted to a bool

    • the not statement converts a Bool to an actual bool

class pythonic_fp.fptools.bool.Bool

Subclassable Boolean-like class.

class pythonic_fp.fptools.bool.Lie(lie='LIE')

Falsy singleton Bool subclass.

:: note:

When using type hints, declare variables Bool, not Lie.

class pythonic_fp.fptools.bool.Truth(truth='TRUTH')

Truthy singleton Bool subclass.

:: note:

When using type hints, declare variables Bool, not Truth.

Function Tools

Pythonic FP - FP tools for functions

Not a replacement for the std library’s functools which is more about modifying function behavior through decorators than functional composition and application.

FP utilities to manipulate and partially apply functions

  • function swap: Swap the arguments of a 2 argument function

  • function it: Function returning an iterator of its arguments

  • function sequenced: Convert function to take a sequence of its arguments

  • function negate: Transforms a predicate to its negation

  • function partial: Returns a partially applied function

pythonic_fp.fptools.function.it(*args)

Function returning an iterator of its arguments.

Return type:

Iterator[A]

pythonic_fp.fptools.function.negate(f)

Take a predicate and return its negation.

Return type:

Callable[ParamSpec, bool]

pythonic_fp.fptools.function.partial(f, *args)

Partially apply arguments to a function, left to right.

  • type-wise the only thing guaranteed is the return type

  • best practice is to cast the result immediately

Return type:

Callable[…, R]

pythonic_fp.fptools.function.sequenced(f)

Convert a function with arbitrary positional arguments to one taking a tuple of the original arguments.

  • was awaiting typing and mypy “improvements” to ParamSpec

    • return type: Callable[tuple[P.args], R] ???

    • return type: Callable[[tuple[P.args]], R] ???

  • not going to happen - https://github.com/python/mypy/pull/18278

  • TODO: Look into replacing this function with a Callable class?

Return type:

Callable[tuple[Any], R]

pythonic_fp.fptools.function.swap(f)

Swap arguments of a two argument function.

Return type:

Callable[V, U, R]

Lazy Function Evaluation

Pythonic FP - Lazy function evaluation

Delayed function evaluations. FP tools for “non-strict” function evaluations. Useful to delay a function’s evaluation until some inner scope.

Non-strict delayed function evaluation:

  • class Lazy: Delay evaluation of functions taking & returning single values

  • function lazy: Delay evaluation of functions taking any number of values

  • function real_lazy: Version of lazy which caches its result

class pythonic_fp.fptools.lazy.Lazy(f, d, pure=True)

Delayed evaluation of a singled valued function.

Class instance delays the executable of a function where Lazy(f, arg) constructs an object that can evaluate the Callable f with its argument at a later time.

  • first argument f taking values of type ~D to values of type ~R

  • second argument arg: ~D is the argument to be passed to f

    • where the type ~D is the tuple type of the argument types to f

  • function is evaluated when the eval method is called

  • result is cached unless pure is set to False

  • returns True in Boolean context if evaluated

Usually use case is to make a function “non-strict” by passing some of its arguments wrapped in Lazy instances.

eval()

Evaluate function with its argument.

  • evaluate function

  • cache result or exception if pure == True

  • reevaluate if pure == False

Return type:

None

get(alt=None)

Get result only if evaluated and no exceptions occurred, otherwise return an alternate value.

A possible use case would be if the calculation is expensive, but if it has already been done, its result is better than the alternate value.

Return type:

Union[-R, Never]

get_exception()

Get result only if evaluate and exceptional.

Return type:

MayBe[Exception]

get_result()

Get result only if evaluated and not exceptional.

Return type:

MayBe[-R]

got_exception()

Return true if Lazy raised exception.

Return type:

MayBe[bool]

got_result()

Return true if an evaluated Lazy did not raise an exception.

Return type:

MayBe[bool]

pythonic_fp.fptools.lazy.lazy(f, *args, **kwargs)

Delayed evaluation of a function with arbitrary positional arguments.

Function returning a delayed evaluation of a function of an arbitrary number of positional arguments.

  • first positional argument f takes a function

  • next positional arguments are the arguments to be applied later to f

    • f is reevaluated whenever eval method of the returned Lazy is called

  • any kwargs passed are ignored

    • if f needs them, then wrap f in another function

Return type:

Lazy[tuple[Any, …], R]

pythonic_fp.fptools.lazy.real_lazy(f, *args, **kwargs)

Cached delayed evaluation of a function with arbitrary positional arguments.

Function returning a delayed evaluation of a function of an arbitrary number of positional arguments.

  • first positional argument f takes a function

  • next positional arguments are the arguments to be applied later to f

    • f is evaluated when eval method of the returned Lazy is called

    • f is evaluated only once with results cached

  • any kwargs passed are ignored

    • if f needs them then wrap f in another function

Return type:

Lazy[tuple[Any, …], R]

Singletons

Pythonic FP - Collection of singleton classes

final class pythonic_fp.fptools.singletons.Nada

Singleton class representing & propagating failure.

  • singleton _nada: nada = Nada() represents a non-existent value

  • returns itself for arbitrary method calls

  • returns itself if called as a Callable with arbitrary arguments

  • interpreted as an empty container by standard Python functions

  • warning: non-standard equality semantics

    • comparison compares true only when 2 non-missing values compare true

    • thus a == b means two non-missing values compare as equal

  • usage

    • import Nada and then

      • either use Nada() directly

      • or define _nada: Final[Nada] = Nada() don’t export it

    • start propagating failure by setting a propagating value to Nada()

      • works best when working with expression

      • failure may fail to propagate

        • for a function/method with just side effects

        • engineer Nada() to fail to trigger side effects

    • test for failure by comparing a result to Nada() itself using

      • is and is not

    • propagate failure through a calculation using

      • == and !=

      • the Nada() value never equals itself

      • and never equals anything else

nada_get(alt=Sentinel('Nada'))

Get an alternate value, defaults to Nada().

Return type:

Any

class pythonic_fp.fptools.singletons.NoValue

Singleton class representing a missing value.

  • similar to None but

    • while None represents “returned no values”

    • NoValue() represents the absence of a value

  • usage

    • import NoValue from pythonic-fp.fptools.singletons and then

      • either use NoValue() directly

      • or define _noValue: Final[NoValue] = NoValue() don’t export it

    • compare using is and is not

      • not == or !=

      • None means returned no values, so None == None makes sense

      • if one or both values are missing, then what is there to compare?

final class pythonic_fp.fptools.singletons.Sentinel(sentinel_name)

Singleton classes representing a sentinel values.

  • intended for library code, not to be exported/shared between modules

    • otherwise some of its intended typing guarantees may be lost

  • useful substitute for None as a hidden sentinel value

    • allows None to be stored in data structures

    • allows end users to choose to use None or () as sentinel values

    • always equals itself (unlike NoValue)

  • usage

    • import Sentinel and then either

      • define _my_sentinel: Final[Sentinel] = Sentinel('my_sentinel')

      • or use Sentinel('my_sentinel') directly

    • compare using either

      • is and is not or == and !=

      • the Sentinel() value always equals itself

      • and never equals anything else, especially other sentinel values

State Monad

Pythonic FP - State Monad

Handling state functionally.

##### class State - Classic FP State Monad

A pure FP immutable implementation for the State Monad.

  • translated to Python from the book “Functional Programming in Scala”

    • authors Chiusana & Bjarnason

    • run “action” returns a tuple (a, s) reversed to the type State[S, A]

      • the standard convention seen in the FP community

      • another “factoid” to remember

  • choose the name bind instead of flatmap

    • the flatmap name is misleading for non-container-like monads

    • flatmap name too long, bind shorter to type

      • without “do-notation”, code tends to march to the right

  • typing for the modify class method may be a bit suspect

class pythonic_fp.fptools.state.State(run)

Data structure generating values while propagating changes of state.

  • class State represents neither a state nor (value, state) pair

    • it wraps a transformation old_state -> (value, new_state)

    • the run method is this wrapped transformation

    • bind is just state propagating function composition

bind(g)

Perform function composition while propagating state.

Return type:

State[~S, B]

both(rb)

Return a tuple of two state actions.

Return type:

State[~S, tuple[~A, B]]

eval(init)

Evaluate the Monad via passing an initial state.

Return type:

~A

static get()

Set run action to return the current state

  • the current state is propagated unchanged

  • current value now set to current state

  • will need type annotation

Return type:

State[ST, ST]

map(f)

Map a function over a run action.

Return type:

State[~S, B]

map2(sb, f)

Map a function of two variables over two state actions.

Return type:

State[~S, C]

static modify(f)

Modify previous state.

  • like put, but modify previous state via f

  • will need type annotation

    • mypy has no “a priori” way to know what ST is

Return type:

State[ST, tuple]

static put(s)

Manually insert a state.

THe run action.

  • ignores previous state and swaps in a new state

  • assigns a canonically meaningless value to current value

Return type:

State[ST, tuple]

static sequence(sas)

Combine a list of state actions into a state action of a list.

  • all state actions must be of the same type

  • run method evaluates list front to back

Return type:

State[ST, list[AA]]

static unit(b)

Create a State action returning the given value.

Return type:

State[ST, B]