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
 1# Copyright 2024 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
27"""
28from __future__ import annotations
29from collections.abc import Callable, Sequence
30from typing import Any
31
32__all__ = [ 'swap', 'sequenced', 'partial' ]
33
34## Functional Utilities
35
36def swap[U,V,R](f: Callable[[U,V],R]) -> Callable[[V,U],R]:
37    """Swap arguments of a two argument function."""
38    return (lambda v,u: f(u,v))
39
40def sequenced[R](f: Callable[..., R]) -> Callable[..., R]:
41    """Convert a function with arbitrary positional arguments to one taking
42    a sequence of the original arguments.
43    """
44    def F(arguments: Sequence[Any]) -> R:
45        return f(*arguments)
46    return F
47
48def partial[R](f: Callable[..., R], *args: Any) -> Callable[..., R]:
49    """Partially apply arguments to a function, left to right.
50
51    * type-wise the only thing guaranteed is the return value
52    * best practice is to either
53      * use `partial` and `sequenced` results immediately and locally
54      * otherwise cast the results when they are created
55
56    """
57    def apply(fn_seq: Callable[..., R], vals: Sequence[Any]) -> Callable[..., R]:
58        return (lambda restT: fn_seq(vals + restT))
59
60    def wrap(*bs: Any) -> R:
61        return apply(sequenced(f), args)(bs)
62
63    return wrap
def swap(f: 'Callable[[U, V], R]') -> 'Callable[[V, U], R]':
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))

Swap arguments of a two argument function.

def sequenced(f: 'Callable[..., R]') -> 'Callable[..., R]':
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

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]':
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 apply(fn_seq: Callable[..., R], vals: Sequence[Any]) -> Callable[..., R]:
59        return (lambda restT: fn_seq(vals + restT))
60
61    def wrap(*bs: Any) -> R:
62        return apply(sequenced(f), args)(bs)
63
64    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