fn.py


Namefn.py JSON
Version 0.6.0 PyPI version JSON
download
home_pagehttps://github.com/fnpy/fn.py
SummaryImplementation of missing features to enjoy functional programming in Python
upload_time2023-06-14 19:54:14
maintainer
docs_urlNone
authorfnpy team
requires_python
licenseCopyright 2013 Alexey Kachayev Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
keywords functional fp utility
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Fn.py: enjoy FP in Python
=========================

.. image:: https://badges.gitter.im/fnpy/fn.py.svg
   :alt: Join the chat at https://gitter.im/fnpy/fn.py
   :target: https://gitter.im/fnpy/fn.py?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge

.. image:: https://travis-ci.org/fnpy/fn.py.svg?branch=master
    :target: https://travis-ci.org/fnpy/fn.py

.. image:: https://img.shields.io/pypi/v/fn.py
    :alt: PyPI
    :target: https://pypi.org/project/fn.py

.. image:: https://img.shields.io/pypi/dm/fn.py
    :alt: PyPI - Downloads
    :target: https://pypi.org/project/fn.py

Despite the fact that Python is not a pure-functional programming language, it
is multi-paradigm and gives you enough freedom to take advantage of a functional
approach.  There are theoretical and practical advantages to the functional
style:

-  Formal provability
-  Modularity
-  Composability
-  Ease of debugging and testing

``Fn.py`` library provides you with the missing "batteries" to get the maximum
from a functional approach, even in mostly-imperative softwares.

You can find more about the functional approach from my Pycon UA 2012 talks:
`Functional Programming with Python
<https://kachayev.github.com/talks/uapycon2012/index.html>`_.

Scala-style lambdas definition
------------------------------

::

    >>> from fn import _
    >>> from fn.op import zipwith
    >>> from itertools import repeat

    >>> list(map(_ * 2, range(5)))
    [0, 2, 4, 6, 8]
    >>> list(filter(_ < 10, [9,10,11]))
    [9]
    >>> list(zipwith(_ + _)([0,1,2], repeat(10)))
    [10, 11, 12]

You can find more examples of ``_`` in `test cases <tests/test_underscore.py>`_
(attributes resolving, method calling, slicing).

**Attention!** If you work in an interactive python shell, ``_`` can be assigned
to the latest output and you'll get unpredictable results.  In this case, you
can use ``X`` instead with ``from fn import _ as X``.

If you are not sure what your function is going to do, print it::

    >>> print(_ + 2)
    (x1) => (x1 + 2)
    >>> print(_ + _ * _)
    (x1, x2, x3) => (x1 + (x2 * x3))

Note that ``_`` will fail with ``ArityError`` (``TypeError`` subclass) when
called with the wrong number of arguments, so as to avoid errors::

    >>> (_ + _)(1)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "fn/underscore.py", line 82, in __call__
        raise ArityError(self, self._arity, len(args))
    fn.underscore.ArityError: (_ + _) expected 2 arguments, got 1


Persistent data structures
--------------------------

**Attention:** Persistent data structures are under active development.

A persistent data structure always preserves its previous version when it is
modified (more on `Wikipedia <https://goo.gl/8VveOH>`_).  Each operation thus
yields a new updated structure instead of performing in-place modifications (all
previous versions are potentially available or GC-ed when possible).

::

    >>> from fn.immutable import SkewHeap
    >>> s1 = SkewHeap(10)
    >>> s2 = s1.insert(20)
    >>> s2
    <fn.immutable.heap.SkewHeap object at 0x...>
    >>> s3 = s2.insert(30)
    >>> s3
    <fn.immutable.heap.SkewHeap object at 0x...>
    >>> id(s3) != id(s2)
    True
    >>> s3.extract()
    (10, <fn.immutable.heap.SkewHeap object at 0x...>)
    >>> s3.extract() # <-- s3 isn't changed
    (10, <fn.immutable.heap.SkewHeap object at 0x...>)

If you think I'm totally crazy and it will work despairingly slow, just give it
5 minutes.  Relax, take a deep breath and read about a few techniques that make
persistent data structures fast and efficient: `structural sharing
<https://en.wikipedia.org/wiki/Persistent_data_structure#Examples_of_persistent_data_structures>`_
and `path copying
<https://en.wikipedia.org/wiki/Persistent_data_structure#Path_Copying>`_.  To
see how it works in "pictures", you can check the great slides from Zach
Allaun's talk (StrangeLoop 2013): `"Functional Vectors, Maps And Sets In Julia"
<https://goo.gl/Cp1Qsq>`_.  And, if you are brave enough, go and read:

- `Chris Okasaki, "Purely Functional Data Structures" <https://goo.gl/c7ptkk>`_
- `Fethi Rabhi and Guy Lapalme, "Algorithms: A Functional Programming Approach" <https://goo.gl/00BxTO>`_

Immutable data structures available in ``fn.immutable``:

- ``LinkedList``: the most "obvious" persistent data structure, used as building
  block for other list-based structures (stack, queue)
- ``Stack``: wraps linked list implementation with well-known pop/push API
- ``Queue``: uses two linked lists and lazy copy to provide O(1) enqueue and
  dequeue operations
