From 75041de51a08b3586fc48b55f99661b9ac4f0a21 Mon Sep 17 00:00:00 2001 From: zyd Date: Wed, 8 Jun 2022 21:20:46 +0900 Subject: [PATCH 1/2] add method ap --- README.md | 2 +- ramda/__init__.py | 1 + ramda/ap.py | 19 +++++++++++++++++++ test/test_ap.py | 30 +++++++++++++++++++----------- 4 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 ramda/ap.py diff --git a/README.md b/README.md index 2599817..b406fb0 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ R.add(date(1,2,3), date(1,2,3)) # float('nan) - [ ] andThen - [x] 0.1.2 any - [ ] anyPass -- [ ] ap +- [x] ap - [ ] aperture - [x] 0.1.2 append - [ ] apply diff --git a/ramda/__init__.py b/ramda/__init__.py index 6f486e3..a6f64a2 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -5,6 +5,7 @@ from .always import always from .And import And from .any import any +from .ap import ap from .append import append from .binary import binary from .clone import clone diff --git a/ramda/ap.py b/ramda/ap.py new file mode 100644 index 0000000..cfcef9d --- /dev/null +++ b/ramda/ap.py @@ -0,0 +1,19 @@ +from .map import map +from .private._concat import _concat +from .private._curry2 import _curry2 +from .private._helper import getAttribute +from .private._isFunction import _isFunction +from .private._reduce import _reduce + + +def inner_ap(applyF, applyX): + if _isFunction(getAttribute(applyX, 'fantasy-land/ap')): + return getAttribute(applyX, 'fantasy-land/ap')(applyF) + if _isFunction(getAttribute(applyF, 'ap')): + return getAttribute(applyF, 'ap')(applyX) + if _isFunction(applyF): + return lambda x: applyF(x)(applyX(x)) + return _reduce(lambda acc, f: _concat(acc, map(f, applyX)), [], applyF) + + +ap = _curry2(inner_ap) diff --git a/test/test_ap.py b/test/test_ap.py index 781300d..6f12c2a 100644 --- a/test/test_ap.py +++ b/test/test_ap.py @@ -7,19 +7,27 @@ https://github.com/ramda/ramda/blob/master/test/ap.js """ +mult2 = R.multiply(2) +plus3 = R.add(3) + class TestAp(unittest.TestCase): - def test_returns_an_array_of_tuples(self): - a = [1, 2, 3] - b = [100, 200, 300] - self.assertEqual([[1, 100], [2, 200], [3, 300]], R.zip(a, b)) - - def test_returns_a_list_as_long_as_the_shorter_of_the_lists_input(self): - a = [1, 2, 3] - b = [100, 200, 300, 400] - c = [10, 20] - self.assertEqual([[1, 100], [2, 200], [3, 300]], R.zip(a, b)) - self.assertEqual([[1, 10], [2, 20]], R.zip(a, c)) + def test_interprets_list_a_as_an_applicative(self): + self.assertEqual([2, 4, 6, 4, 5, 6], R.ap([mult2, plus3])([1, 2, 3])) + + def test_interprets_arrow_r_as_an_applicative(self): + def f(r): return lambda a: r + a + def g(r): return r * 2 + h = R.ap(f, g) + # (<*>) :: (r -> a -> b) -> (r -> a) -> r -> b + # f <*> g = \x -> f x (g x) + self.assertEqual(10 + (10 * 2), h(10)) + self.assertEqual(10 + (10 * 2), R.ap(R.add)(g)(10)) + + def test_dispatches_to_the_first_passed_object_ap_method_when_values_is_a_non_array(self): + obj = {'ap': lambda n: 'called ap with ' + str(n)} + self.assertEqual(obj['ap'](10), R.ap(obj, 10)) + if __name__ == '__main__': unittest.main() From b8c662927f8ec603780d981ca3cc8cfbf2afbde5 Mon Sep 17 00:00:00 2001 From: zyd Date: Wed, 8 Jun 2022 21:39:09 +0900 Subject: [PATCH 2/2] Add test for fantasy-land --- test/helpers/Id.py | 19 +++++++++++++++++++ test/test_ap.py | 7 +++++++ 2 files changed, 26 insertions(+) create mode 100644 test/helpers/Id.py diff --git a/test/helpers/Id.py b/test/helpers/Id.py new file mode 100644 index 0000000..163a1ad --- /dev/null +++ b/test/helpers/Id.py @@ -0,0 +1,19 @@ +import ramda as R +from ramda.private._helper import getAttribute + + +def Id(value): + return { + '@@type': 'ramda/Id', + 'fantasy-land/equals': lambda other: other is not None and getAttribute(other, '@@type') == 'ramda/Id' and R.equals(getAttribute(other, 'value'), value), + 'fantasy-land/concat': lambda id: Id(R.concat(value, getAttribute(id, 'value'))), + 'fantasy-land/map': lambda f: Id(f(value)), + 'fantasy-land/ap': lambda id: Id(getAttribute(id, 'value')(value)), + 'fantasy-land/chain': lambda f: f(value), + 'fantasy-land/reduce': lambda f, x: f(x, value), + 'fantasy-land/traverse': lambda f, of: R.map(Id, f(value)), + 'sequence': lambda of: R.map(Id, value), + 'constructor': {'fantasy-land/of': Id}, + 'toString': lambda: 'Id(' + R.toString(value) + ')', + 'value': value + } diff --git a/test/test_ap.py b/test/test_ap.py index 6f12c2a..d8e15bd 100644 --- a/test/test_ap.py +++ b/test/test_ap.py @@ -1,5 +1,6 @@ import unittest +from test.helpers.Id import Id import ramda as R @@ -28,6 +29,12 @@ def test_dispatches_to_the_first_passed_object_ap_method_when_values_is_a_non_ar obj = {'ap': lambda n: 'called ap with ' + str(n)} self.assertEqual(obj['ap'](10), R.ap(obj, 10)) + def test_works_with_fantasy_land_ap(self): + applyX = Id(10) + applyF = {'value': lambda x: x * 2} + res = R.ap(applyF, applyX) + self.assertEqual(20, res['value']) + self.assertEqual('ramda/Id', res['@@type']) if __name__ == '__main__': unittest.main()