Skip to content

Commit

Permalink
Merge branch 'jahs-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobbridges committed Jul 18, 2017
2 parents 92f579a + ae9f3ea commit 10efa8b
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 19 deletions.
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ sudo: false
language: python
python: 3.5
env:
- TOXENV=codestyle
- TOXENV=py26
- TOXENV=py27
- TOXENV=py33
- TOXENV=py34
- TOXENV=py35
- TOXENV=pypy
- TOXENV=pypy3
install: pip install tox
script: tox
matrix:
include:
- python: 3.6
env: TOXENV=py36
- python: pypy3
env: TOXENV=pypy3

notifications:
email: false
Expand Down
10 changes: 2 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -436,19 +436,13 @@ To install ``fn.py``, simply:

.. code-block:: console
$ pip install fn
Or, if you absolutely must:

.. code-block:: console
$ easy_install fn
$ pip install fn.py
You can also build library from source

.. code-block:: console
$ git clone https://github.com/kachayev/fn.py.git
$ git clone https://github.com/fnpy/fn.py.git
$ cd fn.py
$ python setup.py install
Expand Down
7 changes: 3 additions & 4 deletions fn/immutable/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,9 @@ def __radd__(self, el):
return self.cons(el)

def __iter__(self):
l = self
while l:
yield l.head
l = l.tail
while self:
yield self.head
self = self.tail

def __len__(self):
return self._count
Expand Down
2 changes: 2 additions & 0 deletions fn/iters.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def first_true(iterable, default=False, pred=None):
"""
return next(filter(pred, iterable), default)


# widely-spreaded shortcuts to get first item, all but first item,
# second item, and first item of first item from iterator respectively
head = first = partial(flip(nth), 0)
Expand Down Expand Up @@ -251,6 +252,7 @@ def flatten(items):
else:
yield item


if version_info[0] == 3 and version_info[1] >= 3:
from itertools import accumulate
else:
Expand Down
1 change: 1 addition & 0 deletions fn/op.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
def _apply(f, args=None, kwargs=None):
return f(*(args or []), **(kwargs or {}))


apply = apply if version_info[0] == 2 else _apply


Expand Down
91 changes: 90 additions & 1 deletion fn/recur.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Provides decorator to deal with tail calls in recursive function."""
"""Provides decorators to deal with tail calls in recursive functions."""

from collections import namedtuple


class tco(object):
Expand Down Expand Up @@ -44,3 +46,90 @@ def __call__(self, *args, **kwargs):
if callable(act):
action = act
kwargs = result[2] if len(result) > 2 else {}


class stackless(object):
"""Provides a "stackless" (constant Python stack space) recursion
decorator for generators.
Invoking as f() creates the control structures. Within a
function, only use `yield f.call()` and `yield f.tailcall()`.
Usage examples:
Tail call optimised recursion with tailcall():
@recur.stackless
def fact(n, acc=1):
if n == 0:
yield acc
return
yield fact.tailcall(n-1, n*acc)
Non-tail recursion with call() uses heap space so won't overflow:
@recur.stackless
def fib(n):
if n == 0:
yield 1
return
if n == 1:
yield 1
return
yield (yield fib.call(n-1)) + (yield fib.call(n-2))
Mutual recursion also works:
@recur.stackless
def is_odd(n):
if n == 0:
yield False
return
yield is_even.tailcall(n-1)
@recur.stackless
def is_even(n):
if n == 0:
yield True
return
yield is_odd.tailcall(n-1)
"""

__slots__ = "func",

Thunk = namedtuple("Thunk", ("func", "args", "kwargs", "is_tailcall"))

def __init__(self, func):
self.func = func

def call(self, *args, **kwargs):
return self.Thunk(self.func, args, kwargs, False)

def tailcall(self, *args, **kwargs):
return self.Thunk(self.func, args, kwargs, True)

def __call__(self, *args, **kwargs):
s = [self.func(*args, **kwargs)]
r = []
v = None
while s:
try:
if r:
v = s[-1].send(r[-1])
r.pop()
else:
v = next(s[-1])
except StopIteration:
s.pop()
continue

if isinstance(v, self.Thunk):
g = v.func(*v.args, **v.kwargs)
if v.is_tailcall:
s[-1] = g
else:
s.append(g)
else:
r.append(v)
return r[0] if r else None
2 changes: 2 additions & 0 deletions fn/underscore.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .op import apply, flip, identity
from .uniform import map, zip


div = operator.div if version_info[0] == 2 else operator.truediv

letters = string.letters if version_info[0] == 2 else string.ascii_letters
Expand Down Expand Up @@ -189,4 +190,5 @@ def __call__(self, *args):
__ror__ = fmap(flip(operator.or_), "other | self")
__rxor__ = fmap(flip(operator.xor), "other ^ self")


shortcut = _Callable()
1 change: 1 addition & 0 deletions tests/test_finger_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ def test_iterator(self):
sum(Deque.from_iterable(range(1, 20)))
)


if __name__ == '__main__':
unittest.main()
4 changes: 2 additions & 2 deletions tests/test_iterators.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,5 +280,5 @@ def test_accumulate(self):
)

def test_filterfalse(self):
l = iters.filterfalse(lambda x: x > 10, [1, 2, 3, 11, 12])
self.assertEqual([1, 2, 3], list(l))
filtered = iters.filterfalse(lambda x: x > 10, [1, 2, 3, 11, 12])
self.assertEqual([1, 2, 3], list(filtered))
3 changes: 1 addition & 2 deletions tests/test_underscore.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ def test_comparator(self):
self.assertFalse((_ == 10)(9))

def test_none(self):
# FIXME: turning the '==' into 'is' throws an error
self.assertTrue((_ == None)(None))
self.assertTrue((_ == None)(None)) # noqa: E711

class pushlist(list):
def __lshift__(self, item):
Expand Down
6 changes: 5 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
[tox]
envlist = py{26,27,33,34,35,py,py3}
envlist = py{26,27,33,34,35,36,py,py3},codestyle
skip_missing_interpreters = True

[testenv]
deps = pytest
commands = py.test

[testenv:codestyle]
deps = pycodestyle
commands = pycodestyle

0 comments on commit 10efa8b

Please sign in to comment.