- ``Deque`` (in progress): `"Confluently Persistent Deques via Data
  Structural Bootstrapping" <https://goo.gl/vVTzx3>`_
- ``Deque`` based on ``FingerTree`` data structure (see more information below)
- ``Vector``: O(log32(n)) access to elements by index (which is near-O(1) for
  reasonable vector size), implementation is based on ``BitmappedTrie``, almost
  drop-in replacement for built-in Python ``list``
- ``SkewHeap``: self-adjusting heap implemented as a binary tree with specific
  branching model, uses heap merge as basic operation, more information -
  `"Self-adjusting heaps" <https://goo.gl/R1PZME>`_
- ``PairingHeap``: `"The Pairing-Heap: A New Form of Self-Adjusting Heap"
  <https://goo.gl/aiVtPH>`_
- ``Dict`` (in progress): persistent hash map implementation based on
  ``BitmappedTrie``
- ``FingerTree`` (in progress): `"Finger Trees: A Simple General-purpose Data
  Structure" <https://goo.gl/Bzo0df>`_

To get a clearer vision of how persistent heaps work (``SkewHeap`` and
``PairingHeap``), you can look at slides from my talk `"Union-based heaps"
<https://goo.gl/VMgdG2>`_ (with analyzed data structures definitions in Python
and Haskell).

**Note.** Most functional languages use persistent data structures as basic
building blocks, well-known examples are Clojure, Haskell and Scala.  Clojure
community puts much effort to popularize programming based on the idea of data
immutability.  There are few amazing talk given by Rich Hickey (creator of
Clojure), you can check them to find answers on both questions "How?" and
"Why?":

- `"The Value of Values" <https://goo.gl/137UG5>`_
- `"Persistent Data Structures and Managed References" <https://goo.gl/M3vZ7E>`_

Streams and infinite sequences declaration
------------------------------------------

Lazy-evaluated Scala-style streams.  Basic idea: evaluate each new element "on
demand" and share consumed elements between all created iterators.  A ``Stream``
instance supports ``<<`` to push new elements.

::

    >>> from fn import Stream

    >>> s = Stream() << [1,2,3,4,5]
    >>> list(s)
    [1, 2, 3, 4, 5]
    >>> s[1]
    2
    >>> list(s[0:2])
    [1, 2]

    >>> s = Stream() << range(6) << [6,7]
    >>> list(s)
    [0, 1, 2, 3, 4, 5, 6, 7]

    >>> def gen():
    ...     yield 1
    ...     yield 2
    ...     yield 3

    >>> s = Stream() << gen << (4,5)
    >>> list(s)
    [1, 2, 3, 4, 5]

Lazy-evaluated streams are useful for infinite sequences, e.g. the fibonacci
sequence can be computed as::

    >>> from fn.iters import take, drop, map
    >>> from operator import add

    >>> f = Stream()
    >>> fib = f << [0, 1] << map(add, f, drop(1, f))

    >>> list(take(10, fib))
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    >>> fib[20]
    6765
    >>> list(fib[30:35])
    [832040, 1346269, 2178309, 3524578, 5702887]

Trampolines decorator
---------------------

``fn.recur.tco`` simulates TCO (tail call optimization).  Let's start with a
simple example of recursive factorial computation::

    >>> def fact(n):
    ...    if n == 0: return 1
    ...    return n * fact(n-1)

This variant works, but it's really inefficient.  Why?  It will consume too much
memory, linear in the depth of the recursion: if you execute this function
with a big ``n`` (more than ``sys.getrecursionlimit()``) CPython will fail::

    >>> import sys
    >>> fact(sys.getrecursionlimit() * 2)
    Traceback (most recent call last):
        ...
    RecursionError: maximum recursion depth exceeded ...

Which is good, as it prevents you from terrible mistakes in your code.

How can we optimize this function?  Easy, let's transform it to use a tail
call::

    def fact(n, acc=1):
        if n == 0: return acc
        return fact(n-1, acc*n)

Is this variant better?  Yes, because you don't need to remember previous values
(local variables) to get the final result.  More about `tail call optimization
<https://en.wikipedia.org/wiki/Tail_call>`_ on Wikipedia.  But... the Python
interpreter will execute this function the same way as the previous one, so you
won't win anything here.

``fn.recur.tco`` allows you to optimize a bit this tail call recursion (using a
"trampoline" approach)::

    >>> from fn import recur

    >>> @recur.tco
    ... def fact(n, acc=1):
    ...    if n == 0: return False, acc
    ...    return True, (n-1, acc*n)

``@recur.tco`` executes your function in a ``while`` loop and checks the output:

- ``(False, result)`` means that we finished,
- ``(True, args, kwargs)`` means that we need to recurse,
- ``(func, args, kwargs)`` switches the function executed inside the while loop.

Example for the third case, to recursively check the parity of a number::

    >>> @recur.tco
    ... def even(x):
    ...     if x == 0: return False, True
    ...     return odd, (x-1,)
    ...
    >>> @recur.tco
    ... def odd(x):
    ...     if x == 0: return False, False
    ...     return even, (x-1,)
    ...
    >>> even(100000)
    True

**Attention:** be careful with mutable/immutable data structures processing.

Itertools recipes
-----------------

