grscheller.fp.function

Module fp.functional - compose and partially apply 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 function arguments return values:

  • function swap: swap the arguments of a 2 argument function
  • function sequenced: convert function to take a sequence of its arguments
  • function partial: returns a partially applied function
  • function iter_args: function returning an iterator of its arguments
 1# Copyright 2024-2025 Geoffrey R. Scheller
 2#
 3# Licensed under the Apache License, Version 2.0 (the "License");
 4# you may not use this file except in compliance with the License.
 5# You may obtain a copy of the License at
 6#
 7#     http://www.apache.org/licenses/LICENSE-2.0
 8#
 9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""###Module fp.functional - compose and partially apply functions.
16
17Not a replacement for the std library's `functools` which is more about
18modifying function behavior through decorators than functional composition
19and application.
20
21#### FP utilities to manipulate function arguments return values:
22
23* function **swap:** swap the arguments of a 2 argument function
24* function **sequenced:** convert function to take a sequence of its arguments
25* function **partial:** returns a partially applied function
26* function **iter_args:** function returning an iterator of its arguments
27
28"""
29from __future__ import annotations
30from collections.abc import Callable, Iterator, Sequence
31from typing import Any
32
33__all__ = [ 'swap', 'sequenced', 'partial', 'iter_args' ]
34
35## Functional Utilities
36
37def swap[U,V,R](f: Callable[[U, V], R]) -> Callable[[V, U], R]:
38    """Swap arguments of a two argument function."""
39    return (lambda v, u: f(u,v))
40
41def sequenced[R](f: Callable[..., R]) -> Callable[..., R]:
42    """Convert a function with arbitrary positional arguments to one taking
43    a sequence of the original arguments.
44    """
45    def F(arguments: Sequence[Any]) -> R:
46        return f(*arguments)
47    return F
48
49def partial[R](f: Callable[..., R], *args: Any) -> Callable[..., R]:
50    """Partially apply arguments to a function, left to right.
51
52    * type-wise the only thing guaranteed is the return value
53    * best practice is to either
54      * use `partial` and `sequenced` results immediately and locally
55      * otherwise cast the results when they are created
56
57    """
58    def wrap(*rest: R) -> R:
59        return sequenced(f)(args + rest)
60
61    return wrap
62
63def iter_args[A](*args: A) -> Iterator[A]:
64    """Function returning an iterators of its arguments.
65
66    * useful for API's with single iterable constructors
67
68    """
69    for arg in args:
70        yield arg
def swap(f: 'Callable[[U, V], R]') -> 'Callable[[V, U], R]':
38def swap[U,V,R](f: Callable[[U, V], R]) -> Callable[[V, U], R]:
39    """Swap arguments of a two argument function."""
40    return (lambda v, u: f(u,v))

Swap arguments of a two argument function.

def sequenced(f: 'Callable[..., R]') -> 'Callable[..., R]':
42def sequenced[R](f: Callable[..., R]) -> Callable[..., R]:
43    """Convert a function with arbitrary positional arguments to one taking
44    a sequence of the original arguments.
45    """
46    def F(arguments: Sequence[Any]) -> R:
47        return f(*arguments)
48    return F

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

def partial(f: 'Callable[..., R]', *args: Any) -> 'Callable[..., R]':
50def partial[R](f: Callable[..., R], *args: Any) -> Callable[..., R]:
51    """Partially apply arguments to a function, left to right.
52
53    * type-wise the only thing guaranteed is the return value
54    * best practice is to either
55      * use `partial` and `sequenced` results immediately and locally
56      * otherwise cast the results when they are created
57
58    """
59    def wrap(*rest: R) -> R:
60        return sequenced(f)(args + rest)
61
62    return wrap

Partially apply arguments to a function, left to right.

  • type-wise the only thing guaranteed is the return value
  • best practice is to either
    • use partial and sequenced results immediately and locally
    • otherwise cast the results when they are created
def iter_args(*args: 'A') -> 'Iterator[A]':
64def iter_args[A](*args: A) -> Iterator[A]:
65    """Function returning an iterators of its arguments.
66
67    * useful for API's with single iterable constructors
68
69    """
70    for arg in args:
71        yield arg

Function returning an iterators of its arguments.

  • useful for API's with single iterable constructors