├── .gitignore ├── LICENSE.txt ├── README.rst ├── VERSION ├── hawkweed-py.test ├── hawkweed ├── __init__.py ├── classes │ ├── __init__.py │ ├── collection.py │ ├── dict_cls.py │ ├── future.py │ ├── iterable.py │ ├── list_cls.py │ ├── repr.py │ └── set_cls.py ├── computed │ └── __init__.py ├── functional │ ├── __init__.py │ ├── list_prims.py │ ├── logical.py │ ├── mathematical.py │ └── primitives.py └── monads │ ├── __init__.py │ ├── continuation.py │ ├── either.py │ ├── identity.py │ ├── io.py │ ├── listm.py │ ├── maybe.py │ └── monad.py ├── setup.py ├── tests.sh └── tests ├── __init__.py ├── test_collection.py ├── test_continuation.py ├── test_dict.py ├── test_either.py ├── test_file.txt ├── test_future.py ├── test_identity.py ├── test_io.py ├── test_iterable.py ├── test_list.py ├── test_list_prims.py ├── test_listm.py ├── test_logical.py ├── test_math.py ├── test_mathematical.py ├── test_maybe.py ├── test_monad.py ├── test_primitives.py ├── test_repr.py └── test_set.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | build/ 4 | *.egg-info 5 | dist 6 | .coverage 7 | .cache/ 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Veit Heller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | hawkweed 3 | ============= 4 | 5 | Yet another implementation of missing functions. 6 | 7 | Installation 8 | ------------ 9 | 10 | :: 11 | 12 | pip install hawkweed 13 | 14 | Usage 15 | ----- 16 | 17 | hawkweed is roughly divided into three different parts: datatypes, monads and 18 | functions. All the functions are exhaustively documented with pydoc, all the 19 | parameters, the functions' time complexity (if applicable) and the return value 20 | are given. 21 | 22 | Datatypes 23 | --------- 24 | 25 | Most of the datatypes implemented in hawkweed are mere wrappers around Python 26 | standard datatypes. If the function does not return anything in the Python 27 | datatype, this implementation will return ``self`` to allow for chaining. 28 | 29 | A notable exception is the largely unstable and undocumented ``Future`` class. 30 | 31 | .. code-block:: python 32 | 33 | from hawkweed import List, Dict, Set 34 | 35 | List([1]).append(2).extend([3, None, 4]).remove_empty() # => List([1, 2, 3, 4]) 36 | List(range(10)).take(5) # => generator from 0 to 4 37 | List(range(10)).drop(5) # => generator from 5 to 9 38 | List(range(10)).take_while(lambda x: x < 5) # => generator from 0 to 4 39 | List(range(10)).drop_while(lambda x: x < 5) # => generator from 4 to 9 40 | List(range(10)).nth(3) # => generator yielding 0, 3, 6 and 9 (lazily); works with any subclass of Iterable 41 | List(range(10)).reset(range(5)) # => List([0, 1, 2, 3, 4]) 42 | 43 | Dict({1: 2, 3: 4}).reverse() # => Dict({2: 1, 4: 3}) 44 | Dict({1: 2, 3: 4, 2: None}).remove_empty() # => Dict({1: 2, 3: 4}) 45 | Dict({1: 2, 3: 4, None: "go away"}).remove_empty(filter_keys=True) # => Dict({1: 2, 3: 4}) 46 | Dict({1: 2, 3: 4, 2: 3}).remove_empty(fun=lambda x: x!=2) # => Dict({1: 2, 3: 4}) 47 | Dict({1: 2, 3: 4}).reduce(fun=lambda acc, k, v: acc + k + v, acc=0) # => 10 48 | Dict({1: 2, 3: 4}).reduce(fun=lambda acc, k, v: acc + (k, v)) # => (1, 2, 3, 4) 49 | Dict({1: 2, 3: 4, 5: 6}).pick(1, 5) # => Dict({1: 2, 5: 6}) 50 | 51 | Set({1, 2, 3, 4}).remove_empty(fun=lambda x: x!=3) # => Set({1, 2, 4}) 52 | 53 | # And now for something completely different 54 | Dict({ 55 | "foo": List([1, 2, 3, Dict({"bar": "baz"})]) 56 | }).get_in("foo", 3, "bar") # => "baz" 57 | Dict({ 58 | "foo": List([1, 2, 3, Dict({"bar": "baz"})]) 59 | }).get_in("foo", 100, "bar") # => None 60 | Dict({ 61 | "foo": List([1, 2, 3, Dict({"bar": "baz"})]) 62 | }).get_in("foo", 100, "bar", dflt="i am a default value") # => "i am a default value" 63 | Dict({ 64 | "foo": List([1, 2, 3, Dict({"bar": "baz"})]) 65 | }).update_in("foo", 1, "bar", to="update") # => Dict({"foo": List([1, 2, 3, Dict({"bar": "update"})])}) 66 | # if you want to insert your own datatype, just inherit from hawkweed.Collection 67 | # and implement get(key, dflt=None) and __setitem__ 68 | 69 | Functions 70 | --------- 71 | 72 | All of the functions are standalone and curried whenever possible. They do not depend 73 | on hawkweeds datatypes in any way. 74 | 75 | .. code-block:: python 76 | 77 | from hawkweed import map, reduce, List, all, any, constantly, delay 78 | 79 | map(inc, range(100)) # => range(1, 101) 80 | incrementor = map(inc) 81 | incrementor(List(range(100))) # => range(1, 101) 82 | summator = reduce(add) 83 | summator(range(5)) # => 10 84 | all(lambda x: x > 100, [101, 102, 103]) # => True 85 | any(lambda x: x > 10, [3, 5, 8]) # => False 86 | constantly(10) # => an infinite generator of 10 87 | delayed = delay(print, 'Hello, World!') # => this will return a variable that, when called, will compute the result of print with the argument 'Hello, World!' 88 | # it will cache the result instead of recomputing it upon reevaluation, i.e. `delayed() or delayed()` will only print 'Hello, World!' once 89 | 90 | A few other functions that you might expect from a functional programming library (``compose``, 91 | ``pipe``, ``identity``, ``apply``, ``flip``, ``curry`` and the like) are also implemented. They 92 | should be intuitive and work as expected. If they do not or are not consider it a bug. 93 | 94 | Monads 95 | ------ 96 | 97 | The implemented monads are: Identity, Maybe (Just/Nothing), Continuation, Either, IO, CachedIO, 98 | and List (called ListM). do notation is also supported. 99 | 100 | .. code-block:: python 101 | 102 | from hawkweed import doM, wrapM, Just 103 | 104 | def doMe(): 105 | res1 = yield Just(1) 106 | res2 = yield Just(10) 107 | yield Just(res1+ res2) 108 | 109 | doM(doMe()) # => Just(11) 110 | 111 | wrapM(Just(10)).real 112 | # => 10; the wrapper will try to call the wrapped values' function whenever it does not exist in the monad 113 | 114 | There is a callcc function and all of the functions in Haskell's Data.Maybe_ and Data.Either_ are implemented. 115 | 116 | .. _Data.Maybe: https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html 117 | .. _Data.Either: https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Either.html 118 | 119 | 120 | Have fun! 121 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.5 2 | -------------------------------------------------------------------------------- /hawkweed/__init__.py: -------------------------------------------------------------------------------- 1 | """hawkweed implements Python's missing functions.""" 2 | from hawkweed.classes import * 3 | from hawkweed.functional import * 4 | from hawkweed.monads import * 5 | -------------------------------------------------------------------------------- /hawkweed/classes/__init__.py: -------------------------------------------------------------------------------- 1 | "A collection of augmented standard classes" 2 | from hawkweed.classes.list_cls import List 3 | from hawkweed.classes.dict_cls import Dict 4 | from hawkweed.classes.iterable import Iterable 5 | from hawkweed.classes.set_cls import Set 6 | from hawkweed.classes.repr import Repr 7 | from hawkweed.classes.collection import Collection 8 | -------------------------------------------------------------------------------- /hawkweed/classes/collection.py: -------------------------------------------------------------------------------- 1 | """A collection base class""" 2 | class Collection(): 3 | """A collection base class that introduces nested getting and setting""" 4 | 5 | #The default value for get_in; normally None 6 | DFLT = None 7 | 8 | def get(self, index, dflt=None): 9 | """This method needs to be implemented to subclass Collection""" 10 | raise NotImplementedError("You need to implement get to use the Collection base class") 11 | 12 | def __setitem__(self, *args, **kwargs): 13 | raise NotImplementedError("You need to implement __setitem__ to use the Collection \ 14 | base class") 15 | 16 | def get_in(self, *keys, **kwargs): 17 | """ 18 | A getter for deeply nested values. 19 | 20 | Complexity: the complexity differs depending on the collection's lookup/get complexity 21 | params: 22 | *keys: they keys that should be worked through 23 | **kwargs: this only accepts dflt (the default element, normally DFLT), all others 24 | will be ignored 25 | returns: 26 | the element that was found or the default element 27 | """ 28 | key = keys[0] 29 | cont = len(keys) > 1 30 | 31 | elem = self.get(key) 32 | 33 | if elem is None: 34 | return kwargs.get("dflt", self.DFLT) 35 | 36 | if not cont or not hasattr(elem, "get_in"): 37 | return elem 38 | 39 | return elem.get_in(*keys[1:], **kwargs) 40 | 41 | def update_in(self, *keys, **kwargs): 42 | """ 43 | A setter for deeply nested values. 44 | 45 | Complexity: the complexity differs depending on the collection's lookup/set complexity 46 | params: 47 | *keys: they keys that should be worked through 48 | **kwargs: this only accepts to (a needed argument that specifies what the element 49 | should be set to), all others will be ignored 50 | returns: self 51 | throws: Whatever a collection returns when a key does not exist (mostly Index- or KeyError) 52 | """ 53 | key = keys[0] 54 | cont = len(keys) > 1 55 | 56 | elem = self.get(key) 57 | 58 | if cont: 59 | elem.update_in(*keys[1:], **kwargs) 60 | else: 61 | self[key] = kwargs["to"] 62 | return self 63 | -------------------------------------------------------------------------------- /hawkweed/classes/dict_cls.py: -------------------------------------------------------------------------------- 1 | """An augmented version of the dict class""" 2 | from hawkweed.computed import PY3 3 | from hawkweed.classes.iterable import Iterable 4 | from hawkweed.classes.repr import Repr 5 | from hawkweed.classes.collection import Collection 6 | 7 | class Dict(Repr, dict, Iterable, Collection): 8 | """An augmented version of the dict class""" 9 | def reset(self, other): 10 | """ 11 | Resets a dict to another dicts content 12 | 13 | Complexity: O(n) 14 | params: 15 | other: the dict this dict should be resetted to 16 | returns: self 17 | """ 18 | self.clear() 19 | self.update(other) 20 | return self 21 | 22 | def reverse(self): 23 | """ 24 | Exchanges keys and values; if there are duplicate values, the 25 | last value that's been written wins. 26 | 27 | Complexity: O(2*n) 28 | returns: self 29 | """ 30 | tmp = dict() 31 | for key, val in self.items(): 32 | tmp[val] = key 33 | self.reset(tmp) 34 | del tmp 35 | return self 36 | 37 | def remove_empty(self, fun=None, filter_keys=False): 38 | """ 39 | Removes empty pairs from the dict. 40 | 41 | Complexity: O(2*n) 42 | params: 43 | fun: a function that takes an element and returns whether it 44 | should be kept (defaults to bool()) 45 | filter_keys: a flag that indicates that filtering should be 46 | done by keys, not by values 47 | returns: self 48 | """ 49 | if not fun: 50 | fun = bool 51 | tmp = dict() 52 | for key, val in self.items(): 53 | checker = key if filter_keys else val 54 | if fun(checker): 55 | tmp[key] = val 56 | self.reset(tmp) 57 | del tmp 58 | return self 59 | 60 | def update(self, *args, **kwargs): 61 | """ 62 | Update dictionary. Same as in dict(), but returns self. 63 | 64 | returns: self 65 | """ 66 | super(Dict, self).update(*args, **kwargs) 67 | return self 68 | 69 | def reduce(self, fun, acc=None): 70 | """ 71 | Reduce the dict to a value (using function fun). 72 | 73 | Complexity: O(n) 74 | params: 75 | fun: a function that takes the accumulator and current key 76 | and value and returns the new accumulator 77 | acc: the initial accumulator (defaults to tuple of first 78 | key and value taken from the iterator) 79 | returns: self 80 | """ 81 | iterator = self.items().__iter__() 82 | if acc is None: 83 | acc = iterator.__next__() if PY3 else iterator.next() 84 | for key, val in iterator: 85 | acc = fun(acc, key, val) 86 | return acc 87 | 88 | def clear(self): 89 | """ 90 | Augmented clear function that returns self. 91 | 92 | returns: self 93 | """ 94 | super(Dict, self).clear() 95 | return self 96 | 97 | def setdefault(self, *args, **kwargs): 98 | """ 99 | Augmented setdefault function that returns self. 100 | 101 | returns: self 102 | """ 103 | super(Dict, self).setdefault(*args, **kwargs) 104 | return self 105 | 106 | def pick(self, *keys): 107 | """ 108 | Takes a list of keys to pick and returns a subdict that contains 109 | only those entries. 110 | 111 | Complexity: O(k) where k is the number of keys 112 | params: 113 | *keys: the keys to pick 114 | returns: the subdict 115 | """ 116 | newd = {} 117 | 118 | for key in keys: 119 | if key in self: 120 | newd[key] = self[key] 121 | 122 | return newd 123 | -------------------------------------------------------------------------------- /hawkweed/classes/future.py: -------------------------------------------------------------------------------- 1 | """A Future class""" 2 | from hawkweed.functional.primitives import reduce 3 | from hawkweed.classes.repr import Repr 4 | 5 | class Future(Repr): 6 | """A Future class""" 7 | def __init__(self, value): 8 | """ 9 | Takes a binary function (taking success and error, respectively) 10 | and builds a Future from it. 11 | 12 | Complexity: O(1) 13 | params: 14 | value: the function to encase 15 | returns: 16 | a Future 17 | """ 18 | self.value = value 19 | self.transforms = [] 20 | 21 | @staticmethod 22 | def of(value): 23 | """ 24 | Creates a Future from a static value, immediately returning it. 25 | 26 | Complexity: O(1) 27 | params: 28 | value: the value to encase 29 | returns: 30 | a Future 31 | """ 32 | return Future(lambda res, rej: res(value)) 33 | 34 | @staticmethod 35 | def reject(value): 36 | """ 37 | Creates a Future from a static value, immediately rejecting it. 38 | 39 | Complexity: O(1) 40 | params: 41 | value: the value to encase 42 | returns: 43 | a Future 44 | """ 45 | return Future(lambda res, rej: rej(value)) 46 | 47 | @staticmethod 48 | def encase(fun, args=None): 49 | """ 50 | Encases an ordinary function in a Future. If the function runs 51 | as expected the return value will be returned to the success 52 | callback. If an exception occurs it will be returned to the 53 | error callback. 54 | 55 | Special behaviour: 56 | You need to specify args. If the function does not have any, 57 | add args=[]. If you do not a function that takes arguments 58 | will be returned. 59 | 60 | Complexity: O(1) 61 | params: 62 | fun: the function to encase 63 | args: the arguments to pass to the function (defaults to None, 64 | override to an empty sequence if no arguments are needed) 65 | returns: 66 | a Future 67 | """ 68 | if args is None: 69 | return lambda *args: Future.encase(fun, args=args) 70 | def res(res, rej): 71 | """Internal encase function""" 72 | try: 73 | return res(fun(*args)) 74 | except Exception as e: 75 | return rej(e) 76 | return Future(res) 77 | 78 | def __repr__(self): 79 | return "Future({})".format(self.value) 80 | 81 | def apply(self, fun): 82 | """ 83 | Apply a transformation function fun to the future value. 84 | 85 | Complexity: Application O(1), Execution O(fun) 86 | params: 87 | fun: the function to apply 88 | returns: 89 | a Future 90 | """ 91 | self.transforms.append(fun) 92 | return self 93 | 94 | def chain(self, future): 95 | """ 96 | Chains a future to this one. This will intercept 97 | any calls to fork insofar as both Futures are chained 98 | before any call to the callbacks. Any error in both 99 | Futures will result in a call to the error callback. 100 | 101 | Complexity: O(1) 102 | params: 103 | future: the Future to chain 104 | returns: 105 | a Future 106 | """ 107 | def chained(res, rej): 108 | """Internal chain function""" 109 | self.value(lambda x: future(x).fork(res, rej), rej) 110 | return Future(chained) 111 | 112 | def fork(self, res, err): 113 | """ 114 | Registers resolvers for this Future. 115 | 116 | Complexity: O(1) 117 | params: 118 | res: the resolver function 119 | err: the error function 120 | returns: 121 | whatever the functions return 122 | """ 123 | def resolver(trans): 124 | """Internal fork function that applies transformations""" 125 | try: 126 | return res(reduce(lambda acc, x: x(acc), trans, self.transforms)) 127 | except Exception as e: 128 | if err: 129 | return err(e) 130 | raise 131 | return self.value(resolver, err) 132 | -------------------------------------------------------------------------------- /hawkweed/classes/iterable.py: -------------------------------------------------------------------------------- 1 | """An Iterable baseclass""" 2 | 3 | class Iterable(): 4 | """An Iterable baseclass""" 5 | def __iter__(self): 6 | """This is the only functions that needs to be implemented to be an Iterable.""" 7 | raise NotImplementedError("Iterable subclasses need to implement this function") 8 | 9 | def concatenate(self, *arguments): 10 | """ 11 | Concatenates multiple Iterables. 12 | 13 | Complexity: O(1) 14 | params: 15 | *arguments: multiple Iterables 16 | returns: the new Iterable 17 | """ 18 | def internal(): 19 | """the internal concatenater""" 20 | for i in [self] + list(arguments): 21 | for elem in i: 22 | yield elem 23 | return internal() 24 | 25 | def nth(self, num): 26 | """ 27 | Returns an Iterable that will only yield every nth value. 28 | 29 | Complexity: O(1) 30 | params: 31 | num: the step size 32 | returns: the new Iterable 33 | """ 34 | if num < 1: 35 | raise ValueError("step size must be bigger than 0, was {}".format(num)) 36 | def internal(): 37 | """the internal iterator""" 38 | for i, elem in enumerate(self): 39 | if i % num == 0: 40 | yield elem 41 | return internal() 42 | -------------------------------------------------------------------------------- /hawkweed/classes/list_cls.py: -------------------------------------------------------------------------------- 1 | """An augmented version of the list class""" 2 | from hawkweed.classes.iterable import Iterable 3 | from hawkweed.classes.repr import Repr 4 | from hawkweed.classes.collection import Collection 5 | 6 | class List(Repr, list, Iterable, Collection): 7 | """An augmented version of the list class""" 8 | def reset(self, other): 9 | """ 10 | Resets a list to another lists content 11 | 12 | Complexity: O(n+m) 13 | params: 14 | other: the list this list should be resetted to 15 | returns: self 16 | """ 17 | self.clear() 18 | self.extend(other) 19 | return self 20 | 21 | def remove_empty(self, fun=None): 22 | """ 23 | Removes empty elements from the list. 24 | 25 | Complexity: O(3*n) 26 | params: 27 | fun: a function that takes an element and returns whether it 28 | should be kept (defaults to bool()) 29 | returns: self 30 | """ 31 | if not fun: 32 | fun = bool 33 | 34 | tmp = [] 35 | for elem in self: 36 | if fun(elem): 37 | tmp.append(elem) 38 | 39 | self.reset(tmp) 40 | del tmp 41 | return self 42 | 43 | def rindex(self, elem): 44 | """ 45 | Gets the index of an element starting from the end. Does not copy the list. 46 | 47 | Complexity: O(n) 48 | params: 49 | elem: the element that should be found 50 | returns: self 51 | """ 52 | for i, find in enumerate(reversed(self)): 53 | if elem == find: 54 | return i 55 | return -1 56 | 57 | def take(self, num): 58 | """ 59 | A generator that returns a subarray (from 0 to num). 60 | 61 | Complexity: O(n+k) where k is the param 62 | params: 63 | num: the upper limit of the generator slice 64 | """ 65 | for i, elem in enumerate(self): 66 | if num <= i: 67 | break 68 | yield elem 69 | 70 | def take_while(self, fun): 71 | """ 72 | A generator that returns elements while a given function fun returns True. 73 | 74 | Complexity: O(n) 75 | params: 76 | fun: the predicate function 77 | """ 78 | for elem in self: 79 | if fun(elem): 80 | yield elem 81 | else: 82 | break 83 | 84 | def drop(self, num): 85 | """ 86 | A generator that returns a subarray (from num to len(List)). 87 | 88 | Complexity: O(n+k) where k is the param 89 | params: 90 | num: the lower limit of the generator slice 91 | """ 92 | for elem in self[num:]: 93 | yield elem 94 | 95 | def drop_while(self, fun): 96 | """ 97 | A generator that skips elements while a given function fun returns True. 98 | 99 | Complexity: O(n+k) 100 | params: 101 | fun: the predicate function 102 | """ 103 | i = 0 104 | for i, elem in enumerate(self): 105 | if not fun(elem): 106 | break 107 | 108 | for elem in self[i:]: 109 | yield elem 110 | 111 | def clear(self): 112 | """ 113 | A reimplemented version of clear because it only exists in Python3. 114 | 115 | Complexity: O(n) 116 | returns: self 117 | """ 118 | del self[:] 119 | return self 120 | 121 | def append(self, *args, **kwargs): 122 | """ 123 | Augmented update function that returns self. 124 | 125 | returns: self 126 | """ 127 | super(List, self).append(*args, **kwargs) 128 | return self 129 | 130 | def extend(self, *args, **kwargs): 131 | """ 132 | Augmented extend function that returns self. 133 | 134 | returns: self 135 | """ 136 | super(List, self).extend(*args, **kwargs) 137 | return self 138 | 139 | def get(self, index, dflt=None): 140 | """ 141 | A getter function that behaves like dict.get. 142 | 143 | params: 144 | index: the index to get 145 | dflt: the default return value (defaults to None) 146 | returns: self 147 | """ 148 | if len(self) > index: 149 | return self[index] 150 | return dflt 151 | 152 | def flatten(self): 153 | """ 154 | Returns a new deeply flattened list. 155 | 156 | Complexity: O(n) 157 | returns: the flattened list 158 | """ 159 | flattened = [] 160 | for item in self: 161 | if isinstance(item, List): 162 | flattened.extend(item.flatten()) 163 | elif isinstance(item, list): 164 | flattened.extend(List(item).flatten()) 165 | else: 166 | flattened.append(item) 167 | return flattened 168 | -------------------------------------------------------------------------------- /hawkweed/classes/repr.py: -------------------------------------------------------------------------------- 1 | """The Representation base class.""" 2 | class Repr(object): 3 | """The Representation base class.""" 4 | def __repr__(self): 5 | return "{}({})".format(self.__class__.__name__, super(Repr, self).__repr__()) 6 | 7 | def __str__(self): 8 | return self.__repr__() 9 | -------------------------------------------------------------------------------- /hawkweed/classes/set_cls.py: -------------------------------------------------------------------------------- 1 | """An augmented version of the set type.""" 2 | from hawkweed.classes.repr import Repr 3 | 4 | class Set(Repr, set): 5 | """An augmented version of the set type.""" 6 | def reset(self, other): 7 | """ 8 | Resets a set to another sets content 9 | 10 | Complexity: O(n) 11 | params: 12 | other: the set this set should be resetted to 13 | returns: self 14 | """ 15 | self.clear() 16 | self.update(other) 17 | return self 18 | 19 | def remove_empty(self, fun=None): 20 | """ 21 | Removes empty elements from the list. 22 | 23 | Complexity: O(n+k) where k is the number of things to remove 24 | params: 25 | fun: a function that takes an element and returns whether 26 | it should be kept (defaults to bool()) 27 | returns: self 28 | """ 29 | if not fun: 30 | fun = bool 31 | 32 | rem = [] 33 | for elem in self: 34 | if not fun(elem): 35 | rem.append(elem) 36 | 37 | for elem in rem: 38 | self.remove(elem) 39 | 40 | return self 41 | 42 | def clear(self): 43 | """ 44 | Augmented clear function that returns self. 45 | 46 | returns: self 47 | """ 48 | super(Set, self).clear() 49 | return self 50 | 51 | def remove(self, *args): 52 | """ 53 | Augmented remove function that returns self. 54 | 55 | returns: self 56 | """ 57 | super(Set, self).remove(*args) 58 | return self 59 | 60 | def add(self, *args): 61 | """ 62 | Augmented add function that returns self. 63 | 64 | returns: self 65 | """ 66 | super(Set, self).add(*args) 67 | return self 68 | 69 | def update(self, *args): 70 | """ 71 | Augmented update function that returns self. 72 | 73 | returns: self 74 | """ 75 | super(Set, self).update(*args) 76 | return self 77 | -------------------------------------------------------------------------------- /hawkweed/computed/__init__.py: -------------------------------------------------------------------------------- 1 | """Properties and system information""" 2 | import sys 3 | 4 | PY3 = sys.version_info.major == 3 5 | -------------------------------------------------------------------------------- /hawkweed/functional/__init__.py: -------------------------------------------------------------------------------- 1 | """A collection of useful functions""" 2 | from hawkweed.functional.primitives import * 3 | from hawkweed.functional.logical import * 4 | from hawkweed.functional.mathematical import * 5 | from hawkweed.functional.list_prims import * 6 | -------------------------------------------------------------------------------- /hawkweed/functional/list_prims.py: -------------------------------------------------------------------------------- 1 | """A few utility functions concerning indexable things""" 2 | from hawkweed.functional.primitives import curry 3 | from hawkweed.functional.mathematical import inc, dec 4 | 5 | def first(element): 6 | """ 7 | A wrapper around element[0]. 8 | 9 | params: 10 | element: an element that implements __getitem__ 11 | """ 12 | return element[0] 13 | 14 | def rest(element): 15 | """ 16 | A wrapper around element[1:]. 17 | 18 | params: 19 | element: an element that implements __getitem__ 20 | """ 21 | return element[1:] 22 | 23 | def second(element): 24 | """ 25 | A wrapper around element[1]. 26 | 27 | params: 28 | element: an element that implements __getitem__ 29 | """ 30 | return element[1] 31 | 32 | def last(element): 33 | """ 34 | A wrapper around element[-1]. 35 | 36 | params: 37 | element: an element that implements __getitem__ 38 | """ 39 | return element[-1] 40 | 41 | @curry 42 | def get(index, element): 43 | """ 44 | A wrapper around element[index]. 45 | 46 | params: 47 | index: the index at which we should get 48 | element: an element that implements __getitem__ 49 | """ 50 | return element[index] 51 | 52 | @curry 53 | def remove_from(index, element): 54 | """ 55 | A wrapper around del element[index]. 56 | 57 | params: 58 | index: the index at which we should delete 59 | element: an element that implements __delitem__ 60 | """ 61 | del element[index] 62 | return element 63 | 64 | @curry 65 | def remove_from_keep(index, element): 66 | """ 67 | Non-destructively deletes the element at index index. 68 | 69 | Complexity: O(n) 70 | params: 71 | index: the index at which we should delete 72 | element: an element that implements __delitem__ 73 | returns: the new list 74 | """ 75 | return element[:index] + element[inc(index):] 76 | 77 | @curry 78 | def aperture(n, l): 79 | """ 80 | Creates a generator of consecutive sublists of size n. 81 | If n is bigger than the length of the list, the 82 | generator will be empty. 83 | 84 | Complexity: O(slice_size*n) 85 | params: 86 | n: the slice size 87 | l: the list we should create the generator from 88 | returns: the generator 89 | """ 90 | index = 0 91 | stop = len(l) - dec(n) 92 | while index < stop: 93 | yield l[index:index+n] 94 | index += 1 95 | 96 | @curry 97 | def get_attr(attr, element): 98 | """ 99 | Like get, but for attributes. 100 | 101 | Complexity: O(1) 102 | params: 103 | attr: the attribute to get 104 | element: the element to search 105 | returns: the attribute 106 | """ 107 | return getattr(element, attr) 108 | 109 | @curry 110 | def take(n, l): 111 | """ 112 | Takes n elements from the list l. 113 | 114 | Complexity: O(n) 115 | params: 116 | n: the number of elements to take 117 | l: the list to take from 118 | returns: a generator object 119 | """ 120 | index = 0 121 | while index < n: 122 | yield l[index] 123 | index += 1 124 | -------------------------------------------------------------------------------- /hawkweed/functional/logical.py: -------------------------------------------------------------------------------- 1 | """A small collection of functions for logic.""" 2 | from hawkweed.functional.primitives import curry 3 | 4 | @curry 5 | def all(fun, values): 6 | """ 7 | A function that will return True if the predicate is True for all 8 | provided values. 9 | 10 | Complexity: O(n) 11 | params: 12 | fun: the predicate function 13 | values: the values to test against 14 | returns: boolean 15 | """ 16 | for element in values: 17 | if not fun(element): 18 | return False 19 | return True 20 | 21 | @curry 22 | def any(fun, values): 23 | """ 24 | A function that will return True if the predicate is True for any 25 | of the provided values. 26 | 27 | Complexity: O(n) 28 | params: 29 | fun: the predicate function 30 | values: the values to test against 31 | returns: boolean 32 | """ 33 | for element in values: 34 | if fun(element): 35 | return True 36 | return False 37 | 38 | @curry 39 | def and_(fst, snd): 40 | """Wraps the logical and function.""" 41 | return fst and snd 42 | 43 | @curry 44 | def or_(fst, snd): 45 | """Wraps the logical and function.""" 46 | return fst or snd 47 | 48 | def not_(val): 49 | """Wraps the logical not function.""" 50 | return not val 51 | 52 | @curry 53 | def complement(fun, *args, **kwargs): 54 | """ 55 | Takes a function and it's args and returns it's complement. 56 | 57 | Complexity: O(1) 58 | params: 59 | fun: the function to complement 60 | *args: the arguments that should be passed to the function 61 | **kwaargs: the keyword arguments that should be passed to the function 62 | """ 63 | return not fun(*args, **kwargs) 64 | 65 | def false(): 66 | """ 67 | Always returns False. 68 | 69 | Complexity: O(1) 70 | returns: False 71 | """ 72 | return False 73 | 74 | def true(): 75 | """ 76 | Always returns True. 77 | 78 | Complexity: O(1) 79 | returns: True 80 | """ 81 | return True 82 | -------------------------------------------------------------------------------- /hawkweed/functional/mathematical.py: -------------------------------------------------------------------------------- 1 | """A few mathematical functions""" 2 | from hawkweed.functional.primitives import curry 3 | 4 | def inc(num): 5 | """ 6 | A function that increments its argument by 1. 7 | 8 | Complexity: O(1) 9 | params: 10 | num: the number to increment 11 | returns: the incremented number 12 | """ 13 | return num + 1 14 | 15 | def dec(num): 16 | """ 17 | A function that decrements its argument by 1. 18 | 19 | Complexity: O(1) 20 | params: 21 | num: the number to decrement 22 | returns: the decremented number 23 | """ 24 | return num - 1 25 | 26 | @curry 27 | def add(fst, snd): 28 | """This is a functional wrapper around the + operator""" 29 | return fst + snd 30 | 31 | @curry 32 | def sub(fst, snd): 33 | """This is a functional wrapper around the - operator""" 34 | return fst - snd 35 | 36 | @curry 37 | def mul(fst, snd): 38 | """This is a functional wrapper around the * operator""" 39 | return fst * snd 40 | 41 | @curry 42 | def div(fst, snd): 43 | """This is a functional wrapper around the / operator""" 44 | return fst / snd 45 | 46 | @curry 47 | def mod(fst, snd): 48 | """This is a functional wrapper around the % operator""" 49 | return fst % snd 50 | 51 | @curry 52 | def clamp(frm, to, value): 53 | """ 54 | Clamps an ordinable type between two others. 55 | 56 | Complexity: O(1) 57 | params: 58 | frm: the lower end 59 | to: the upper end 60 | value: the value 61 | returns: the clamped value 62 | """ 63 | if frm > to: 64 | raise ValueError("frm cannot be bigger than to in clamp") 65 | if value > to: 66 | return to 67 | if value < frm: 68 | return frm 69 | return value 70 | -------------------------------------------------------------------------------- /hawkweed/functional/primitives.py: -------------------------------------------------------------------------------- 1 | """A collection of functional primitives""" 2 | from functools import wraps, partial 3 | from functools import reduce as _reduce 4 | from inspect import getargspec 5 | 6 | 7 | def curry(fun): 8 | """ 9 | A working but dirty version of a currying function/decorator. 10 | """ 11 | def _internal_curry(fun, original=None, given=0): 12 | if original is None: 13 | original = fun 14 | spec = getargspec(original) 15 | opt = len(spec.defaults or []) 16 | needed = len(spec.args) - given - opt 17 | @wraps(fun) 18 | def internal(*args, **kwargs): 19 | """The internal currying function""" 20 | if len(args) >= needed: 21 | return fun(*args, **kwargs) 22 | else: 23 | return _internal_curry(wraps(fun)(partial(fun, *args, **kwargs)), 24 | original, 25 | given=len(args)) 26 | return internal 27 | return _internal_curry(fun) 28 | 29 | @curry 30 | def map(fun, values): 31 | """ 32 | A function that maps a function to a list of values and returns the new generator. 33 | 34 | Complexity: O(n*k) where k is the complexity of the given function 35 | params: 36 | fun: the function that should be applied 37 | values: the list of values we should map over 38 | returns: 39 | the new generator 40 | """ 41 | return (fun(value) for value in values) 42 | 43 | @curry 44 | def filter(fun, values): 45 | """ 46 | A function that filters a list of values by a predicate function and returns 47 | a generator. 48 | 49 | Complexity: O(n*k) where k is the complexity of the given function 50 | params: 51 | fun: the fucntion that should be applied 52 | values: the list of values we should filter 53 | returns: 54 | the new generator 55 | """ 56 | return (value for value in values if fun(value)) 57 | 58 | @curry 59 | def reduce(fun, init, values=None): 60 | """ 61 | A function that reduces a list to a single value using a given function. 62 | 63 | Complexity: O(n*k) where k is the complexity of the given function 64 | params: 65 | fun: the function that should be applied 66 | values: the list of values we should reduce 67 | returns: 68 | the reduced value 69 | """ 70 | if values is None: 71 | return _reduce(fun, init) 72 | else: 73 | return _reduce(fun, values, init) 74 | 75 | @curry 76 | def apply(fun, args, kwargs=None): 77 | """ 78 | applies a list of arguments (and an optional dict of keyword arguments) 79 | to a function. 80 | 81 | Complexity: O(k) where k is the complexity of the given function 82 | params: 83 | fun: the function that should be applied 84 | args: the list of values we should reduce 85 | returns: 86 | the reduced value 87 | """ 88 | if kwargs is None: 89 | kwargs = {} 90 | return fun(*args, **kwargs) 91 | 92 | def pipe(*funs): 93 | """ 94 | composes a bunch of functions. They will be applied one after the other. 95 | 96 | Complexity: depends on the given functions 97 | params: 98 | *funs: the functions that should be chained 99 | returns: the chained function 100 | """ 101 | def internal(*args, **kwargs): 102 | """The internal piping function""" 103 | return reduce(lambda acc, fun: fun(acc), 104 | funs[0](*args, **kwargs), 105 | funs[1:]) 106 | return internal 107 | 108 | def compose(*funs): 109 | """ 110 | composes a bunch of functions. They will be applied in reverse order 111 | (so this function is the reverse of pipe). 112 | 113 | Complexity: depends on the given functions 114 | params: 115 | *funs: the functions that should be chained 116 | returns: the chained function 117 | """ 118 | return apply(pipe, reversed(funs)) 119 | 120 | def starpipe(*funs): 121 | """ 122 | composes a bunch of functions. They will be applied one after the other. 123 | The arguments will be passed as star args. 124 | 125 | Complexity: depends on the given functions 126 | params: 127 | *funs: the functions that should be chained 128 | returns: the chained function 129 | """ 130 | def internal(*args, **kwargs): 131 | """The internal piping function""" 132 | return reduce(lambda acc, fun: fun(*acc), 133 | funs[0](*args, **kwargs), 134 | funs[1:]) 135 | return internal 136 | 137 | def starcompose(*funs): 138 | """ 139 | composes a bunch of functions. They will be applied in reverse order 140 | (so this function is the reverse of starpipe). Like in starpipe, arguments 141 | will be passed as starargs. 142 | 143 | Complexity: depends on the given functions 144 | params: 145 | *funs: the functions that should be chained 146 | returns: the chained function 147 | """ 148 | return apply(starpipe, reversed(funs)) 149 | 150 | 151 | def identity(value): 152 | """ 153 | The identity function. Takes a value and returns it. 154 | 155 | Complexity: O(1) 156 | params: 157 | value: the value 158 | returns: the value 159 | """ 160 | return value 161 | 162 | @curry 163 | def tap(fun, value): 164 | """ 165 | A function that takes a function and a value, applies the function 166 | to the value and returns the value. 167 | 168 | Complexity: O(k) where k is the complexity of the given function 169 | params: 170 | fun: the function 171 | value: the value 172 | returns: the value 173 | """ 174 | fun(value) 175 | return value 176 | 177 | def constantly(value): 178 | """ 179 | A generator that returns the given value forever. 180 | 181 | Complexity: O(1) 182 | params: 183 | value: the value to return 184 | returns: an infinite generator of the value 185 | """ 186 | while True: 187 | yield value 188 | 189 | def delay(fun, *args, **kwargs): 190 | """ 191 | A function that takes a function and its arguments and delays its execution 192 | until it is needed. It also caches the executed return value and prevents 193 | it from being executed again (always returning the first result). 194 | 195 | params: 196 | fun: the function 197 | args: the function's args 198 | kwargs: the function's keyword arguments 199 | returns: the function result 200 | """ 201 | # this is a horrible hack around Python 2.x's lack of nonlocal 202 | _int = ["__delay__unset"] 203 | @wraps(fun) 204 | def internal(): 205 | """The internal delay function""" 206 | if _int[0] == "__delay__unset": 207 | _int[0] = fun(*args, **kwargs) 208 | return _int[0] 209 | return internal 210 | 211 | @curry 212 | def flip(fun, first, second, *args): 213 | """ 214 | Takes a function and applies its arguments in reverse order. 215 | 216 | params: 217 | fun: the function 218 | first: the first argument 219 | second: the second argument 220 | args: the remaining args (this weird first, second, args thing 221 | is there to prevent preemptive passing of arguments) 222 | returns: the result of the function fun 223 | """ 224 | return apply(fun, reversed(args + (first, second))) 225 | -------------------------------------------------------------------------------- /hawkweed/monads/__init__.py: -------------------------------------------------------------------------------- 1 | """A few monads""" 2 | from hawkweed.monads.monad import Monad 3 | from hawkweed.monads.listm import ListM 4 | from hawkweed.monads.continuation import Continuation 5 | from hawkweed.monads.identity import Identity 6 | from hawkweed.monads.maybe import * 7 | from hawkweed.monads.io import IO, CachedIO 8 | from hawkweed.monads.either import either 9 | -------------------------------------------------------------------------------- /hawkweed/monads/continuation.py: -------------------------------------------------------------------------------- 1 | """The continuation monad""" 2 | from hawkweed.monads.monad import Monad 3 | 4 | class Continuation(Monad): 5 | """The continuation monad""" 6 | def __call__(self, cont): 7 | """ 8 | Calls the monad with a continuation. 9 | 10 | Complexity: O(k) where k is the complexity of the monad value 11 | params: 12 | cont: the continuation 13 | returns: the result of the application 14 | """ 15 | return self.value(cont) 16 | 17 | def bind(self, fun): 18 | """ 19 | The monadic bind function of Continuation. 20 | Returns a function that takes a continuation. 21 | 22 | Complexity: O(k) where k is the complexity of the monad value 23 | params: 24 | fun: the function 25 | returns: 26 | the deferred function 27 | """ 28 | return lambda cont: self.value(lambda x: fun(x)(cont)) 29 | 30 | def callcc(cc): 31 | """ 32 | call with current continuation. 33 | 34 | Complexity: varies 35 | params: 36 | cc: the current continuation 37 | returns: the continuation 38 | """ 39 | fun = lambda cont: cc(lambda val: Continuation(lambda _: cont(val)))(cont) 40 | return Continuation(fun) 41 | -------------------------------------------------------------------------------- /hawkweed/monads/either.py: -------------------------------------------------------------------------------- 1 | """The Either monad""" 2 | from hawkweed.monads.monad import Monad 3 | 4 | class Either(Monad): 5 | """The Either abstract base class""" 6 | def __init__(self, value): 7 | raise NotImplementedError("please use the type instances Left or Right") 8 | 9 | class Left(Either): 10 | """The Left instance of the Either monad""" 11 | def __init__(self, value): 12 | self.value = value 13 | 14 | def bind(self, fun): 15 | """ 16 | The monadic bind function of Left. 17 | It will just return itself. 18 | 19 | Complexity: O(1) 20 | params: 21 | fun: the function 22 | returns: self 23 | """ 24 | return self 25 | 26 | class Right(Either): 27 | """The Right instance of the Either monad""" 28 | def __init__(self, value): 29 | self.value = value 30 | 31 | def bind(self, fun): 32 | """ 33 | The monadic bind function of Right. 34 | It will just apply the function to the 35 | value and be done with it. 36 | 37 | Complexity: O(k) where k is the complexity of the function 38 | params: 39 | fun: the function 40 | returns: the transformed value 41 | """ 42 | return fun(self.value) 43 | 44 | def either(left, right, monad): 45 | """ 46 | Takes a function left, a function right and a value 47 | and binds according to the value (into left if it is a Left, 48 | into right if it is a Right). Otherwise throws a ValueError. 49 | 50 | Complexity: O(1) or complexity of the given function 51 | params: 52 | left: the function that should be executed on Left 53 | right: the function that should be executed on Right 54 | throws: ValueError 55 | returns: 56 | whatever the functions return 57 | """ 58 | if is_left(monad): 59 | return monad.bind(left) 60 | if is_right(monad): 61 | return monad.bind(right) 62 | raise ValueError("monad in either must either be left or right") 63 | 64 | def lefts(monads): 65 | """ 66 | Takes a list and returns only the instances of Left. 67 | 68 | Complexity: O(1) 69 | params: 70 | monads: the list 71 | returns: 72 | an iterable of the Left values 73 | """ 74 | return (x for x in monads if is_left(x)) 75 | 76 | def rights(monads): 77 | """ 78 | Takes a list and returns only the instances of Right. 79 | 80 | Complexity: O(1) 81 | params: 82 | monads: the list 83 | returns: 84 | a generator of the Right values 85 | """ 86 | return (x for x in monads if is_right(x)) 87 | 88 | def is_either(monad): 89 | """ 90 | Checks whether a value is an instance of Either. 91 | 92 | Complexity: O(1) 93 | params: 94 | val: the value to check 95 | returns: the truth value 96 | """ 97 | return is_left(monad) or is_right(monad) 98 | 99 | def is_left(monad): 100 | """ 101 | Checks whether a value is an instance of Left. 102 | 103 | Complexity: O(1) 104 | params: 105 | val: the value to check 106 | returns: the truth value 107 | """ 108 | return isinstance(monad, Left) 109 | 110 | def is_right(monad): 111 | """ 112 | Checks whether a value is an instance of Right. 113 | 114 | Complexity: O(1) 115 | params: 116 | val: the value to check 117 | returns: the truth value 118 | """ 119 | return isinstance(monad, Right) 120 | 121 | def partition_eithers(monads): 122 | """ 123 | Takes a list and returns a two-element Atuple where the first 124 | element is a list of all the instances of Left and the second 125 | element is a list of all the instances of Right. 126 | 127 | Complexity: O(1) 128 | params: 129 | monads: the list of monads 130 | returns: the tuple 131 | """ 132 | return lefts(monads), rights(monads) 133 | -------------------------------------------------------------------------------- /hawkweed/monads/identity.py: -------------------------------------------------------------------------------- 1 | """The identity monad""" 2 | from hawkweed.monads.monad import Monad 3 | 4 | class Identity(Monad): 5 | """The identity monad""" 6 | def bind(self, fun): 7 | """ 8 | The monadic bind function of Identity. 9 | It will just apply the function to the 10 | value and be done with it. 11 | 12 | Complexity: O(k) where k is the complexity of the function 13 | params: 14 | fun: the function 15 | returns: the transformed value 16 | """ 17 | return fun(self.value) 18 | -------------------------------------------------------------------------------- /hawkweed/monads/io.py: -------------------------------------------------------------------------------- 1 | """The IO and CachedIO monads""" 2 | from hawkweed.functional.primitives import apply 3 | from hawkweed.monads.monad import Monad 4 | 5 | class IO(Monad): 6 | """The IO monad""" 7 | def __init__(self, value, *args): 8 | self.value = value 9 | self.args = args 10 | 11 | def ret(self): 12 | return apply(self.value, self.args) 13 | 14 | def bind(self, fun): 15 | return fun(apply(self.value, self.args)) 16 | 17 | class CachedIO(IO): 18 | """The CachedIO monad""" 19 | def __init__(self, value, *args): 20 | self.value = value 21 | self.args = args 22 | self.res = None 23 | self.evald = False 24 | 25 | def _get_cache(self): 26 | if self.evald: 27 | return self.res 28 | self.res = apply(self.value, self.args) 29 | self.evald = True 30 | return self.res 31 | 32 | def ret(self): 33 | return self._get_cache() 34 | 35 | def bind(self, fun): 36 | return fun(self._get_cache()) 37 | -------------------------------------------------------------------------------- /hawkweed/monads/listm.py: -------------------------------------------------------------------------------- 1 | """The list monad""" 2 | from hawkweed.monads.monad import Monad 3 | 4 | class ListM(Monad): 5 | """The list monad""" 6 | 7 | @staticmethod 8 | def unit(val): 9 | """ 10 | takes one value and returns a generator that yields that value. 11 | 12 | Complexity: O(1) 13 | params: 14 | val: the value to yield 15 | returns: 16 | the generator of the parameter 17 | """ 18 | yield val 19 | 20 | def bind(self, fun): 21 | """ 22 | The monadic bind function of ListM. 23 | It will apply the function to all the elements of the 24 | value and return a generator. 25 | 26 | Complexity: O(k*n) where k is the complexity of the function 27 | params: 28 | fun: the function 29 | returns: 30 | the generator of the transformed values 31 | """ 32 | for element in self.value: 33 | yield fun(element) 34 | -------------------------------------------------------------------------------- /hawkweed/monads/maybe.py: -------------------------------------------------------------------------------- 1 | """The Maybe monad""" 2 | from hawkweed.functional.list_prims import first 3 | from hawkweed.monads.monad import Monad 4 | 5 | class Maybe(Monad): 6 | """The Maybe abstract base class""" 7 | def __init__(self, value): 8 | raise NotImplementedError("please use the type instances Just or Nothing") 9 | 10 | class Just(Maybe): 11 | """The Just monad""" 12 | def __init__(self, value): 13 | self.value = value 14 | 15 | def bind(self, fun): 16 | """ 17 | The monadic bind function of Just. 18 | It will just apply the function to the 19 | value and be done with it. 20 | 21 | Complexity: O(k) where k is the complexity of the function 22 | params: 23 | fun: the function 24 | returns: the transformed value 25 | """ 26 | return fun(self.value) 27 | 28 | class Nothing(Maybe): 29 | """The Nothing monad""" 30 | def __init__(self): 31 | self.value = None 32 | 33 | def bind(self, fun): 34 | """ 35 | The monadic bind function of Nothing. 36 | It will just return itself. 37 | 38 | Complexity: O(1) 39 | params: 40 | fun: the function 41 | returns: self 42 | """ 43 | return self 44 | 45 | def is_maybe(val): 46 | """ 47 | Checks whether a value is an instance of Maybe. 48 | 49 | Complexity: O(1) 50 | params: 51 | val: the value to check 52 | returns: the truth value 53 | """ 54 | return is_just(val) or is_nothing(val) 55 | 56 | def is_just(val): 57 | """ 58 | Checks whether a value is an instance of Just. 59 | 60 | Complexity: O(1) 61 | params: 62 | val: the value to check 63 | returns: the truth value 64 | """ 65 | return isinstance(val, Just) 66 | 67 | def is_nothing(val): 68 | """ 69 | Checks whether a value is an instance of Nothing. 70 | 71 | Complexity: O(1) 72 | params: 73 | val: the value to check 74 | returns: the truth value 75 | """ 76 | return isinstance(val, Nothing) 77 | 78 | def from_just(val): 79 | """ 80 | returns the value of a just monad; otherwise 81 | throws a ValueError. 82 | 83 | Complexity: O(1) 84 | params: 85 | val: the monad to get the value from 86 | throws: ValueError 87 | returns: the monad value 88 | """ 89 | if is_just(val): 90 | return val.value 91 | raise ValueError("val is not an instance of Just") 92 | 93 | def from_maybe(val, dflt=None): 94 | """ 95 | returns the value of a maybe monad; otherwise 96 | throws a ValueError. 97 | (catamorphism function) 98 | 99 | Complexity: O(1) 100 | params: 101 | val: the monad to get the value from 102 | dflt: the default value (defaults to None) 103 | throws: ValueError 104 | returns: the monad value 105 | """ 106 | if is_just(val): 107 | return val.value 108 | elif is_nothing(val): 109 | return dflt 110 | else: 111 | raise ValueError("val is not an instance of Maybe") 112 | 113 | def list_to_maybe(val): 114 | """ 115 | if the list is empty, return Nothing. Otherwise 116 | return Just with the first element of the list. 117 | 118 | Complexity: O(1) 119 | params: 120 | val: the list fo build a Maybe from 121 | throws: ValueError 122 | returns: the monad 123 | """ 124 | if not isinstance(val, list): 125 | raise ValueError("val is not an instance of list") 126 | if val == []: 127 | return Nothing() 128 | return Just(first(val)) 129 | 130 | def maybe_to_list(val): 131 | """ 132 | if the value is nothing, return an empty list. 133 | Otherwise return a list with the monad value 134 | as single element. 135 | 136 | Complexity: O(1) 137 | params: 138 | val: the monad to build the list from 139 | throws: ValueError 140 | returns: list 141 | """ 142 | if is_nothing(val): 143 | return [] 144 | elif is_just(val): 145 | return [val.value] 146 | else: 147 | raise ValueError("val is not an instance of Maybe") 148 | 149 | def value_to_maybe(val): 150 | """ 151 | if the value is falsy, return Nothing. Otherwise 152 | return Just with the value as value. 153 | 154 | Complexity: O(1) 155 | params: 156 | val: the value to build a Maybe from 157 | returns: the monad 158 | """ 159 | if not val: 160 | return Nothing() 161 | return Just(val) 162 | 163 | def cat_maybe(vals): 164 | """ 165 | Takes a list of Maybes and returns a generator that produces 166 | all the Just values. 167 | 168 | Complexity: O(n) 169 | params: 170 | vals: the list of maybes 171 | returns: 172 | a generator producing all the Just values 173 | """ 174 | return (val for val in vals if is_just(val)) 175 | 176 | def map_maybe(fun, vals): 177 | """ 178 | Takes a function and a list of elements and maps the function 179 | over them. It returns a generator that produces all the values 180 | that are Just after applying the function. 181 | 182 | Complexity: O(n) 183 | params: 184 | fun: the function to apply 185 | vals: the list of values 186 | returns: 187 | a generator producing all the just values 188 | """ 189 | for element in vals: 190 | processed = fun(element) 191 | if is_just(processed): 192 | yield processed 193 | -------------------------------------------------------------------------------- /hawkweed/monads/monad.py: -------------------------------------------------------------------------------- 1 | """The monad base class""" 2 | from hawkweed.functional.primitives import apply 3 | 4 | class Monad(object): 5 | """The monad base class""" 6 | def __init__(self, value): 7 | self.value = value 8 | 9 | def __repr__(self): 10 | return "{}({})".format(self.__class__.__name__, self.value) 11 | 12 | def __str__(self): 13 | return self.__repr__ 14 | 15 | def ret(self): 16 | """ 17 | Returns the monad value. 18 | 19 | Complexity: O(1) 20 | returns: the monads value 21 | """ 22 | return self.value 23 | 24 | def bind(self, fun): 25 | """ 26 | The bind function. Needs to be implemented by any monad. 27 | 28 | Complexity: varies 29 | params: 30 | fun: the function to bind ti the monad value 31 | returns: 32 | the manipulated monad value 33 | """ 34 | raise NotImplementedError("Monads need to implement the bind function") 35 | 36 | def __rshift__(self, val): 37 | """ 38 | The 'bind' operator. Syntactic sugar for binding. 39 | """ 40 | return self.bind(val) 41 | 42 | @classmethod 43 | def lift(cls, fun): 44 | """ 45 | Lifts a function into the monad. 46 | 47 | Complexity: O(1) 48 | params: 49 | fun: the function to lift 50 | returns: a function that funnels values into the given 51 | function and returns the result wrapped in a monad 52 | """ 53 | def lifter(*args, **kwargs): 54 | "The lifting function""" 55 | return cls(apply(fun, args, kwargs)) 56 | return lifter 57 | 58 | def __eq__(self, other): 59 | return isinstance(other, self.__class__) and self.value == other.value 60 | 61 | def __ne__(self, other): 62 | return not self.__eq__(other) 63 | 64 | def doM(fun): 65 | """ 66 | Takes a generator and unpacks values for every yield. 67 | 68 | Complexity: O(k) where k is the complexity of fun 69 | params: 70 | fun: the generator 71 | returns: the return value of the generator 72 | """ 73 | def internal(value): 74 | """The step function""" 75 | try: 76 | res = fun.send(value).value 77 | return internal(res) 78 | except StopIteration: 79 | return value 80 | return internal(next(fun).value) 81 | 82 | def wrapM(monad): 83 | """ 84 | Takes a monad and wraps it so that any attribute that is not 85 | in the monad is automaticall forwarded to the monad value. 86 | 87 | Complexity: O(1) 88 | params: 89 | monad: the monad to wrap 90 | returns: the proxy object 91 | """ 92 | class Proxy(): 93 | """The monad proxy object""" 94 | def __init__(self, monad): 95 | self.monad = monad 96 | 97 | def __getattr__(self, prop): 98 | if hasattr(self.monad, prop): 99 | return getattr(self.monad, prop) 100 | elif hasattr(self.monad.value, prop): 101 | return getattr(self.monad.value, prop) 102 | raise AttributeError(prop) 103 | 104 | return Proxy(monad) 105 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | 4 | from setuptools import find_packages 5 | from setuptools import setup 6 | 7 | with open('README.rst') as readme: 8 | long_description = readme.read() 9 | 10 | with open('VERSION') as v: 11 | version = v.read().strip() 12 | 13 | setup( 14 | name = 'hawkweed', 15 | version = version, 16 | description = 'Extending Python builtin types', 17 | long_description = long_description, 18 | author = 'Veit Heller', 19 | author_email = 'veit@veitheller.de', 20 | license = 'MIT License', 21 | url = 'https://github.com/hellerve/hawkweed', 22 | download_url = 'https://github.com/hellerve/hawkweed/tarball/{}'.format(version), 23 | packages = find_packages(), 24 | include_package_data = True, 25 | ) 26 | -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | echo [i] Running tests against Python 2.7 2 | python2.7 hawkweed-py.test $@ 3 | echo [i] Running tests against Python 3.5 4 | python3.5 hawkweed-py.test $@ 5 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellerve/hawkweed/0cdb378f7dac5ab76ad53d6c1917f0ac5687793c/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_collection.py: -------------------------------------------------------------------------------- 1 | from hawkweed.classes import List, Dict 2 | from nose.tools import * 3 | 4 | def test_get_in(): 5 | assert_equal(List([Dict({1:1})]).get_in(0, 1), 1) 6 | 7 | def test_get_in_not_in(): 8 | assert_equal(List([Dict({1:1})]).get_in(1, 1), None) 9 | 10 | def test_get_in_dflt(): 11 | assert_equal(List([1]).get_in(1, "dflt"), "dflt") 12 | 13 | def test_update_in(): 14 | assert_equal(List([Dict({1:1})]).update_in(0, 1, to=2), 15 | List([Dict({1:2})])) 16 | 17 | def test_get_in_not_in(): 18 | assert_equal(List([Dict({1:1})]).get_in(1, 1), None) 19 | 20 | def test_get_in_dflt(): 21 | assert_equal(List([1]).get_in(1, dflt="dflt"), "dflt") 22 | 23 | def test_get_in_reset_dflt(): 24 | x = List([1]) 25 | x.DFLT = "dflt" 26 | assert_equal(x.get_in(1), "dflt") 27 | -------------------------------------------------------------------------------- /tests/test_continuation.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.monads.continuation import Continuation, callcc 3 | 4 | def test_call(): 5 | assert_equal(Continuation(lambda x: x + 10)(10), 20) 6 | 7 | def test_bind(): 8 | assert_equal(Continuation(lambda x: x)(lambda x: x + 10)(10), 20) 9 | 10 | def test_callcc(): 11 | assert_equal(callcc(lambda x: x)(lambda x: x)(10)(20), 20) 12 | -------------------------------------------------------------------------------- /tests/test_dict.py: -------------------------------------------------------------------------------- 1 | from hawkweed.classes import Dict 2 | from nose.tools import * 3 | 4 | def test_reverse(): 5 | assert_equal(Dict({1: 2, 3: 4}).reverse(), Dict({2: 1, 4: 3})) 6 | 7 | def test_reset(): 8 | reset = Dict({2: 1, 4: 3}) 9 | assert_equal(Dict({1: 2, 3: 4}).reset(reset), reset) 10 | 11 | def test_clear(): 12 | assert_equal(Dict({1: 2, 3: 4}).clear(), Dict({})) 13 | 14 | def test_setdefault(): 15 | d = Dict({2: 1, 4: 3}) 16 | assert_equal(d.setdefault(1), d) 17 | 18 | def test_update(): 19 | assert_equal(Dict({1: 2, 3: 4}).update({2: 3}), Dict({1: 2, 2: 3, 3: 4})) 20 | 21 | def test_remove_empty(): 22 | assert_equal(Dict({1: 2, 3: None}).remove_empty(), Dict({1: 2})) 23 | 24 | def test_remove_empty_keys(): 25 | assert_equal(Dict({1: 2, None: 3}).remove_empty(filter_keys=True), Dict({1: 2})) 26 | 27 | def test_remove_empty_custom_fun(): 28 | assert_equal(Dict({1: 2, 4: 3}).remove_empty(fun=lambda x: x != 3), Dict({1: 2})) 29 | 30 | def test_reduce(): 31 | assert_equal(Dict({1: 2, 3: 4}).reduce(lambda acc, k, v: acc + k + v, 0), 10) 32 | 33 | def test_reduce_no_acc(): 34 | assert_equal(Dict({1: 2, 3: 4}).reduce(lambda acc, k, v: acc + (k, v)), (1, 2, 3, 4)) 35 | 36 | def test_pick(): 37 | d = Dict({1: 2, 3: 4, 5: 6}) 38 | assert_equal(d.pick(1, 5), Dict({1: 2, 5: 6})) 39 | -------------------------------------------------------------------------------- /tests/test_either.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | from nose.tools import * 3 | from hawkweed.monads.either import Either, Left, Right, is_right,\ 4 | is_left, is_either, either, lefts, rights, partition_eithers 5 | from hawkweed.functional.primitives import identity 6 | 7 | def test_right(): 8 | assert_equal(Right(10).bind(identity), 10) 9 | 10 | def test_nothing(): 11 | l = Left("failure") 12 | assert_equal(l.bind(lambda _: "lol"), l) 13 | 14 | def test_is_right(): 15 | assert_true(is_right(Right(10))) 16 | assert_false(is_right(Left("no"))) 17 | assert_false(is_right(10)) 18 | 19 | def test_is_left(): 20 | assert_true(is_left(Left("yes"))) 21 | assert_false(is_left(Right(10))) 22 | assert_false(is_left(10)) 23 | 24 | def test_is_either(): 25 | assert_true(is_either(Right(10))) 26 | assert_true(is_either(Left("yes"))) 27 | assert_false(is_either(10)) 28 | 29 | def test_either(): 30 | v = "val" 31 | either(lambda x: assert_equal(Left(v), x), None, Left(v)) 32 | either(None, lambda x: assert_equal(v, x), Right(v)) 33 | with assert_raises(ValueError): 34 | either(None, None, 10) 35 | 36 | def test_lefts(): 37 | l = [Left("failure"), Left("i died"), Left("noes")] 38 | lr = l + [Right(1)] 39 | assert_equal(list(lefts(lr)), l) 40 | 41 | def test_rights(): 42 | r = [Right(x) for x in range(4)] 43 | rl = [Left("moo")] + r 44 | assert_equal(list(rights(rl)), r) 45 | 46 | def test_partition_eithers(): 47 | r = [Right(x) for x in range(4)] 48 | l = [Left(x) for x in ["failure"] * 4] 49 | rl = list(chain.from_iterable(zip(r, l))) 50 | assert_equal([list(x) for x in partition_eithers(rl)], [l, r]) 51 | -------------------------------------------------------------------------------- /tests/test_file.txt: -------------------------------------------------------------------------------- 1 | i am a line 2 | i am another line 3 | -------------------------------------------------------------------------------- /tests/test_future.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.classes.future import Future 3 | from hawkweed.functional.primitives import curry 4 | from hawkweed.functional.mathematical import inc 5 | 6 | def test_of_fork(): 7 | Future.of(1).fork(curry(assert_equal)(1), None) 8 | 9 | def test_reject(): 10 | Future.reject(1).fork(None, curry(assert_equal)(1)) 11 | 12 | def test_encase(): 13 | Future.encase(inc, args=(0,)).fork(curry(assert_equal)(1), None) 14 | 15 | def test_apply(): 16 | Future.of(1).apply(inc).fork(curry(assert_equal)(2), None) 17 | 18 | def test_chain(): 19 | Future.of(1).chain(Future.encase(inc)).fork(curry(assert_equal)(2), None) 20 | -------------------------------------------------------------------------------- /tests/test_identity.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.monads.identity import Identity 3 | 4 | def test_identity(): 5 | assert_equal(Identity(1).value, 1) 6 | assert_equal(Identity(1).bind(lambda x: x + 1), 2) 7 | -------------------------------------------------------------------------------- /tests/test_io.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.monads.io import IO, CachedIO 3 | 4 | def test_io(): 5 | with open("tests/test_file.txt") as f: 6 | linegen = IO(f.readline) 7 | res1 = linegen.ret() 8 | res2 = linegen.ret() 9 | assert_not_equal(res1, res2) 10 | 11 | def test_cached_io(): 12 | with open("tests/test_file.txt") as f: 13 | linegen = CachedIO(f.readline) 14 | res1 = linegen.ret() 15 | res2 = linegen.ret() 16 | assert_equal(res1, res2) 17 | -------------------------------------------------------------------------------- /tests/test_iterable.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from nose.tools import assert_equal, assert_raises 3 | from hawkweed.classes import List 4 | 5 | def prepare(f): 6 | def internal(): 7 | x = List([1,2,3,4]) 8 | f(x) 9 | return internal 10 | 11 | @prepare 12 | def test_concatenate(x): 13 | y = List([5,6,7,8]) 14 | for i, x in enumerate(x.concatenate(y)): 15 | assert_equal(i+1, x) 16 | 17 | @prepare 18 | def test_concatenate_multiple(x): 19 | y = List([5,6,7,8]) 20 | z = List([9,10]) 21 | for i, x in enumerate(x.concatenate(y, z)): 22 | assert_equal(i+1, x) 23 | 24 | @prepare 25 | def test_concatenate_none(x): 26 | for i, x in enumerate(x.concatenate()): 27 | assert_equal(i+1, x) 28 | 29 | def test_nth(): 30 | x = List(range(100)) 31 | for i in x.nth(2): 32 | assert_equal(i % 2, 0) 33 | 34 | def test_nth_1(): 35 | x = List(range(100)) 36 | for i, x in enumerate(x.nth(1)): 37 | assert_equal(i, x) 38 | 39 | @prepare 40 | def test_nth_smaller_than_0(x): 41 | with assert_raises(ValueError) as ve: 42 | x.nth(0) 43 | assert_equal(str(ve.exception), "step size must be bigger than 0, was 0") 44 | with assert_raises(ValueError) as ve: 45 | x.nth(-1) 46 | assert_equal(str(ve.exception), "step size must be bigger than 0, was -1") 47 | -------------------------------------------------------------------------------- /tests/test_list.py: -------------------------------------------------------------------------------- 1 | from hawkweed.classes import List 2 | from nose.tools import * 3 | 4 | def test_clear(): 5 | assert_equal(List([1,2]).clear(), List([])) 6 | 7 | def test_append(): 8 | assert_equal(List([1,2]).append(3), List([1, 2, 3])) 9 | 10 | def test_extend(): 11 | assert_equal(List([1,2]).extend([3]), List([1, 2, 3])) 12 | 13 | def test_get(): 14 | assert_equal(List([1]).get(0), 1) 15 | 16 | def test_get_not_in(): 17 | assert_equal(List([1]).get(1), None) 18 | 19 | def test_get_dflt(): 20 | assert_equal(List([1]).get(1, "dflt"), "dflt") 21 | 22 | def test_reset(): 23 | reset = List([1]) 24 | assert_equal(List([1, 2, 3, 4]).reset(reset), reset) 25 | 26 | def test_remove_empty(): 27 | assert_equal(List([1, 2, 3, None]).remove_empty(), List([1, 2, 3])) 28 | 29 | def test_remove_empty_custom_fun(): 30 | assert_equal(List([1, 2, 3, 4]).remove_empty(fun=lambda x: x != 4), List([1, 2, 3])) 31 | 32 | def test_take(): 33 | assert_equal(List(List(range(10)).take(5)), List(range(5))) 34 | 35 | def test_take_iterate(): 36 | for i, e in enumerate(List(range(10)).take(5)): 37 | assert_equal(i, e) 38 | 39 | def test_drop(): 40 | assert_equal(List(List(range(10)).drop(5)), List(range(5,10))) 41 | 42 | def test_drop_iterate(): 43 | dropped = 5 44 | for i, e in enumerate(List(range(10)).drop(dropped)): 45 | assert_equal(i + dropped, e) 46 | 47 | def test_take_while(): 48 | assert_equal(List(List(range(10)).take_while(lambda x: x < 5)), List(range(5))) 49 | 50 | def test_take_while_iterate(): 51 | for i, e in enumerate(List(range(10)).take_while(lambda x: x < 5)): 52 | assert_equal(i, e) 53 | 54 | def test_drop_while(): 55 | assert_equal(List(List(range(10)).drop_while(lambda x: x < 5)), List(range(5,10))) 56 | 57 | def test_drop_while_iterate(): 58 | dropped = 5 59 | for i, e in enumerate(List(range(10)).drop_while(lambda x: x < dropped)): 60 | assert_equal(i + dropped, e) 61 | 62 | def test_rindex(): 63 | assert_equal(List(range(10)).rindex(8), 1) 64 | 65 | def test_flatten(): 66 | assert_equal(List([[0], [1, 2], []]).flatten(), [0, 1, 2]) 67 | 68 | def test_flatten_deeply(): 69 | assert_equal(List([[1], [2], 3, [4, [5]]]).flatten(), list(range(1, 6))) 70 | -------------------------------------------------------------------------------- /tests/test_list_prims.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.functional.list_prims import * 3 | 4 | def test_first(): 5 | assert_equal(first([1]), 1) 6 | 7 | def test_rest(): 8 | assert_equal(rest([1, 2, 3]), [2, 3]) 9 | 10 | def test_second(): 11 | assert_equal(second([1, 2]), 2) 12 | 13 | def test_last(): 14 | assert_equal(last([1, 2, 3]), 3) 15 | 16 | def test_get(): 17 | assert_equal(get(2, [1, 2, 3]), 3) 18 | 19 | def test_remove_from(): 20 | x = [1, 2, 3] 21 | assert_equal(remove_from(2, x), [1, 2]) 22 | assert_equal(x, [1, 2]) 23 | 24 | def test_remove_from_keep(): 25 | x = [1, 2, 3] 26 | assert_equal(remove_from_keep(2, x), [1, 2]) 27 | assert_equal(x, [1, 2, 3]) 28 | 29 | def test_aperture(): 30 | x = [1, 2, 3] 31 | assert_equal(list(aperture(2, x)), [[1, 2], [2, 3]]) 32 | 33 | def test_aperture_empty(): 34 | assert_equal(list(aperture(2, [])), []) 35 | 36 | def test_aperture_bigger_n(): 37 | assert_equal(list(aperture(2, [1])), []) 38 | 39 | def test_take(): 40 | assert_equal(list(take(5, list(range(10)))), list(range(5))) 41 | 42 | def test_getattr(): 43 | class T(): 44 | no = 1 45 | 46 | assert_equal(get_attr("no")(T()), 1) 47 | -------------------------------------------------------------------------------- /tests/test_listm.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.functional.primitives import identity 3 | from hawkweed.monads.listm import ListM 4 | 5 | def test_unit(): 6 | x = ListM.unit(1) 7 | assert_equal(next(x), 1) 8 | with assert_raises(StopIteration): 9 | next(x) 10 | 11 | def test_bind(): 12 | x = ListM(range(10)) 13 | for i, x in enumerate(x.bind(identity)): 14 | assert_equal(i, x) 15 | -------------------------------------------------------------------------------- /tests/test_logical.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.functional.logical import * 3 | 4 | def test_all(): 5 | assert_true(all(lambda x: x > 0, range(1,100))) 6 | assert_false(all(lambda x: x > 0, range(100,-1,-1))) 7 | 8 | def test_any(): 9 | assert_true(any(lambda x: x > 0, [0, 0, 0, 1, 0])) 10 | assert_false(any(lambda x: x > 0, [0, 0, 0, 0, -1])) 11 | 12 | def test_complement(): 13 | assert_false(complement(all, lambda x: x > 0, range(1, 3))) 14 | assert_true(complement(all, lambda x: x > 0, [0])) 15 | 16 | def test_false(): 17 | assert_false(false()) 18 | 19 | def test_true(): 20 | assert_true(true()) 21 | 22 | def test_and(): 23 | assert_true(and_(True)(True)) 24 | assert_false(and_(False)(True)) 25 | 26 | def test_or(): 27 | assert_true(or_(True)(True)) 28 | assert_false(or_(False)(False)) 29 | -------------------------------------------------------------------------------- /tests/test_math.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hellerve/hawkweed/0cdb378f7dac5ab76ad53d6c1917f0ac5687793c/tests/test_math.py -------------------------------------------------------------------------------- /tests/test_mathematical.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.functional.mathematical import * 3 | 4 | def test_inc(): 5 | for i in range(100): 6 | assert_equal(inc(i), i + 1) 7 | 8 | def test_dec(): 9 | for i in range(100): 10 | assert_equal(dec(i), i - 1) 11 | 12 | def test_add(): 13 | assert_equal(add(1)(2), 3) 14 | 15 | def test_sub(): 16 | assert_equal(sub(1)(2), -1) 17 | 18 | def test_mul(): 19 | assert_equal(mul(1)(2), 2) 20 | 21 | def test_div(): 22 | assert_equal(div(10)(10), 1) 23 | 24 | def test_mod(): 25 | assert_equal(mod(3)(2), 1) 26 | 27 | def test_clamp(): 28 | assert_equal(clamp(1, 10, 3), 3) 29 | assert_equal(clamp(1, 10, -3), 1) 30 | assert_equal(clamp(1, 10, 30), 10) 31 | -------------------------------------------------------------------------------- /tests/test_maybe.py: -------------------------------------------------------------------------------- 1 | from types import GeneratorType 2 | from nose.tools import * 3 | from hawkweed.functional.primitives import identity 4 | from hawkweed.monads.maybe import * 5 | 6 | def test_just(): 7 | assert_equal(Just(10).bind(identity), 10) 8 | 9 | def test_nothing(): 10 | assert_equal(Nothing().value, None) 11 | 12 | def test_chain(): 13 | assert_equal(Just(10).bind(lambda val: Just(11).bind(lambda val2: val + val2)), 21) 14 | assert_equal(Just(10).bind(lambda val: Nothing().bind(lambda val2: val + val)), Nothing()) 15 | 16 | def test_is_just(): 17 | assert_true(is_just(Just(1))) 18 | assert_false(is_just(Nothing())) 19 | 20 | def test_is_nothing(): 21 | assert_true(is_nothing(Nothing())) 22 | assert_false(is_nothing(Just(1))) 23 | 24 | def test_is_maybe(): 25 | assert_true(is_maybe(Just(1))) 26 | assert_true(is_maybe(Nothing())) 27 | assert_false(is_maybe(object())) 28 | 29 | def test_from_just(): 30 | assert_equal(from_just(Just(1)), 1) 31 | with assert_raises(ValueError): 32 | from_just(Nothing()) 33 | with assert_raises(ValueError): 34 | from_just(1) 35 | 36 | def test_from_maybe(): 37 | assert_equal(from_maybe(Just(1), 100), 1) 38 | assert_equal(from_maybe(Just(1)), 1) 39 | assert_equal(from_maybe(Nothing(), 100), 100) 40 | assert_equal(from_maybe(Nothing()), None) 41 | with assert_raises(ValueError): 42 | from_maybe(1) 43 | 44 | def test_list_to_maybe(): 45 | assert_equal(list_to_maybe([]), Nothing()) 46 | assert_equal(list_to_maybe([1]), Just(1)) 47 | assert_equal(list_to_maybe([1, 10]), Just(1)) 48 | with assert_raises(ValueError): 49 | list_to_maybe(1) 50 | 51 | def test_maybe_to_list(): 52 | assert_equal(maybe_to_list(Nothing()), []) 53 | assert_equal(maybe_to_list(Just(1)), [1]) 54 | with assert_raises(ValueError): 55 | maybe_to_list(1) 56 | 57 | def test_value_to_maybe(): 58 | assert_equal(value_to_maybe([]), Nothing()) 59 | assert_equal(value_to_maybe([1]), Just([1])) 60 | assert_equal(value_to_maybe(1), Just(1)) 61 | assert_equal(value_to_maybe(0), Nothing()) 62 | 63 | def test_cat_maybe(): 64 | assert_equal(list(cat_maybe([Just(1), Nothing(), Just(2)])), 65 | [Just(1), Just(2)]) 66 | 67 | def test_map_maybe(): 68 | assert_equal(list(map_maybe(identity, [Just(1), Nothing(), Just(2)])), 69 | [Just(1), Just(2)]) 70 | assert_is_instance(map_maybe(identity, [Just(1)]), GeneratorType) 71 | -------------------------------------------------------------------------------- /tests/test_monad.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.monads.monad import Monad, doM, wrapM 3 | from hawkweed.monads.maybe import Just 4 | 5 | def test_doM(): 6 | def x(): 7 | res1 = yield Just(1) 8 | res2 = yield Just(2) 9 | yield Just(res1 + res2) 10 | assert_equal(doM(x()), 3) 11 | 12 | def test_wrapM(): 13 | assert_equal(wrapM(Just(1)).imag, 0) 14 | assert_equal(wrapM(Just(1)).real, 1) 15 | assert_equal(wrapM(Just(1)).bind(lambda x: x + 1), 2) 16 | 17 | def test_ret(): 18 | assert_equal(Monad(1).ret(), 1) 19 | 20 | def test_bind_sugar(): 21 | with assert_raises(NotImplementedError): 22 | Monad(1) >> 1 23 | 24 | def test_lift(): 25 | liftM = Monad.lift(lambda x: x) 26 | assert_equal(liftM(1), Monad(1)) 27 | -------------------------------------------------------------------------------- /tests/test_primitives.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | from nose.tools import * 4 | from hawkweed.functional.primitives import * 5 | from hawkweed.functional.mathematical import add, inc 6 | 7 | def test_curry(): 8 | assert_equal(curry(lambda x, y: x + y)(1)(2), 3) 9 | assert_equal(curry(lambda x, y: x + y)(1, 2), 3) 10 | 11 | def test_curry_decorator(): 12 | @curry 13 | def add(x, y): 14 | return x + y 15 | assert_equal(add(1, 2), 3) 16 | assert_equal(add(1)(2), 3) 17 | assert_equal(add.__name__, "add") 18 | 19 | def test_map(): 20 | dbl = map(lambda x: x + x) 21 | assert_equal(list(dbl([1, 2, 3])), [2, 4, 6]) 22 | 23 | def test_reduce(): 24 | sm = reduce(lambda acc, x: acc + x) 25 | assert_equal(sm([1,2,3,4]), 10) 26 | assert_equal(sm(10, [1,2,3,4]), 20) 27 | 28 | def test_apply(): 29 | sum3 = lambda x, y, z: x + y + z 30 | assert_equal(apply(sum3, [1, 2, 3]), sum3(1, 2, 3)) 31 | sum3 = lambda x, y, z=0: x + y + z 32 | assert_equal(apply(sum3, [1, 2], {"z": 2}), sum3(1, 2, z=2)) 33 | 34 | def test_pipe(): 35 | add1 = lambda x: x + 1 36 | assert_equal(pipe(add1, add1)(2), 4) 37 | fun1 = lambda x: x[1] 38 | fun2 = lambda x: x[0] 39 | assert_equal(pipe(fun1, fun2)([1,[4]]), 4) 40 | 41 | def test_compose(): 42 | add1 = lambda x: x + 1 43 | assert_equal(pipe(add1, add1)(2), compose(add1, add1)(2)) 44 | fun1 = lambda x: x[1] 45 | fun2 = lambda x: x[0] 46 | assert_equal(compose(fun2, fun1)([1,[4]]), 4) 47 | 48 | def test_identity(): 49 | assert_equal(identity([]), []) 50 | assert_equal(identity(1), 1) 51 | 52 | def test_tap(): 53 | assert_equal(tap(identity, 1), 1) 54 | assert_equal(tap(identity)(1), 1) 55 | 56 | def test_constantly(): 57 | for i, n in enumerate(constantly(1)): 58 | if i > 99: 59 | break 60 | assert_equal(n, 1) 61 | 62 | def test_delay(): 63 | # horrible test for a horrible function, heh 64 | x = delay(range, 1, 10000) 65 | before1 = time.clock() 66 | x() 67 | after1 = time.clock() 68 | before2 = time.clock() 69 | x() 70 | after2 = time.clock() 71 | assert_true((after1 - before1) > (after2 - before2)) 72 | 73 | def test_flip(): 74 | test = lambda x, y: [x, y] 75 | args = [1, 2] 76 | assert_equal(apply(flip(test), args), list(reversed(args))) 77 | 78 | def test_starcompose(): 79 | assert_equal(starcompose(add, map(inc))([1, 2]), 5) 80 | 81 | def test_starpipe(): 82 | assert_equal(starpipe(map(inc), add)([1, 2]), 5) 83 | -------------------------------------------------------------------------------- /tests/test_repr.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | from hawkweed.classes.list_cls import List 3 | 4 | def test_repr(): 5 | assert_equal(List([0]).__repr__(), "List([0])") 6 | 7 | def test_str(): 8 | x = List([0]) 9 | assert_equal(x.__str__(), x.__repr__()) 10 | -------------------------------------------------------------------------------- /tests/test_set.py: -------------------------------------------------------------------------------- 1 | from hawkweed.classes import Set 2 | from nose.tools import * 3 | 4 | def test_clear(): 5 | assert_equal(Set({1,2}).clear(), Set({})) 6 | 7 | def test_remove(): 8 | assert_equal(Set({1}).remove(1), Set({})) 9 | 10 | def test_add(): 11 | assert_equal(Set({}).add(1), Set({1})) 12 | 13 | def test_update(): 14 | assert_equal(Set({}).update({1}), Set({1})) 15 | 16 | def test_reset(): 17 | reset = Set({1}) 18 | assert_equal(Set({1, 2, 3, 4}).reset(reset), reset) 19 | 20 | def test_remove_empty(): 21 | assert_equal(Set({1, 2, 3, None}).remove_empty(), Set({1, 2, 3})) 22 | 23 | def test_remove_empty_custom_fun(): 24 | assert_equal(Set({1, 2, 3, 4}).remove_empty(fun=lambda x: x != 4), Set({1, 2, 3})) 25 | --------------------------------------------------------------------------------