Skip to content

Commit 3f32e99

Browse files
committed
Merge branch 'foldE' into sequence
Conflicts: effect/test_fold.py
2 parents 8d7ee79 + 290f499 commit 3f32e99

4 files changed

Lines changed: 60 additions & 3 deletions

File tree

docs/source/api/effect.fold.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
effect.fold module
2+
==================
3+
4+
.. automodule:: effect.fold
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

docs/source/api/effect.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Submodules
88

99
effect.async
1010
effect.do
11+
effect.fold
1112
effect.ref
1213
effect.retry
1314
effect.testing

effect/fold.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
1+
import traceback
12
from functools import reduce
23

34
from effect import Constant, Effect
45

56

7+
class FoldError(Exception):
8+
"""
9+
Raised when one of the Effects passed to :func:`fold_effect` fails.
10+
11+
:ivar accumulator: The data accumulated so far, before the failing Effect.
12+
:ivar wrapped_exception: The exc_info tuple representing the original
13+
exception raised by the failing Effect.
14+
"""
15+
def __init__(self, accumulator, wrapped_exception):
16+
self.accumulator = accumulator
17+
self.wrapped_exception = wrapped_exception
18+
19+
def __str__(self):
20+
tb_lines = traceback.format_tb(self.wrapped_exception[2])
21+
tb = ''.join(tb_lines)
22+
return "FoldError(%r, %r) -> ORIGINAL TRACEBACK FOLLOWS\n%s" % (
23+
self.accumulator, self.wrapped_exception, tb)
24+
25+
626
def fold_effect(f, initial, effects):
727
"""
828
Fold over effects.
@@ -18,8 +38,12 @@ def fold_effect(f, initial, effects):
1838
:param effects: sequence of Effects.
1939
"""
2040

41+
def failed(acc, e):
42+
raise FoldError(acc, e)
43+
2144
def folder(acc, element):
22-
return acc.on(lambda r: element.on(lambda r2: f(r, r2)))
45+
return acc.on(lambda r: element.on(lambda r2: f(r, r2),
46+
error=lambda e: failed(r, e)))
2347

2448
return reduce(folder, effects, Effect(Constant(initial)))
2549

effect/test_fold.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11

22
import operator
33

4-
from effect import ComposedDispatcher, Effect, base_dispatcher, sync_perform
5-
from effect.fold import fold_effect, sequence
4+
from pytest import raises
5+
6+
from effect import (
7+
ComposedDispatcher, Effect, Error,
8+
base_dispatcher, sync_perform)
9+
from effect.fold import FoldError, fold_effect, sequence
610
from effect.testing import SequenceDispatcher
711

812

@@ -34,6 +38,27 @@ def test_fold_effect_empty():
3438
assert result == 0
3539

3640

41+
def test_fold_effect_errors():
42+
"""
43+
When one of the effects in the folding list fails, a FoldError is raised
44+
with the accumulator so far.
45+
"""
46+
effs = [Effect('a'), Effect(Error(ZeroDivisionError('foo'))), Effect('c')]
47+
48+
dispatcher = SequenceDispatcher([
49+
('a', lambda i: 'Ei'),
50+
])
51+
52+
eff = fold_effect(operator.add, 'Nil', effs)
53+
54+
with dispatcher.consume():
55+
with raises(FoldError) as excinfo:
56+
sync_perform(_disp(dispatcher), eff)
57+
assert excinfo.value.accumulator == 'NilEi'
58+
assert excinfo.value.wrapped_exception[0] is ZeroDivisionError
59+
assert str(excinfo.value.wrapped_exception[1]) == 'foo'
60+
61+
3762
def test_sequence():
3863
"""Collects each Effectful result into a list."""
3964
effs = [Effect('a'), Effect('b'), Effect('c')]

0 commit comments

Comments
 (0)