``fn.uniform`` unifies generator functions between Python versions (use
generator versions of ``map, filter, reduce, zip, range, filterfalse,
zip_longest``, backported ``accumulate``).

``fn.iters`` offers high-level recipes for working with iterators, most of them
are from the `itertools docs
<https://docs.python.org/3/library/itertools.html#itertools-recipes>`_ and
adapted for Python 2+/3+.

-  ``take``, ``drop``
-  ``takelast``, ``droplast``
-  ``head`` (alias: ``first``), ``tail`` (alias: ``rest``)
-  ``second``, ``ffirst``
-  ``compact``, ``reject``
-  ``every``, ``some``
-  ``iterate``
-  ``consume``
-  ``nth``
-  ``padnone``, ``ncycles``
-  ``repeatfunc``
-  ``grouper``, ``powerset``, ``pairwise``
-  ``roundrobin``
-  ``partition``, ``splitat``, ``splitby``
-  ``flatten``
-  ``iter_except``
-  ``first_true``

See the `docstrings <fn/iters.py>`_ and `tests <tests/test_iterators.py>`_ for
more information.

High-level operations with functions
------------------------------------

``fn.F`` wraps functions for easy-to-use partial application and composition::

    >>> from fn import F
    >>> from operator import add, mul

    # F(f, *args) means partial application
    # same as functools.partial but returns fn.F instance
    >>> F(add, 1)(10)
    11

    # F << F means functions composition,
    # so (F(f) << g)(x) == f(g(x))
    >>> f = F(add, 1) << F(mul, 100)
    >>> list(map(f, [0, 1, 2]))
    [1, 101, 201]
    >>> list(map(F() << str << (_ ** 2) << (_ + 1), range(3)))
    ['1', '4', '9']

You can also pipe functions with ``>>``::

    >>> from fn.iters import filter, range

    >>> func = F() >> (filter, _ < 6) >> sum
    >>> func(range(10))
    15

You can find more examples in the ``fn._`` `implementation <fn/underscore.py>`_.

``fn.op.apply`` executes a function with given args and kwargs.  ``fn.op.flip``
wraps a function, flipping the order of its arguments.

::

    >>> from fn.op import apply, flip
    >>> from operator import add, sub

    >>> apply(add, [1, 2])
    3
    >>> flip(sub)(20, 10)
    -10
    >>> list(map(apply, [add, mul], [(1 ,2), (10, 20)]))
    [3, 200]

``fn.op.foldl`` and ``fn.op.foldr`` create a reducer from a function of two
arguments (think of it as curried ``reduce``).

::

    >>> from fn import op
    >>> op.foldl(_ + _)([1,2,3])
    6
    >>> mult = op.foldr(_ * _, 1)
    >>> mult([1,2,3])
    6
    >>> op.foldr(op.call, 0 )([_ ** 2, _ + 10])
    100
    >>> op.foldr(op.call, 10)([_ ** 2, _ + 10])
    400


Function currying
-----------------

::

    >>> from fn.func import curried
    >>> @curried
    ... def sum5(a, b, c, d, e):
    ...     return a + b + c + d + e
    ...
    >>> sum5(1)(2)(3)(4)(5)
    15
    >>> sum5(1, 2, 3)(4, 5)
    15


Functional style error-handling
-----------------------------------

``fn.monad.Option`` represents optional values, each instance of ``Option`` can
be either ``Full`` or ``Empty``.  It provides you with a simple way to write
long computation sequences and get rid of many ``if/else`` blocks.  See usage
examples below.

Assume that you have a ``Request`` class that gives you a parameter value by its
name, and you have to convert it to uppercase and strip it::

    >>> class Request(dict):
    ...     def parameter(self, name):
    ...         return self.get(name, None)

    >>> r = Request(testing="Fixed", empty="   ")
    >>> param = r.parameter("testing")
    >>> if param is None:
    ...     fixed = ""
    ... else:
    ...     param = param.strip()
    ...     if len(param) == 0:
    ...         fixed = ""
    ...     else:
    ...         fixed = param.upper()
    >>> fixed
    'FIXED'


Hmm, looks ugly..  But now with ``fn.monad.Option``::

    >>> from operator import methodcaller
    >>> from fn.monad import optionable

    >>> class Request(dict):
    ...     @optionable
    ...     def parameter(self, name):
    ...         return self.get(name, None)

    >>> r = Request(testing="Fixed", empty="   ")
    >>> (r
    ...     .parameter("testing")
    ...     .map(methodcaller("strip"))
    ...     .filter(len)
    ...     .map(methodcaller("upper"))
    ...     .get_or("")
    ... )
    'FIXED'

Use ``or_call`` for more complex alternatives, for example::

    from fn.monad import Option

    request = dict(url="face.png", mimetype="PNG")
    tp = (Option 
        .from_value(request.get("type", None))  # check "type" key first
        .or_call(from_mimetype, request)  # or.. check "mimetype" key
        .or_call(from_extension, request)  # or... get "url" and check extension
        .get_or("application/undefined")
    )


Installation
------------

To install ``fn.py``, simply::

    $ pip install fn.py

You can also build library from source

::

    $ git clone https://github.com/fnpy/fn.py.git
    $ pip install -e fn.py

Work in progress
----------------

"Roadmap":

- ``fn.monad.Either`` to deal with error logging
-  C-accelerator for most modules

