From 371e333f7893cd1363c47d5ace8267272ac6f8db Mon Sep 17 00:00:00 2001 From: zyd Date: Sun, 12 Jun 2022 22:08:07 +0900 Subject: [PATCH 1/3] add method chain --- README.md | 2 +- ramda/__init__.py | 1 + ramda/chain.py | 17 ++++++++ ramda/private/_flatCat.py | 30 ++++++++++++++ ramda/private/_forceReduced.py | 5 +++ ramda/private/_xchain.py | 6 +++ test/test_chain.py | 76 ++++++++++++++++++++++++++++++++++ 7 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 ramda/chain.py create mode 100644 ramda/private/_flatCat.py create mode 100644 ramda/private/_forceReduced.py create mode 100644 ramda/private/_xchain.py create mode 100644 test/test_chain.py diff --git a/README.md b/README.md index adea725..ec72f8e 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ R.add(date(1,2,3), date(1,2,3)) # float('nan) - [ ] bind - [ ] both - [ ] call -- [ ] chain +- [x] chain - [ ] clamp - [x] 0.1.2 clone diff --git a/ramda/__init__.py b/ramda/__init__.py index d892a13..ed63233 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -8,6 +8,7 @@ from .ap import ap from .append import append from .binary import binary +from .chain import chain from .clone import clone from .comparator import comparator from .compose import compose diff --git a/ramda/chain.py b/ramda/chain.py new file mode 100644 index 0000000..6e286c5 --- /dev/null +++ b/ramda/chain.py @@ -0,0 +1,17 @@ +from .map import map +from .private._curry2 import _curry2 +from .private._dispatchable import _dispatchable +from .private._isFunction import _isFunction +from .private._makeFlat import _makeFlat +from .private._xchain import _xchain + + +def inner_chain(fn, monad): + if _isFunction(monad): + def wrapper(x): + return fn(monad(x))(x) + return wrapper + return _makeFlat(False)(map(fn, monad)) + + +chain = _curry2(_dispatchable(['fantasy-land/chain', 'chain'], _xchain, inner_chain)) diff --git a/ramda/private/_flatCat.py b/ramda/private/_flatCat.py new file mode 100644 index 0000000..3c3c812 --- /dev/null +++ b/ramda/private/_flatCat.py @@ -0,0 +1,30 @@ +from ._forceReduced import _forceReduced +from ._helper import getAttribute +from ._isArrayLike import _isArrayLike +from ._xArrayReduce import _xArrayReduce +from ._xfBase import XfBase +from ._xReduce import _xReduce + + +class XPreservingReduced(XfBase): + def __init__(self, xf): + self.xf = xf + + def step(self, result, _input): + ret = getAttribute(self.xf, '@@transducer/step')(result, _input) + if getAttribute(ret, '@@transducer/reduced'): + return _forceReduced(ret) + return ret + + +class XFlatCat(XfBase): + def __init__(self, xf): + self.xf = XPreservingReduced(xf) + + def step(self, result, _input): + if not _isArrayLike(_input): + return _xArrayReduce(self.xf, result, [_input]) + return _xReduce(self.xf, result, _input) + + +def _flatCat(xf): return XFlatCat(xf) diff --git a/ramda/private/_forceReduced.py b/ramda/private/_forceReduced.py new file mode 100644 index 0000000..84af2b1 --- /dev/null +++ b/ramda/private/_forceReduced.py @@ -0,0 +1,5 @@ +def _forceReduced(x): + return { + '@@transducer/value': x, + '@@transducer/reduced': True + } diff --git a/ramda/private/_xchain.py b/ramda/private/_xchain.py new file mode 100644 index 0000000..c66b42f --- /dev/null +++ b/ramda/private/_xchain.py @@ -0,0 +1,6 @@ +from ._flatCat import _flatCat +from ._xmap import _xmap + + +def _xchain(f): + return lambda xf: _xmap(f)(_flatCat(xf)) diff --git a/test/test_chain.py b/test/test_chain.py new file mode 100644 index 0000000..bc63c7a --- /dev/null +++ b/test/test_chain.py @@ -0,0 +1,76 @@ + +import unittest + +import ramda as R +from ramda.private._isTransformer import _isTransformer + +from .helpers.Id import Id +from .helpers.listXf import listXf + +""" +https://github.com/ramda/ramda/blob/master/test/chain.js +""" + +intoArray = R.into([]) +def add1(x): return [x + 1] +def dec(x): return [x - 1] +def times2(x): return [x * 2] + + +class TestChain(unittest.TestCase): + def test_maps_a_function_over_a_nested_list_and_returns_the_shallow_flattened_result(self): + self.assertEqual([2, 4, 6, 2, 0, 20, -6, 10, 14], R.chain(times2, [1, 2, 3, 1, 0, 10, -3, 5, 7])) + self.assertEqual([2, 4, 6], R.chain(times2, [1, 2, 3])) + + def test_does_not_flatten_recursively(self): + def f(xs): + if len(xs) == 0: + return [] + return [xs[0]] + + self.assertEqual([1, [2], 3], R.chain(f, [[1], [[2], 100], [], [3, [4]]])) + + def test_maps_a_function_into_a_shallow_flat_result(self): + self.assertEqual([2, 4, 6, 8], intoArray(R.chain(times2), [1, 2, 3, 4])) + + def test_interprets_r_as_a_monad(self): + def h(r): return r * 2 + def f(a): return lambda r: r + a + bound = R.chain(f, h) + # (>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b) + # h >>= f = \w -> f (h w) w + self.assertEqual((10 * 2) + 10, bound(10)) + self.assertEqual([1, 2, 3, 1], R.chain(R.append, R.head)([1, 2, 3])) + + def test_dispatches_to_objects_that_implement_chain(self): + class Obj: + def __init__(self, x): + self.x = x + + def chain(self, f): + return f(self.x) + + obj = Obj(100) + self.assertEqual([101], R.chain(add1, obj)) + + def test_dispatches_to_transformer_objects(self): + self.assertEqual(True, _isTransformer(R.chain(add1, listXf))) + + def test_composes(self): + mdouble = R.chain(times2) + mdec = R.chain(dec) + self.assertEqual([19, 39, 59], mdec(mdouble([10, 20, 30]))) + + def test_can_compose_transducer_style(self): + mdouble = R.chain(times2) + mdec = R.chain(dec) + xcomp = R.compose(mdec, mdouble) + self.assertEqual([18, 38, 58], intoArray(xcomp, [10, 20, 30])) + + def test_fantasy_land(self): + id = R.chain(add1, Id(10)) + self.assertEqual([11], id) + + +if __name__ == '__main__': + unittest.main() From 73e0e4db53fd0213382322806f4823386ab3f94d Mon Sep 17 00:00:00 2001 From: zyd Date: Sun, 12 Jun 2022 22:36:34 +0900 Subject: [PATCH 2/3] increase coverage --- test/test_chain.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_chain.py b/test/test_chain.py index bc63c7a..863b221 100644 --- a/test/test_chain.py +++ b/test/test_chain.py @@ -71,6 +71,20 @@ def test_fantasy_land(self): id = R.chain(add1, Id(10)) self.assertEqual([11], id) + def test_forcedReduced(self): + def reducedStep(acc, x): + if x > 4: + return R.reduced(acc + x) + return acc + x + reducedListXf = { + '@@transducer/init': lambda: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + '@@transducer/step': reducedStep, + '@@transducer/result': lambda x: x + } + times2ReducedListXf = R.chain(times2, reducedListXf) + self.assertEqual((1 + 2 + 3) * 2, R.reduce(times2ReducedListXf, 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) + self.assertEqual((1 + 2) * 2, R.reduce(times2ReducedListXf, 0, [1, 2])) + if __name__ == '__main__': unittest.main() From 1bae02bfd2c83d7a9673ff94dbe0d24e83bfad23 Mon Sep 17 00:00:00 2001 From: zyd Date: Sun, 12 Jun 2022 23:06:39 +0900 Subject: [PATCH 3/3] increase coverage --- test/test_chain.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_chain.py b/test/test_chain.py index 863b221..5d2230f 100644 --- a/test/test_chain.py +++ b/test/test_chain.py @@ -33,6 +33,9 @@ def f(xs): def test_maps_a_function_into_a_shallow_flat_result(self): self.assertEqual([2, 4, 6, 8], intoArray(R.chain(times2), [1, 2, 3, 4])) + def test_XFlatCat_if_input_is_not_arrayLike(self): + self.assertEqual([2, 4, 6, 8], intoArray(R.chain(R.multiply(2)), [1, 2, 3, 4])) + def test_interprets_r_as_a_monad(self): def h(r): return r * 2 def f(a): return lambda r: r + a @@ -56,6 +59,9 @@ def chain(self, f): def test_dispatches_to_transformer_objects(self): self.assertEqual(True, _isTransformer(R.chain(add1, listXf))) + def test_xFlatCat_if_input_not_arrayLike(self): + add1ListXf = R.chain(add1, listXf) + def test_composes(self): mdouble = R.chain(times2) mdec = R.chain(dec)