A typed and tested package stub with resources for functional programming in Python.
Currently implements base and pointed functor sets allowing recursive mapping of nested values, as well as the curry
, curry_n
, compose
and pipe
functions.
Base and pointed functors can be used via the top-level phnew
factory instance, as well as via the builders in 'phns/builder.py' and direct from 'phns/functor.py'.
The primary functions curry
, curry_n
, compose
and pipe
can be imported directly from 'phns/primary.py'.
Utility functions can be imported from 'phns/utility.py'.
Import the phnew
factory instance and pass the shorthand for the structure to be built along with its initial internal value. For a base functor, the simplest shorthand is f
, for a pointed functor pf
.
from phns import phnew
demo_f = phnew('f', 1)
Alternatively, the given builder can be used independently:
from phns.builder import get_functor
demo_f = get_functor(1)
A class can also be imported from phns.functor
and instantiated directly, whether the base Functor
, FunctorIter
or FunctorDict
, or the pointed PFunctor
, PFunctorIter
or PFunctorDict
.
Note that by default a list, tuple, set, frozenset or bytearray passed to the phnew
factory instance or to a builder is added to an instance of the -Iter
class, and a dictionary to an instance of the -Dict
class. For more on these classes and overriding this behaviour, see Containers below. For use with strings, see Other iterables.
For alternatives to f
and pf
, see Shorthands below.
Each functor has a .map
method for operations using its internal value. Map by passing to this method the function to be applied.
With a non-pointed functor, the return value of the method is the result of the mapping. The internal value does not change.
demo_f.map(lambda x: x + 1)
With a pointed functor, the return value is a new instance of that functor, with its internal value being the result of the mapping. This allows uses of .map
to be chained.
PFunctor.of(1).map(lambda x: x + 1).map(lambda x: x * 2)
By default a list, tuple, set, frozenset or bytearray passed with the phnew
f
or pf
shorthand or directly to a builder is added to an instance of the -Iter
class, and a dictionary to an instance of the -Dict
class. This means that each item in the data structure is mapped.
In order to avoid this and map the data structure as a whole, the phnew
shorthand f.
or pf.
can be used:
from phns import phnew
demo_f = phnew('f.', 1)
Alternatively, the given builder can have its as_base
keyword argument set to True
:
from phns.builder import get_functor
demo_f = get_functor(1, as_base=True)
The complementary phnew
shorthands f:
and pf:
are equivalent to f
and pf
, providing the default behaviour. See also Shorthands below.
For use of the factory instance and builders with strings, see Other iterables below.
In the case of either an -Iter
instance containing a list
or tuple
or a -Dict
instance, the mapping can be applied not only to the data structure's top-level values, but also to nested instances of the structure, by setting the .map
method's second argument (as_tree
) to True
:
FunctorIter([1, [2, 3]]).map(lambda x: x + 1, True)
An -Iter
or -Dict
instance also applies the function in this way if instantiated by whichever means with the as_tree
keyword argument set to True
:
demo_fi_1 = get_functor([1, [2, 3]], as_tree=True)
demo_fi_2 = FunctorIter([1, [2, 3]], as_tree=True)
demo_pfi = PFunctorIter.of([1, [2, 3]], as_tree=True)
Alternatively, the appropriate phnew
shorthand can be used, either f:{
or f{
for a base functor or pf:{
or pf{
for a pointed:
demo_fi_3 = phnew('f:{', [1, 2, 3])
See also Shorthands below.
Note that the builders pass to the -Iter
classes only lists, tuples, sets, frozensets and bytearrays.
For strings, to allow individual character mapping, it is possible to:
- instantiate an
-Iter
class directly - set the
as_iter
keyword argument of the builder toTrue
- use the corresponding
phnew
shorthand, eitherf:.
orpf:.
For other iterables, the above may also be possible, along with adding the new type to the reference list in 'phns/builder.py', in each case potentially with modifications. Pull requests are welcome.
-
f
/f:
builds based on value type, producing:- a
FunctorDict
if the value is a dictionary - a
FunctorIter
if the value is a list, tuple, set, frozenset or bytearray - a
Functor
otherwise
- a
-
f{
/f:{
builds based on value type and activates nested mapping, producing:- a
FunctorDict
if the value is a dictionary, withas_tree
set toTrue
- a
FunctorIter
if the value is a list, tuple, set, frozenset or bytearray, withas_tree
set toTrue
- a
Functor
otherwise
- a
-
f.
builds irrespective of value type, producing aFunctor
-
f:.
builds irrespective of value type, producing aFunctorIter
-
pf
/pf:
builds based on value type, producing:- a
PFunctorDict
if the value is a dictionary - a
PFunctorIter
if the value is a list, tuple, set, frozenset or bytearray - a
PFunctor
otherwise
- a
-
pf{
/pf:{
builds based on value type and activates nested mapping, producing:- a
PFunctorDict
if the value is a dictionary, withas_tree
set toTrue
- a
PFunctorIter
if the value is a list, tuple, set, frozenset or bytearray, withas_tree
set toTrue
- a
PFunctor
otherwise
- a
-
pf.
builds irrespective of value type, producing aPFunctor
-
pf:.
builds irrespective of value type, producing aPFunctorIter
For curry
, curry_n
, compose
and pipe
, import from 'phns/primary.py':
from phns.primary import *
Passing an uncurried function to curry
returns a collector function, allowing the initial function's arguments to be provided singly or in groups. The function is invoked when the last argument is received. The variation curry_n
takes as its second argument an integer specifying the number of arguments to be collected.
Passing one or more functions to compose
or pipe
returns a single function to call the whole set in sequence. The process begins when this is called with any arguments to the first in the set, with the return value from each passed to the next, or out from the last. Note that compose
calls the set from right to left, pipe
from left to right.
The module 'phns/utility.py' includes traverse_iter
and traverse_dict
for trees of a given data structure, plus get_args
to help determine arity.
The two verification scripts - 'verify.py' and 'verify.sh' - can be used to check types and run the interactive examples and unit tests.
The verification scripts can be run as follows:
python3 verify.py
sh verify.sh
Either of the two can also be run with the command ./<filename>
while in the same directory, and from elsewhere using the pattern path/to/<filename>
, by first making the file executable, if not already, with chmod +x <filename>
. Both the Python and shell binary are assumed to be accessible via the '/usr/bin' directory, per the hashbang at the top of each file.
./verify.py
./verify.sh
Each of the three - type checking, interactive examples and unit tests - can instead be run individually using the specific command in 'verify.sh'.
Type checking uses Mypy, an external tool. The Mypy-related dependencies per Python 3.11 are listed in the file 'requirements.txt'.
To run the type checking only:
mypy phns/
The interactive examples use doctest
in the standard library.
To run the interactive examples only:
python3 -m doctest phns/*.py
The unit tests use unittest
, also in the standard library.
To run the unit tests only:
python3 -m unittest --quiet test/*.py
The following are the expected next steps in the development of the code base. The general medium-term aim is a comprehensive set of the core patterns applied in functional programming. Pull requests are welcome for these and other potential improvements.
- classes and builder for applicative functors, followed by a minimal base monad
- reducers and transducers
./
├── phns
│ ├── __init__.py
│ ├── builder.py
│ ├── factory.py
│ ├── functor.py
│ ├── primary.py
│ └── utility.py
├── test
│ ├── __init__.py
│ ├── test_builder.py
│ ├── test_factory.py
│ ├── test_functor.py
│ ├── test_primary.py
│ └── test_utility.py
├── .gitignore
├── LICENSE.txt
├── README.md
├── requirements.txt
├── verify.py
└── verify.sh