Ideas to think about:

-  Scala-style for-yield loop to simplify long map/filter blocks

Contribute
----------

If you find a bug:

1. Check for open issues or open a fresh issue to start a discussion
   around a feature idea or a bug.
2. Fork the repository on Github to start making your changes to the
   master branch (or branch off of it).
3. Write a test which shows that the bug was fixed or that the feature
   works as expected.

If you like fixing bugs:

1. Check for open issues with the label "Help Wanted" and either claim
   it or collaborate with those who have claimed it.
2. Fork the repository on Github to start making your changes to the
   master branch (or branch off of it).
3. Write a test which shows that the bug was fixed or that the feature
   works as expected.

How to contact the maintainers
------------------------------

- Gitter: https://gitter.im/fnpy/fn.py
- Jacob's (Organization Owner) Email: him <at> jacobandkate143.com
- Alex's (Original Project Owner) Email: kachayev <at> gmail.com


Changelog
=========

(v0.6.0) June 14, 2023
----------------------

Commit `ef005bd <https://github.com/fnpy/fn.py/commit/ef005bdc89aae0494b18792834a1dd47a027036c>`_ by `jacobbridges <https://github.com/jacobbridges>`_ on Jun 14, 2023

- `Pull Request #46 <https://github.com/fnpy/fn.py/pull/46>`_
- Switch from TravisCI to Github Actions


Commit `d8c21ab <https://github.com/fnpy/fn.py/commit/d8c21abef1db3bf558b4d90469483461d4149210>`_ by `artemisart <https://github.com/artemisart>`_ on Apr 25, 2020

- `Pull Request #40 <https://github.com/fnpy/fn.py/pull/40>`_
- Documentation + some fixes


Commit `9958f1f <https://github.com/fnpy/fn.py/commit/9958f1f8678f27239760e17a8866288875519a7e>`_ by `soasme <https://github.com/soasme>`_ on Sep 5, 2019

- `Pull Request #39 <https://github.com/fnpy/fn.py/pull/39>`_
- Import Iterable from collections.abc for >=3.8


Commit `486119e <https://github.com/fnpy/fn.py/commit/486119e4d25892c8a8052826fd392601ad6e4d4e>`_ by `fwfurtado <https://github.com/fwfurtado>`_ on Sep 5, 2019

- `Pull Request #37 <https://github.com/fnpy/fn.py/pull/37>`_
- Added supporting to type hint in curried decorator


Commit `c01c2b7 <https://github.com/fnpy/fn.py/commit/c01c2b7235e0ef8b65a53b91df560c7e01c50027>`_ by `katoken-0215 <https://github.com/katoken-0215>`_ on Mar 24, 2018

- `Pull Request #33 <https://github.com/fnpy/fn.py/pull/33>`_
- Introduce guard for recursive call by doctest.

(v0.5.0) July 17, 2017
----------------------

Commit: `10efa8b <https://github.com/fnpy/fn.py/commit/10efa8b35c327ae77dfb01878451694bd5a47ea9>`_.

