FuPy logo

Implementation Details

The main classes in FuPy are

For evaluation tracing there are also

The classes FuPy.basics.Empty and FuPy.basics.Unit are just used as specific types (empty and singleton, respectively).

Func

Generic class FuPy.basics.Func[A, B] serves two roles:

  • Func[A, B] is the function space type for functions from A to B, also written as A -> B.

  • It is a (thin) wrapper for function objects (or other callables), so that function combinators are supported as infix operators, by overloading @ (composition), | (case), & (split), + (functorial plus), * (functorial times), ** (exponentiation).

    The underlying function is stored in the attribute self.func.

  • This wrapper also carries some additional information in attributes:

    • name: how the function prints, in Math notation

    • top: the top-level operator, in Math notation, if the function is an expression, and '' otherwise

    • required_args_count: the minimum number of required arguments of self.func; this is used for auto-(un)packing of arguments

OperatorSection

Class FuPy.basics.OperatorSection is a helper class to make operator sections work. Only one instance of this class is needed and it is made available as x_ (though it is recommended to rename it to _). OperatorSection redefines various overloadable infix operators op (such as ‘@’), so that

  • (_ op other),

  • (other op _), and

  • (_ op _),

behave as the corresponding operator sections

  • Func(lambda x: x op other)

  • Func(lambda x: other op x)

  • Func(lambda x: Func(lambda y: x op y)

Where are Func objects created?

  • Func() constructor; end-user should not have to call this

  • func() decorator; end-user calls this as

@func
def even(x: int) -> bool:
    return x % 2 == 0
  • Function combinators supported in Func: @ | & + * ** ^

  • FuPy lambda abstractions defined by la('la_expr')

  • Operator sections defined via operators in OperatorSection

  • Operators wrapped in FuPy’s operator_ module

Lazy

TO BE COMPLETED

Where are Lazy objects (thunks) created?

  • Lazy() constructor call; end-user should not have to call this

  • lazy(f) for function f; used e.g. in fix

  • lazy('expr')

  • Some function combinators supported in Func

Where are Lazy objects evaluated?

  • When explicitly calling Lazy._get(); end-user should not have to call this

  • Via evaluate() or utils.force()

  • When calling them: Lazy.__call__()

  • When indexing them: Lazy.__getitem__()

  • When operating on them

    • See operators in Lazy (To Be Completed)

  • In guard(p)(a),(f | g)(a), and (f + g)(a), a will be evaluated

Interactions between Func, OperatorSection, Lazy

Suppose that

  • f and g are instances of Func

  • _ is an instance of OperatorSection

  • theta and theta_ are instances of Lazy

Here is how their various combinations under @ are handled.

  • f @ g: f.__matmul__(g) returns Func(lambda x: f(g(x)))

  • f @ _: f.__matmul__(_) returns NotImplemented; next _.__rmatmul__(f) returns Func(lambda x: f @ x)

  • f @ theta: f.__matmul__(theta) returns NotImplemented; next theta.__rmatmul__(f) evaluates theta, say to value g (which must be of type Func) and invokes f @ g, which (see above)) is handled by f.__matmul__(g)

  • _ @ f: _.__matmul__(f) returns Func(lambda x: x @ f)`

  • _ @ _: _.__matmul__(_) returns Func(lambda x: x @ _); when the latter is called, say on f, this results in f @ _` (see above)

  • _ @ theta: _.__matmul__(theta) returns Func(lambda x: x @ theta); when the latter is called, say on f, this results in f @ theta` (see above)

  • theta @ g: theta.__matmul__(g) evaluates theta, say to value f (which must be of type Func), and invokes f @ g, which is handled by f.__matmul__(g)

  • theta @ _: theta.__matmul__ returns NotImplemented; next _.__rmatmul__(theta) returns Func(lambda x: theta @ x); when the latter is called, say on g, this results in theta @ g (see above)

  • theta @ theta_: theta.__matmul__(theta_) evaluates theta, say to the value f (which must be of type Func) and invokes f @ theta_ (see above)

Combinators | (case) and & (split) are handled similarly.

Combinators + (functorial plus) and * (functorial times) are different, because these operators are also meaningful for types other than Func, in particular numbers and strings.

Combinator ** (iterated composition)

Combinator ^ (functorial exponentiation)