Usage

How to installing the package

Install the project into your Python environment:

$ pip install pythonic-fp-gadgets

Importing the modules

Import the gadgets where “gadgets” are simple objects with no external dependencies other than the Python standard library.

from pythonic_fp.gadgets.box import Box
from pythonic_fp.gadgets.iterate_arguments import ita
from pythonic_fp.gadgets.latest_common_ancestor import lca
from pythonic_fp.gadgets.sentinels.flavored import Sentinel
from pythonic_fp.gadgets.sentinels.novalue import NoValue

Use case examples

Flavored Sentinels

Sentinel values of different (hashable) flavors. Can be used with functions or classes.

Some Use Cases

Could be used like an Enum.

from pythonic_fp.gadgets.sentinels.flavored import Sentinel

def calculate_something(n: int, x: float) -> tuple[Sentinel[int], float]:
    if n <= 0:
        return (Sentinel(0), x)
    return (Sentinel(n), x/n)

def process_result(pair: tuple[Sentinel[int], float]) -> float:
    if pair[0] is Sentinel(0):
        return 0.0
    return pair[1]

result = process_result(calculate_something(213, 15234.541))

Can be also be used as a private implementation detail for a class. Here is an example of an class that can take an “optional” value yet still be able to store None as a value.

from typing import cast, ClassVar, Final, overload
from pythonic_fp.gadgets.sentinels.flavored import Sentinel


class MyClass:
    _sentinel: Final[ClassVar[Sentinel[str]]] = Sentinel('_secret_str')

    @overload
    def __init__(self) -> None: ...
    @overload
    def __init__(self, value: float) -> None: ...
    @overload
    def __init__(self, value: None) -> None: ...
    @overload
    def __init__(self, value: float | None) -> None: ...

    def __init__(
        self, value: float | None | Sentinel[str] = Sentinel('_secret_str')
    ) -> None:
        if value is self._sentinel:
            self.value: float | None = 42.0
        else:
            self.value = cast(float | None, value)

    def get_value(self) -> float | None:
        return self.value

Sentinel NoValue

This test from the test suite is a bit contrived, but I think it illustrates the usage.

from typing import cast, Final, overload
from pythonic_fp.gadgets.sentinels.novalue import NoValue


class Foo:
    @overload
    def __init__(self, /) -> None: ...
    @overload
    def __init__(self, repeat: None, /) -> None: ...

    def __init__(self, repeat: int | None | NoValue = NoValue(), /) -> None:
        if repeat is None:
            self._repeat = 0
        else:
            self._repeat = 1

    def repeat(self) -> str:
        return 'foo' * self._repeat


class RepeatFoo(Foo):
    @overload
    def __init__(self, /) -> None: ...
    @overload
    def __init__(self, repeat: None, /) -> None: ...
    @overload
    def __init__(self, repeat: int, /) -> None: ...

    def __init__(self, repeat: int | None | NoValue = NoValue(), /) -> None:
        if repeat is None:
            self._repeat = 1
        elif repeat is NoValue():
            self._repeat = 2
        else:
            ii: int = cast(int, repeat)
            self._repeat = ii if ii >=0 else -ii


def test_foo() -> None:
    foo: Foo = Foo()
    foo_none: Foo = Foo(None)

    assert foo.repeat() == 'foo'
    assert foo_none.repeat() == ''


def test_repeat_foo() -> None:
    foo: Foo = RepeatFoo()
    foo_none: Foo = RepeatFoo(None)
    foo_0: Foo = RepeatFoo(0)
    foo_1: Foo = RepeatFoo(1)
    foo_2: Foo = RepeatFoo(2)
    foo_3: Foo = RepeatFoo(3)
    foo_neg_4: Foo = RepeatFoo(-4)

    assert foo.repeat() == 'foofoo'
    assert foo_none.repeat() == 'foo'
    assert foo_0.repeat() == ''
    assert foo_1.repeat() == 'foo'
    assert foo_2.repeat() == 'foofoo'
    assert foo_3.repeat() == 'foofoofoo'
    assert foo_neg_4.repeat() == 'foofoofoofoo'