From 8d934f9f2842f50db2b3545a0ba2168af04d1576 Mon Sep 17 00:00:00 2001 From: zyd Date: Sun, 26 Jun 2022 13:13:42 +0900 Subject: [PATCH] add method cond --- README.md | 33 ++++++++++++++++++++- ramda/__init__.py | 1 + ramda/cond.py | 25 ++++++++++++++++ test/test_cond.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 ramda/cond.py create mode 100644 test/test_cond.py diff --git a/README.md b/README.md index 084d035..97c6aa0 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,38 @@ isinstance(clone, Obj) # True - [x] 0.1.2 compose - [ ] composeWith - [x] 0.1.2 concat -- [ ] cond +- [x] cond + +Please notice the number of given arguments should match functions. +Otherwise Python will complain about the mis-matching arguments. + +For example: + +```python +fn = R.cond([ + [lambda a: a == 1, lambda a: f'a == {a}'], + [lambda a, b: a == b, lambda a, b: f'{a} == {b}'] +]) +fn(1) # a == 1 +fn(2, 2) # 2 == 2 + +fn(2) # Throw error, because b is not provided for prediction, failed when (lambda a, b: a == b)(2), missing argument b +# to solve above issue, you should try your best to provide enough arguments + +fn(1, 2) # Throw error, because (lambda(a: f'a == {a}'))(1, 2) has extra arguments 2 +# To solve above issue, always use sencond function with enough arguments +# Try create cond like below. +fn = R.cond([ + [lambda a: a == 1, lambda a, _: f'a == {a}'], # ignore b + [lambda a, b: a == b, lambda a, b: f'{a} == {b}'] +]) + +fn = R.cond([ + [lambda a: a == 1, lambda a, *args: f'a == {a}'], # ignore any arguments + [lambda a, b: a == b, lambda a, b: f'{a} == {b}'] +]) +``` + - [x] 0.4.0 construct - [x] 0.4.0 constructN - [x] 0.1.4 converge diff --git a/ramda/__init__.py b/ramda/__init__.py index 93bae61..3d7aa22 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -13,6 +13,7 @@ from .comparator import comparator from .compose import compose from .concat import concat +from .cond import cond from .construct import construct from .constructN import constructN from .converge import converge diff --git a/ramda/cond.py b/ramda/cond.py new file mode 100644 index 0000000..9037eaf --- /dev/null +++ b/ramda/cond.py @@ -0,0 +1,25 @@ +from .map import map +from .Max import Max +from .private._arity import _arity +from .private._curry1 import _curry1 +from .private._inspect import funcArgsLength, getArgsToUse +from .reduce import reduce + + +def inner_cond(pairs): + arity = reduce( + Max, 0, map(lambda pair: funcArgsLength(pair[0]), pairs) + ) + + def wrapper(*args): + idx = 0 + while idx < len(pairs): + pred = pairs[idx][0] + argsToUse = getArgsToUse(pred, args) + if pred(*argsToUse): + return pairs[idx][1](*args) + idx += 1 + return _arity(arity, wrapper) + + +cond = _curry1(inner_cond) diff --git a/test/test_cond.py b/test/test_cond.py new file mode 100644 index 0000000..1044216 --- /dev/null +++ b/test/test_cond.py @@ -0,0 +1,75 @@ + +import unittest + +import ramda as R +from ramda.private._inspect import funcArgsLength + +""" +https://github.com/ramda/ramda/blob/master/test/cond.js +""" + + +class TestCond(unittest.TestCase): + def test_returns_a_function(self): + self.assertTrue(callable(R.cond([]))) + + def test_returns_a_conditional_function(self): + fn = R.cond([ + [R.equals(0), R.always('water freezes at 0°C')], + [R.equals(100), R.always('water boils at 0°C')], + [R.T, lambda temp: f'nothing special happens at {temp}°C'] + ]) + fn = R.cond([ + [R.equals('foo'), R.always(1)], + [R.equals('bar'), R.always(2)] + ]) + # self.assertEqual('water freezes at 0°C', fn(0)) + # self.assertEqual('nothing special happens at 50°C', fn(50)) + # self.assertEqual('water boils at 100°C', fn(100)) + + def test_returns_a_function_which_returns_None_if_none_of_the_predicates_matches(self): + fn = R.cond([ + [R.equals('foo'), R.always(1)], + [R.equals('bar'), R.always(2)] + ]) + self.assertEqual(None, fn('quux')) + + def test_predicates_are_tested_in_order(self): + fn = R.cond([ + [R.T, R.always('foo')], + [R.T, R.always('bar')], + [R.T, R.always('baz')] + ]) + self.assertEqual('foo', fn()) + + def test_forwards_all_arguments_to_predicates_and_to_transformers(self): + fn = R.cond([ + [lambda _, x: x == 42, lambda a, b, c: a + b + c] + ]) + self.assertEqual(46, fn(1, 42, 3)) + + def test_cond_with_different_number_of_arguments(self): + fn = R.cond([ + [lambda a: a == 1, lambda a, *_: f'a == {a}'], + [lambda a, b: a == b, lambda a, b, *_: f'{a} == {b}'], + [lambda a, b, c: a == b + c, lambda a, b, c: f'{a} == {b} + {c}'], + ]) + self.assertEqual('a == 1', fn(1)) + self.assertEqual('a == 1', fn(1, 2)) + self.assertEqual('a == 1', fn(1, 2, 3)) + self.assertEqual('2 == 2', fn(2, 2)) + self.assertEqual('2 == 2', fn(2, 2, 3)) + self.assertEqual('4 == 1 + 3', fn(4, 1, 3)) + self.assertEqual(None, fn(4, 1, 4)) + + def test_retains_highest_predicate_arity(self): + fn = R.cond([ + [R.nAry(2, R.T), R.T], + [R.nAry(3, R.T), R.T], + [R.nAry(1, R.T), R.T] + ]) + self.assertEqual(3, funcArgsLength(fn)) + + +if __name__ == '__main__': + unittest.main()