Skip to content

Commit 10efa8b

Browse files
committed
Merge branch 'jahs-master'
2 parents 92f579a + ae9f3ea commit 10efa8b

File tree

11 files changed

+116
-19
lines changed

11 files changed

+116
-19
lines changed

.travis.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@ sudo: false
22
language: python
33
python: 3.5
44
env:
5+
- TOXENV=codestyle
56
- TOXENV=py26
67
- TOXENV=py27
78
- TOXENV=py33
89
- TOXENV=py34
910
- TOXENV=py35
1011
- TOXENV=pypy
11-
- TOXENV=pypy3
1212
install: pip install tox
1313
script: tox
14+
matrix:
15+
include:
16+
- python: 3.6
17+
env: TOXENV=py36
18+
- python: pypy3
19+
env: TOXENV=pypy3
1420

1521
notifications:
1622
email: false

README.rst

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -436,19 +436,13 @@ To install ``fn.py``, simply:
436436

437437
.. code-block:: console
438438
439-
$ pip install fn
440-
441-
Or, if you absolutely must:
442-
443-
.. code-block:: console
444-
445-
$ easy_install fn
439+
$ pip install fn.py
446440
447441
You can also build library from source
448442

449443
.. code-block:: console
450444
451-
$ git clone https://github.com/kachayev/fn.py.git
445+
$ git clone https://github.com/fnpy/fn.py.git
452446
$ cd fn.py
453447
$ python setup.py install
454448

fn/immutable/list.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,9 @@ def __radd__(self, el):
4747
return self.cons(el)
4848

4949
def __iter__(self):
50-
l = self
51-
while l:
52-
yield l.head
53-
l = l.tail
50+
while self:
51+
yield self.head
52+
self = self.tail
5453

5554
def __len__(self):
5655
return self._count

fn/iters.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def first_true(iterable, default=False, pred=None):
5858
"""
5959
return next(filter(pred, iterable), default)
6060

61+
6162
# widely-spreaded shortcuts to get first item, all but first item,
6263
# second item, and first item of first item from iterator respectively
6364
head = first = partial(flip(nth), 0)
@@ -251,6 +252,7 @@ def flatten(items):
251252
else:
252253
yield item
253254

255+
254256
if version_info[0] == 3 and version_info[1] >= 3:
255257
from itertools import accumulate
256258
else:

fn/op.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
def _apply(f, args=None, kwargs=None):
99
return f(*(args or []), **(kwargs or {}))
1010

11+
1112
apply = apply if version_info[0] == 2 else _apply
1213

1314

fn/recur.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
"""Provides decorator to deal with tail calls in recursive function."""
1+
"""Provides decorators to deal with tail calls in recursive functions."""
2+
3+
from collections import namedtuple
24

35

46
class tco(object):
@@ -44,3 +46,90 @@ def __call__(self, *args, **kwargs):
4446
if callable(act):
4547
action = act
4648
kwargs = result[2] if len(result) > 2 else {}
49+
50+
51+
class stackless(object):
52+
"""Provides a "stackless" (constant Python stack space) recursion
53+
decorator for generators.
54+
55+
Invoking as f() creates the control structures. Within a
56+
function, only use `yield f.call()` and `yield f.tailcall()`.
57+
58+
Usage examples:
59+
60+
Tail call optimised recursion with tailcall():
61+
62+
@recur.stackless
63+
def fact(n, acc=1):
64+
if n == 0:
65+
yield acc
66+
return
67+
yield fact.tailcall(n-1, n*acc)
68+
69+
Non-tail recursion with call() uses heap space so won't overflow:
70+
71+
@recur.stackless
72+
def fib(n):
73+
if n == 0:
74+
yield 1
75+
return
76+
if n == 1:
77+
yield 1
78+
return
79+
yield (yield fib.call(n-1)) + (yield fib.call(n-2))
80+
81+
Mutual recursion also works:
82+
83+
@recur.stackless
84+
def is_odd(n):
85+
if n == 0:
86+
yield False
87+
return
88+
yield is_even.tailcall(n-1)
89+
90+
@recur.stackless
91+
def is_even(n):
92+
if n == 0:
93+
yield True
94+
return
95+
yield is_odd.tailcall(n-1)
96+
97+
"""
98+
99+
__slots__ = "func",
100+
101+
Thunk = namedtuple("Thunk", ("func", "args", "kwargs", "is_tailcall"))
102+
103+
def __init__(self, func):
104+
self.func = func
105+
106+
def call(self, *args, **kwargs):
107+
return self.Thunk(self.func, args, kwargs, False)
108+
109+
def tailcall(self, *args, **kwargs):
110+
return self.Thunk(self.func, args, kwargs, True)
111+
112+
def __call__(self, *args, **kwargs):
113+
s = [self.func(*args, **kwargs)]
114+
r = []
115+
v = None
116+
while s:
117+
try:
118+
if r:
119+
v = s[-1].send(r[-1])
120+
r.pop()
121+
else:
122+
v = next(s[-1])
123+
except StopIteration:
124+
s.pop()
125+
continue
126+
127+
if isinstance(v, self.Thunk):
128+
g = v.func(*v.args, **v.kwargs)
129+
if v.is_tailcall:
130+
s[-1] = g
131+
else:
132+
s.append(g)
133+
else:
134+
r.append(v)
135+
return r[0] if r else None

fn/underscore.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .op import apply, flip, identity
1010
from .uniform import map, zip
1111

12+
1213
div = operator.div if version_info[0] == 2 else operator.truediv
1314

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

193+
192194
shortcut = _Callable()

tests/test_finger_tree.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@ def test_iterator(self):
4141
sum(Deque.from_iterable(range(1, 20)))
4242
)
4343

44+
4445
if __name__ == '__main__':
4546
unittest.main()

tests/test_iterators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,5 @@ def test_accumulate(self):
280280
)
281281

282282
def test_filterfalse(self):
283-
l = iters.filterfalse(lambda x: x > 10, [1, 2, 3, 11, 12])
284-
self.assertEqual([1, 2, 3], list(l))
283+
filtered = iters.filterfalse(lambda x: x > 10, [1, 2, 3, 11, 12])
284+
self.assertEqual([1, 2, 3], list(filtered))

tests/test_underscore.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,7 @@ def test_comparator(self):
146146
self.assertFalse((_ == 10)(9))
147147

148148
def test_none(self):
149-
# FIXME: turning the '==' into 'is' throws an error
150-
self.assertTrue((_ == None)(None))
149+
self.assertTrue((_ == None)(None)) # noqa: E711
151150

152151
class pushlist(list):
153152
def __lshift__(self, item):

0 commit comments

Comments
 (0)