You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
3.1 KiB

3 years ago
3 years ago
3 years ago
3 years ago
  1. """
  2. A collection of functional utilities/helpers
  3. """
  4. from functools import reduce, wraps
  5. from copy import deepcopy
  6. from itertools import chain, dropwhile
  7. from typing import Tuple, Any, List, Union
  8. from gooey.python_bindings.types import Try, Success, Failure
  9. def getin(m, path, default=None):
  10. """returns the value in a nested dict"""
  11. keynotfound = ':com.gooey-project/not-found'
  12. result = reduce(lambda acc, val: acc.get(val, {keynotfound: None}), path, m)
  13. # falsey values like 0 would incorrectly trigger the default to be returned
  14. # so the keynotfound val is used to signify a miss vs just a falesy val
  15. if isinstance(result, dict) and keynotfound in result:
  16. return default
  17. return result
  18. def assoc(m, key, val):
  19. """Copy-on-write associates a value in a dict"""
  20. cpy = deepcopy(m)
  21. cpy[key] = val
  22. return cpy
  23. def dissoc(m, key, val):
  24. cpy = deepcopy(m)
  25. del cpy[key]
  26. return cpy
  27. def associn(m, path, value):
  28. """ Copy-on-write associates a value in a nested dict """
  29. def assoc_recursively(m, path, value):
  30. if not path:
  31. return value
  32. p = path[0]
  33. return assoc(m, p, assoc_recursively(m.get(p,{}), path[1:], value))
  34. return assoc_recursively(m, path, value)
  35. def associnMany(m, *args: Tuple[Union[str, List[str]], Any]):
  36. def apply(_m, change: Tuple[Union[str, List[str]], Any]):
  37. path, value = change
  38. if isinstance(path, list):
  39. return associn(_m, path, value)
  40. else:
  41. return associn(_m, path.split('.'), value)
  42. return reduce(apply, args, m)
  43. def merge(*maps):
  44. """Merge all maps left to right"""
  45. copies = map(deepcopy, maps)
  46. return reduce(lambda acc, val: acc.update(val) or acc, copies)
  47. def flatmap(f, coll):
  48. """Applies concat to the result of applying f to colls"""
  49. return list(chain(*map(f, coll)))
  50. def indexunique(f, coll):
  51. """Build a map from the collection keyed off of f
  52. e.g.
  53. [{id:1,..}, {id:2, ...}] => {1: {id:1,...}, 2: {id:2,...}}
  54. Note: duplicates, if present, are overwritten
  55. """
  56. return zipmap(map(f, coll), coll)
  57. def zipmap(keys, vals):
  58. """Return a map from keys to values"""
  59. return dict(zip(keys, vals))
  60. def compact(coll):
  61. """Returns a new list with all falsy values removed"""
  62. if isinstance(coll, dict):
  63. return {k:v for k,v in coll.items() if v is not None}
  64. else:
  65. return list(filter(None, coll))
  66. def ifPresent(f):
  67. """Execute f only if value is present and not None"""
  68. def inner(value):
  69. if value:
  70. return f(value)
  71. else:
  72. return True
  73. return inner
  74. def identity(x):
  75. """Identity function always returns the supplied argument"""
  76. return x
  77. def unit(val):
  78. return val
  79. def bind(val, f):
  80. return f(val) if val else None
  81. def lift(f):
  82. @wraps(f)
  83. def inner(x) -> Try:
  84. try:
  85. return Success(f(x))
  86. except Exception as e:
  87. return Failure(e)
  88. return inner