diff --git a/README.md b/README.md index ec72f8e..73ee95d 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,34 @@ R.equals(float('nan'), float('nan')) # True - [ ] invert - [ ] invertObj - [x] 0.1.2 invoker -- [ ] is +- [x] is + +This is a language specific feature. +So we check all python built-in types as many as we can. + +```python +R.Is(int, 1) # True +R.Is(float, 1.0) # True +R.Is(str, '1') # True +R.Is(list, [1,2,3]) # True +R.Is(dict, {'a': 1}) # True +R.Is(set, {1,2,3}) # True +R.Is(tuple, (1,2,3)) # True +R.Is(None, None) # True +R.Is(bool, True) # True +R.Is(bool, False) # True + +# For user-defined object +class Parent: + pass +class Child(Parent): + pass +R.Is(Parent, Parent()) # True +R.Is(Parent, Child()) # True +R.Is(Child, Child()) # True +R.Is(Child, Parent()) # False +``` + - [x] 0.1.2 isEmpty ```python @@ -413,7 +440,7 @@ Python modulo on negative numbers has different behavior than JS. - [ ] nthArg - [ ] o - [x] 0.1.2 objOf -- [ ] of +- [x] of - [x] 0.1.2 omit we support both `dict` type and `object` type. diff --git a/ramda/Is.py b/ramda/Is.py new file mode 100644 index 0000000..14a9f4d --- /dev/null +++ b/ramda/Is.py @@ -0,0 +1,10 @@ +from .private._curry2 import _curry2 + + +def inner_is(Ctor, val): + if val is None: + return val is None + return isinstance(val, Ctor) + + +Is = _curry2(inner_is) diff --git a/ramda/__init__.py b/ramda/__init__.py index ed63233..d4dc201 100644 --- a/ramda/__init__.py +++ b/ramda/__init__.py @@ -44,6 +44,7 @@ from .intersection import intersection from .into import into from .invoker import invoker +from .Is import Is from .isEmpty import isEmpty from .join import join from .juxt import juxt @@ -65,6 +66,7 @@ from .Not import Not from .nth import nth from .objOf import objOf +from .of import of from .omit import omit from .once import once from .Or import Or diff --git a/ramda/of.py b/ramda/of.py new file mode 100644 index 0000000..39dfd56 --- /dev/null +++ b/ramda/of.py @@ -0,0 +1,14 @@ +from .private._curry2 import _curry2 +from .private._helper import getAttribute +from .private._isFunction import _isFunction + + +def inner_of(Ctor, val): + if _isFunction(getAttribute(Ctor, 'fantasy-land/of')): + return getAttribute(Ctor, 'fantasy-land/of')(val) + if _isFunction(getAttribute(Ctor, 'of')): + return getAttribute(Ctor, 'of')(val) + return [val] + + +of = _curry2(inner_of) diff --git a/ramda/private/_helper.py b/ramda/private/_helper.py index 8427f37..94531e1 100644 --- a/ramda/private/_helper.py +++ b/ramda/private/_helper.py @@ -39,13 +39,20 @@ def get(self, type): t = T() init_fn = getAttribute(t, '@@transducer/init') - method case: + method case 1: class Mapper: def map(fn): return fn m = Mapper() map_fn = getAttribute(m, 'map') + method case 2: + class Mapper: + def map(self, fn): + return fn + m = Mapper() + map_fn = getAttribute(m, 'map') + return: function got from key, otherwise None """ if isinstance(v, dict) and key in v: @@ -54,10 +61,15 @@ def map(fn): return getattr(v, key, None) if _has(v, 'get'): try: - return v.get(key, None) + # Case that get is (key, default) -> value signature + return v.get(key, default=None) except TypeError: - # in case v has get method but with different signature - return None + try: + # Case that get is a instance method with (self, key, default) -> value signature + return v.get(v, key, default=None) + except TypeError: + # Unknown signature + return None def safeLen(x): diff --git a/test/test_is.py b/test/test_is.py new file mode 100644 index 0000000..3447842 --- /dev/null +++ b/test/test_is.py @@ -0,0 +1,108 @@ + +import unittest + +import ramda as R + +""" +https://github.com/ramda/ramda/blob/master/test/is.js +""" + + +class TestIs(unittest.TestCase): + def test_works_with_built_in_types(self): + + # Numeric types + self.assertTrue(R.Is(int, 1)) + self.assertTrue(R.Is(float, 1.1)) + self.assertTrue(R.Is(complex, 1 + 1j)) + + self.assertFalse(R.Is(int, 1.1)) + self.assertFalse(R.Is(float, 1)) + self.assertFalse(R.Is(complex, 1)) + + # Sequence types + self.assertTrue(R.Is(list, [])) + self.assertTrue(R.Is(tuple, ())) + + self.assertTrue(R.Is(list, [1, 2, 3])) + self.assertTrue(R.Is(tuple, (1, 2, 3))) + + self.assertFalse(R.Is(tuple, [1, 2, 3])) + self.assertFalse(R.Is(list, (1, 2, 3))) + + # Text Sequence types + self.assertTrue(R.Is(str, "")) + self.assertTrue(R.Is(str, 'abc')) + self.assertTrue(R.Is(str, '''abc''')) + self.assertTrue(R.Is(str, u'abc')) + self.assertTrue(R.Is(str, f'abc')) + self.assertTrue(R.Is(str, r'abc')) + + self.assertFalse(R.Is(str, b'abc')) + + # Binary Sequence types + self.assertTrue(R.Is(bytes, b"")) + self.assertTrue(R.Is(bytearray, bytearray(b""))) + self.assertTrue(R.Is(memoryview, memoryview(b""))) + + # Set types + self.assertTrue(R.Is(set, set())) + self.assertTrue(R.Is(frozenset, frozenset())) + + self.assertTrue(R.Is(set, set([1, 2, 3]))) + self.assertTrue(R.Is(frozenset, frozenset([1, 2, 3]))) + + self.assertFalse(R.Is(frozenset, set([1, 2, 3]))) + self.assertFalse(R.Is(set, frozenset([1, 2, 3]))) + + # Mapping types + self.assertTrue(R.Is(dict, {})) + self.assertTrue(R.Is(dict, {'a': 1, 'b': 2})) + self.assertTrue(R.Is(dict, dict())) + self.assertTrue(R.Is(dict, dict(a=1, b=2))) + + # Union types + u = int | str + self.assertTrue(R.Is(u, 1)) + self.assertTrue(R.Is(u, "1")) + + # None type + self.assertTrue(R.Is(None, None)) + + # Boolean types + self.assertTrue(R.Is(bool, True)) + self.assertTrue(R.Is(bool, False)) + + def test_works_with_objects(self): + class A: + pass + + class B(A): + pass + + class C(B): + pass + + a = A() + b = B() + c = C() + + self.assertTrue(R.Is(A, a)) + self.assertFalse(R.Is(B, a)) + self.assertFalse(R.Is(C, a)) + + self.assertTrue(R.Is(A, b)) + self.assertTrue(R.Is(B, b)) + self.assertFalse(R.Is(C, b)) + + self.assertTrue(R.Is(A, c)) + self.assertTrue(R.Is(B, c)) + self.assertTrue(R.Is(C, c)) + + def test_does_not_coerce(self): + self.assertFalse(R.Is(int, "1")) + self.assertFalse(R.Is(float, "1")) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_of.py b/test/test_of.py new file mode 100644 index 0000000..ae0a682 --- /dev/null +++ b/test/test_of.py @@ -0,0 +1,39 @@ + +import unittest + +import ramda as R + +from .helpers.Maybe import Just, Maybe + +""" +https://github.com/ramda/ramda/blob/master/test/of.js +""" + + +class TestOf(unittest.TestCase): + def test_returns_its_argument_as_an_array(self): + self.assertEqual([100], R.of(list, 100)) + self.assertEqual([[100]], R.of(list, [100])) + self.assertEqual([None], R.of(list, None)) + self.assertEqual([[]], R.of(list, [])) + + def test_dispatches_to_an_available_of_method(self): + class MaybeWithOf(Maybe): + def of(x): + return Just(x) + + just = R.of(MaybeWithOf, 100) + self.assertTrue(R.equals(just, Just(100))) + + def test_dispatches_to_an_available_fantasy_land_of_method(self): + class MaybeWithOf(Maybe): + def get(self, name, default=None): + if name == 'fantasy-land/of': + return lambda x: Just(x) + + just = R.of(MaybeWithOf, 100) + self.assertTrue(R.equals(just, Just(100))) + + +if __name__ == '__main__': + unittest.main()