- Added ``recur.stackless()`` Provides a "stackless" (constant Python stack space) recursion decorator for generators.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/fnpy/fn.py",
    "name": "fn.py",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "functional,fp,utility",
    "author": "fnpy team",
    "author_email": "vash0the0stampede@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/bd/0e/8908d24ddde153ef461297354f3707391a34cdab8c50c5ae705d73ceb9b9/fn.py-0.6.0.tar.gz",
    "platform": null,
    "description": "Fn.py: enjoy FP in Python\n=========================\n\n.. image:: https://badges.gitter.im/fnpy/fn.py.svg\n   :alt: Join the chat at https://gitter.im/fnpy/fn.py\n   :target: https://gitter.im/fnpy/fn.py?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge\n\n.. image:: https://travis-ci.org/fnpy/fn.py.svg?branch=master\n    :target: https://travis-ci.org/fnpy/fn.py\n\n.. image:: https://img.shields.io/pypi/v/fn.py\n    :alt: PyPI\n    :target: https://pypi.org/project/fn.py\n\n.. image:: https://img.shields.io/pypi/dm/fn.py\n    :alt: PyPI - Downloads\n    :target: https://pypi.org/project/fn.py\n\nDespite the fact that Python is not a pure-functional programming language, it\nis multi-paradigm and gives you enough freedom to take advantage of a functional\napproach.  There are theoretical and practical advantages to the functional\nstyle:\n\n-  Formal provability\n-  Modularity\n-  Composability\n-  Ease of debugging and testing\n\n``Fn.py`` library provides you with the missing \"batteries\" to get the maximum\nfrom a functional approach, even in mostly-imperative softwares.\n\nYou can find more about the functional approach from my Pycon UA 2012 talks:\n`Functional Programming with Python\n<https://kachayev.github.com/talks/uapycon2012/index.html>`_.\n\nScala-style lambdas definition\n------------------------------\n\n::\n\n    >>> from fn import _\n    >>> from fn.op import zipwith\n    >>> from itertools import repeat\n\n    >>> list(map(_ * 2, range(5)))\n    [0, 2, 4, 6, 8]\n    >>> list(filter(_ < 10, [9,10,11]))\n    [9]\n    >>> list(zipwith(_ + _)([0,1,2], repeat(10)))\n    [10, 11, 12]\n\nYou can find more examples of ``_`` in `test cases <tests/test_underscore.py>`_\n(attributes resolving, method calling, slicing).\n\n**Attention!** If you work in an interactive python shell, ``_`` can be assigned\nto the latest output and you'll get unpredictable results.  In this case, you\ncan use ``X`` instead with ``from fn import _ as X``.\n\nIf you are not sure what your function is going to do, print it::\n\n    >>> print(_ + 2)\n    (x1) => (x1 + 2)\n    >>> print(_ + _ * _)\n    (x1, x2, x3) => (x1 + (x2 * x3))\n\nNote that ``_`` will fail with ``ArityError`` (``TypeError`` subclass) when\ncalled with the wrong number of arguments, so as to avoid errors::\n\n    >>> (_ + _)(1)\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n      File \"fn/underscore.py\", line 82, in __call__\n        raise ArityError(self, self._arity, len(args))\n    fn.underscore.ArityError: (_ + _) expected 2 arguments, got 1\n\n\nPersistent data structures\n--------------------------\n\n**Attention:** Persistent data structures are under active development.\n\nA persistent data structure always preserves its previous version when it is\nmodified (more on `Wikipedia <https://goo.gl/8VveOH>`_).  Each operation thus\nyields a new updated structure instead of performing in-place modifications (all\nprevious versions are potentially available or GC-ed when possible).\n\n::\n\n    >>> from fn.immutable import SkewHeap\n    >>> s1 = SkewHeap(10)\n    >>> s2 = s1.insert(20)\n    >>> s2\n    <fn.immutable.heap.SkewHeap object at 0x...>\n    >>> s3 = s2.insert(30)\n    >>> s3\n    <fn.immutable.heap.SkewHeap object at 0x...>\n    >>> id(s3) != id(s2)\n    True\n    >>> s3.extract()\n    (10, <fn.immutable.heap.SkewHeap object at 0x...>)\n    >>> s3.extract() # <-- s3 isn't changed\n    (10, <fn.immutable.heap.SkewHeap object at 0x...>)\n\nIf you think I'm totally crazy and it will work despairingly slow, just give it\n5 minutes.  Relax, take a deep breath and read about a few techniques that make\npersistent data structures fast and efficient: `structural sharing\n<https://en.wikipedia.org/wiki/Persistent_data_structure#Examples_of_persistent_data_structures>`_\nand `path copying\n<https://en.wikipedia.org/wiki/Persistent_data_structure#Path_Copying>`_.  To\nsee how it works in \"pictures\", you can check the great slides from Zach\nAllaun's talk (StrangeLoop 2013): `\"Functional Vectors, Maps And Sets In Julia\"\n<https://goo.gl/Cp1Qsq>`_.  And, if you are brave enough, go and read:\n\n- `Chris Okasaki, \"Purely Functional Data Structures\" <https://goo.gl/c7ptkk>`_\n- `Fethi Rabhi and Guy Lapalme, \"Algorithms: A Functional Programming Approach\" <https://goo.gl/00BxTO>`_\n\nImmutable data structures available in ``fn.immutable``:\n\n- ``LinkedList``: the most \"obvious\" persistent data structure, used as building\n  block for other list-based structures (stack, queue)\n- ``Stack``: wraps linked list implementation with well-known pop/push API\n- ``Queue``: uses two linked lists and lazy copy to provide O(1) enqueue and\n  dequeue operations\n- ``Deque`` (in progress): `\"Confluently Persistent Deques via Data\n  Structural Bootstrapping\" <https://goo.gl/vVTzx3>`_\n- ``Deque`` based on ``FingerTree`` data structure (see more information below)\n- ``Vector``: O(log32(n)) access to elements by index (which is near-O(1) for\n  reasonable vector size), implementation is based on ``BitmappedTrie``, almost\n  drop-in replacement for built-in Python ``list``\n- ``SkewHeap``: self-adjusting heap implemented as a binary tree with specific\n  branching model, uses heap merge as basic operation, more information -\n  `\"Self-adjusting heaps\" <https://goo.gl/R1PZME>`_\n- ``PairingHeap``: `\"The Pairing-Heap: A New Form of Self-Adjusting Heap\"\n  <https://goo.gl/aiVtPH>`_\n- ``Dict`` (in progress): persistent hash map implementation based on\n  ``BitmappedTrie``\n- ``FingerTree`` (in progress): `\"Finger Trees: A Simple General-purpose Data\n  Structure\" <https://goo.gl/Bzo0df>`_\n\nTo get a clearer vision of how persistent heaps work (``SkewHeap`` and\n``PairingHeap``), you can look at slides from my talk `\"Union-based heaps\"\n<https://goo.gl/VMgdG2>`_ (with analyzed data structures definitions in Python\nand Haskell).\n\n**Note.** Most functional languages use persistent data structures as basic\nbuilding blocks, well-known examples are Clojure, Haskell and Scala.  Clojure\ncommunity puts much effort to popularize programming based on the idea of data\nimmutability.  There are few amazing talk given by Rich Hickey (creator of\nClojure), you can check them to find answers on both questions \"How?\" and\n\"Why?\":\n\n- `\"The Value of Values\" <https://goo.gl/137UG5>`_\n- `\"Persistent Data Structures and Managed References\" <https://goo.gl/M3vZ7E>`_\n\nStreams and infinite sequences declaration\n------------------------------------------\n\nLazy-evaluated Scala-style streams.  Basic idea: evaluate each new element \"on\ndemand\" and share consumed elements between all created iterators.  A ``Stream``\ninstance supports ``<<`` to push new elements.\n\n::\n\n    >>> from fn import Stream\n\n    >>> s = Stream() << [1,2,3,4,5]\n    >>> list(s)\n    [1, 2, 3, 4, 5]\n    >>> s[1]\n    2\n    >>> list(s[0:2])\n    [1, 2]\n\n    >>> s = Stream() << range(6) << [6,7]\n    >>> list(s)\n    [0, 1, 2, 3, 4, 5, 6, 7]\n\n    >>> def gen():\n    ...     yield 1\n    ...     yield 2\n    ...     yield 3\n\n    >>> s = Stream() << gen << (4,5)\n    >>> list(s)\n    [1, 2, 3, 4, 5]\n\nLazy-evaluated streams are useful for infinite sequences, e.g. the fibonacci\nsequence can be computed as::\n\n    >>> from fn.iters import take, drop, map\n    >>> from operator import add\n\n    >>> f = Stream()\n    >>> fib = f << [0, 1] << map(add, f, drop(1, f))\n\n    >>> list(take(10, fib))\n    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n    >>> fib[20]\n    6765\n    >>> list(fib[30:35])\n    [832040, 1346269, 2178309, 3524578, 5702887]\n\nTrampolines decorator\n---------------------\n\n``fn.recur.tco`` simulates TCO (tail call optimization).  Let's start with a\nsimple example of recursive factorial computation::\n\n    >>> def fact(n):\n    ...    if n == 0: return 1\n    ...    return n * fact(n-1)\n\nThis variant works, but it's really inefficient.  Why?  It will consume too much\nmemory, linear in the depth of the recursion: if you execute this function\nwith a big ``n`` (more than ``sys.getrecursionlimit()``) CPython will fail::\n\n    >>> import sys\n    >>> fact(sys.getrecursionlimit() * 2)\n    Traceback (most recent call last):\n        ...\n    RecursionError: maximum recursion depth exceeded ...\n\nWhich is good, as it prevents you from terrible mistakes in your code.\n\nHow can we optimize this function?  Easy, let's transform it to use a tail\ncall::\n\n    def fact(n, acc=1):\n        if n == 0: return acc\n        return fact(n-1, acc*n)\n\nIs this variant better?  Yes, because you don't need to remember previous values\n(local variables) to get the final result.  More about `tail call optimization\n<https://en.wikipedia.org/wiki/Tail_call>`_ on Wikipedia.  But... the Python\ninterpreter will execute this function the same way as the previous one, so you\nwon't win anything here.\n\n``fn.recur.tco`` allows you to optimize a bit this tail call recursion (using a\n\"trampoline\" approach)::\n\n    >>> from fn import recur\n\n    >>> @recur.tco\n    ... def fact(n, acc=1):\n    ...    if n == 0: return False, acc\n    ...    return True, (n-1, acc*n)\n\n``@recur.tco`` executes your function in a ``while`` loop and checks the output:\n\n- ``(False, result)`` means that we finished,\n- ``(True, args, kwargs)`` means that we need to recurse,\n- ``(func, args, kwargs)`` switches the function executed inside the while loop.\n\nExample for the third case, to recursively check the parity of a number::\n\n    >>> @recur.tco\n    ... def even(x):\n    ...     if x == 0: return False, True\n    ...     return odd, (x-1,)\n    ...\n    >>> @recur.tco\n    ... def odd(x):\n    ...     if x == 0: return False, False\n    ...     return even, (x-1,)\n    ...\n    >>> even(100000)\n    True\n\n**Attention:** be careful with mutable/immutable data structures processing.\n\nItertools recipes\n-----------------\n\n``fn.uniform`` unifies generator functions between Python versions (use\ngenerator versions of ``map, filter, reduce, zip, range, filterfalse,\nzip_longest``, backported ``accumulate``).\n\n``fn.iters`` offers high-level recipes for working with iterators, most of them\nare from the `itertools docs\n<https://docs.python.org/3/library/itertools.html#itertools-recipes>`_ and\nadapted for Python 2+/3+.\n\n-  ``take``, ``drop``\n-  ``takelast``, ``droplast``\n-  ``head`` (alias: ``first``), ``tail`` (alias: ``rest``)\n-  ``second``, ``ffirst``\n-  ``compact``, ``reject``\n-  ``every``, ``some``\n-  ``iterate``\n-  ``consume``\n-  ``nth``\n-  ``padnone``, ``ncycles``\n-  ``repeatfunc``\n-  ``grouper``, ``powerset``, ``pairwise``\n-  ``roundrobin``\n-  ``partition``, ``splitat``, ``splitby``\n-  ``flatten``\n-  ``iter_except``\n-  ``first_true``\n\nSee the `docstrings <fn/iters.py>`_ and `tests <tests/test_iterators.py>`_ for\nmore information.\n\nHigh-level operations with functions\n------------------------------------\n\n``fn.F`` wraps functions for easy-to-use partial application and composition::\n\n    >>> from fn import F\n    >>> from operator import add, mul\n\n    # F(f, *args) means partial application\n    # same as functools.partial but returns fn.F instance\n    >>> F(add, 1)(10)\n    11\n\n    # F << F means functions composition,\n    # so (F(f) << g)(x) == f(g(x))\n    >>> f = F(add, 1) << F(mul, 100)\n    >>> list(map(f, [0, 1, 2]))\n    [1, 101, 201]\n    >>> list(map(F() << str << (_ ** 2) << (_ + 1), range(3)))\n    ['1', '4', '9']\n\nYou can also pipe functions with ``>>``::\n\n    >>> from fn.iters import filter, range\n\n    >>> func = F() >> (filter, _ < 6) >> sum\n    >>> func(range(10))\n    15\n\nYou can find more examples in the ``fn._`` `implementation <fn/underscore.py>`_.\n\n``fn.op.apply`` executes a function with given args and kwargs.  ``fn.op.flip``\nwraps a function, flipping the order of its arguments.\n\n::\n\n    >>> from fn.op import apply, flip\n    >>> from operator import add, sub\n\n    >>> apply(add, [1, 2])\n    3\n    >>> flip(sub)(20, 10)\n    -10\n    >>> list(map(apply, [add, mul], [(1 ,2), (10, 20)]))\n    [3, 200]\n\n``fn.op.foldl`` and ``fn.op.foldr`` create a reducer from a function of two\narguments (think of it as curried ``reduce``).\n\n::\n\n    >>> from fn import op\n    >>> op.foldl(_ + _)([1,2,3])\n    6\n    >>> mult = op.foldr(_ * _, 1)\n    >>> mult([1,2,3])\n    6\n    >>> op.foldr(op.call, 0 )([_ ** 2, _ + 10])\n    100\n    >>> op.foldr(op.call, 10)([_ ** 2, _ + 10])\n    400\n\n\nFunction currying\n-----------------\n\n::\n\n    >>> from fn.func import curried\n    >>> @curried\n    ... def sum5(a, b, c, d, e):\n    ...     return a + b + c + d + e\n    ...\n    >>> sum5(1)(2)(3)(4)(5)\n    15\n    >>> sum5(1, 2, 3)(4, 5)\n    15\n\n\nFunctional style error-handling\n-----------------------------------\n\n``fn.monad.Option`` represents optional values, each instance of ``Option`` can\nbe either ``Full`` or ``Empty``.  It provides you with a simple way to write\nlong computation sequences and get rid of many ``if/else`` blocks.  See usage\nexamples below.\n\nAssume that you have a ``Request`` class that gives you a parameter value by its\nname, and you have to convert it to uppercase and strip it::\n\n    >>> class Request(dict):\n    ...     def parameter(self, name):\n    ...         return self.get(name, None)\n\n    >>> r = Request(testing=\"Fixed\", empty=\"   \")\n    >>> param = r.parameter(\"testing\")\n    >>> if param is None:\n    ...     fixed = \"\"\n    ... else:\n    ...     param = param.strip()\n    ...     if len(param) == 0:\n    ...         fixed = \"\"\n    ...     else:\n    ...         fixed = param.upper()\n    >>> fixed\n    'FIXED'\n\n\nHmm, looks ugly..  But now with ``fn.monad.Option``::\n\n    >>> from operator import methodcaller\n    >>> from fn.monad import optionable\n\n    >>> class Request(dict):\n    ...     @optionable\n    ...     def parameter(self, name):\n    ...         return self.get(name, None)\n\n    >>> r = Request(testing=\"Fixed\", empty=\"   \")\n    >>> (r\n    ...     .parameter(\"testing\")\n    ...     .map(methodcaller(\"strip\"))\n    ...     .filter(len)\n    ...     .map(methodcaller(\"upper\"))\n    ...     .get_or(\"\")\n    ... )\n    'FIXED'\n\nUse ``or_call`` for more complex alternatives, for example::\n\n    from fn.monad import Option\n\n    request = dict(url=\"face.png\", mimetype=\"PNG\")\n    tp = (Option \n        .from_value(request.get(\"type\", None))  # check \"type\" key first\n        .or_call(from_mimetype, request)  # or.. check \"mimetype\" key\n        .or_call(from_extension, request)  # or... get \"url\" and check extension\n        .get_or(\"application/undefined\")\n    )\n\n\nInstallation\n------------\n\nTo install ``fn.py``, simply::\n\n    $ pip install fn.py\n\nYou can also build library from source\n\n::\n\n    $ git clone https://github.com/fnpy/fn.py.git\n    $ pip install -e fn.py\n\nWork in progress\n----------------\n\n\"Roadmap\":\n\n- ``fn.monad.Either`` to deal with error logging\n-  C-accelerator for most modules\n\nIdeas to think about:\n\n-  Scala-style for-yield loop to simplify long map/filter blocks\n\nContribute\n----------\n\nIf you find a bug:\n\n1. Check for open issues or open a fresh issue to start a discussion\n   around a feature idea or a bug.\n2. Fork the repository on Github to start making your changes to the\n   master branch (or branch off of it).\n3. Write a test which shows that the bug was fixed or that the feature\n   works as expected.\n\nIf you like fixing bugs:\n\n1. Check for open issues with the label \"Help Wanted\" and either claim\n   it or collaborate with those who have claimed it.\n2. Fork the repository on Github to start making your changes to the\n   master branch (or branch off of it).\n3. Write a test which shows that the bug was fixed or that the feature\n   works as expected.\n\nHow to contact the maintainers\n------------------------------\n\n- Gitter: https://gitter.im/fnpy/fn.py\n- Jacob's (Organization Owner) Email: him <at> jacobandkate143.com\n- Alex's (Original Project Owner) Email: kachayev <at> gmail.com\n\n\nChangelog\n=========\n\n(v0.6.0) June 14, 2023\n----------------------\n\nCommit `ef005bd <https://github.com/fnpy/fn.py/commit/ef005bdc89aae0494b18792834a1dd47a027036c>`_ by `jacobbridges <https://github.com/jacobbridges>`_ on Jun 14, 2023\n\n- `Pull Request #46 <https://github.com/fnpy/fn.py/pull/46>`_\n- Switch from TravisCI to Github Actions\n\n\nCommit `d8c21ab <https://github.com/fnpy/fn.py/commit/d8c21abef1db3bf558b4d90469483461d4149210>`_ by `artemisart <https://github.com/artemisart>`_ on Apr 25, 2020\n\n- `Pull Request #40 <https://github.com/fnpy/fn.py/pull/40>`_\n- Documentation + some fixes\n\n\nCommit `9958f1f <https://github.com/fnpy/fn.py/commit/9958f1f8678f27239760e17a8866288875519a7e>`_ by `soasme <https://github.com/soasme>`_ on Sep 5, 2019\n\n- `Pull Request #39 <https://github.com/fnpy/fn.py/pull/39>`_\n- Import Iterable from collections.abc for >=3.8\n\n\nCommit `486119e <https://github.com/fnpy/fn.py/commit/486119e4d25892c8a8052826fd392601ad6e4d4e>`_ by `fwfurtado <https://github.com/fwfurtado>`_ on Sep 5, 2019\n\n- `Pull Request #37 <https://github.com/fnpy/fn.py/pull/37>`_\n- Added supporting to type hint in curried decorator\n\n\nCommit `c01c2b7 <https://github.com/fnpy/fn.py/commit/c01c2b7235e0ef8b65a53b91df560c7e01c50027>`_ by `katoken-0215 <https://github.com/katoken-0215>`_ on Mar 24, 2018\n\n- `Pull Request #33 <https://github.com/fnpy/fn.py/pull/33>`_\n- Introduce guard for recursive call by doctest.\n\n(v0.5.0) July 17, 2017\n----------------------\n\nCommit: `10efa8b <https://github.com/fnpy/fn.py/commit/10efa8b35c327ae77dfb01878451694bd5a47ea9>`_.\n\n- Added ``recur.stackless()`` Provides a \"stackless\" (constant Python stack space) recursion decorator for generators.\n",
    "bugtrack_url": null,
    "license": "Copyright 2013 Alexey Kachayev  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at  http://www.apache.org/licenses/LICENSE-2.0  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ",
    "summary": "Implementation of missing features to enjoy functional programming in Python",
    "version": "0.6.0",
    "project_urls": {
        "Homepage": "https://github.com/fnpy/fn.py"
    },
    "split_keywords": [
        "functional",
        "fp",
        "utility"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "0b561e08a717acc540890ac0ef5eb42a9bfda341a4bc89031fbe41d2aa1f2eb5",
                "md5": "e0b144a57296e99644eaa7c0fd0d865c",
                "sha256": "31a6c596d60e3c236aec8097270259c53c3635de4327927a2fd1928a56116658"
            },
            "downloads": -1,
            "filename": "fn.py-0.6.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e0b144a57296e99644eaa7c0fd0d865c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 29031,
            "upload_time": "2023-06-14T19:54:12",
            "upload_time_iso_8601": "2023-06-14T19:54:12.913806Z",
            "url": "https://files.pythonhosted.org/packages/0b/56/1e08a717acc540890ac0ef5eb42a9bfda341a4bc89031fbe41d2aa1f2eb5/fn.py-0.6.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bd0e8908d24ddde153ef461297354f3707391a34cdab8c50c5ae705d73ceb9b9",
                "md5": "69c513ed7e59996f57f8955e603a9578",
                "sha256": "97b7e2d625cbcb9b322d4dbc57386f9e454da23617347abe1854fed434e0cb10"
            },
            "downloads": -1,
            "filename": "fn.py-0.6.0.tar.gz",
            "has_sig": false,
            "md5_digest": "69c513ed7e59996f57f8955e603a9578",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 41474,
            "upload_time": "2023-06-14T19:54:14",
            "upload_time_iso_8601": "2023-06-14T19:54:14.109157Z",
            "url": "https://files.pythonhosted.org/packages/bd/0e/8908d24ddde153ef461297354f3707391a34cdab8c50c5ae705d73ceb9b9/fn.py-0.6.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-14 19:54:14",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "fnpy",
    "github_project": "fn.py",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "fn.py"
}
        
Elapsed time: 0.08604s