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.

101 lines
2.6 KiB

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