Functional

Hush provides first class higher order functions, which is the fundamental building block for functional programming. As such, the paradigm integrates very well with the language, and it is recommend as the main approach for solving problems in Hush.

Here’s an example of a simple iterator library, using functional programming and implementing basic laziness:

  1. # Iterator: traversal utilities.
  2. # Construct an iterator.
  3. # An Iterator is an object capable of successively yielding values.
  4. # Iterators are *mutable* (they may mutate their inner state as they iterate).
  5. # Parameters:
  6. # * next: function() -> @[ finished: bool, value: any ] # the iterator function
  7. # Returns the Iterator instance.
  8. function Iterator(next)
  9. @[
  10. # Provide the next method.
  11. next: next,
  12. # Get the iterator function.
  13. # Iterating mutates the iterator, which will successively yield the next value.
  14. # This should be used in for loops, as in:
  15. # for item in iterator.iter() do
  16. # ...
  17. # end
  18. iter: function ()
  19. self.next
  20. end,
  21. # Get the nth element in the iterator.
  22. # This is equivalent to calling self.next() `n + 1` times,
  23. # but might be optimized for some iterators.
  24. # Parameters:
  25. # * n: the desired element index
  26. # Returns the nth element.
  27. nth: function (n)
  28. for _ in std.range(0, n, 1) do
  29. let iteration = self.next()
  30. if iteration.finished then
  31. return iteration
  32. end
  33. end
  34. self.next()
  35. end,
  36. # Map a function over an iterator.
  37. # This function is lazy.
  38. # Parameters:
  39. # * fun: function(any) -> any # the function to apply
  40. # Returns a new iterator, consuming self.
  41. map: function (fun)
  42. let map = function (iteration)
  43. if iteration.finished then
  44. iteration
  45. else
  46. @[ finished: false, value: fun(iteration.value) ]
  47. end
  48. end
  49. let base = self
  50. let it = Iterator(
  51. function ()
  52. map(base.next())
  53. end
  54. )
  55. # Elide mapping unnecessary elements when jumping.
  56. # This is an important optimization for, e.g.:
  57. # Array([1,2,3,4,5])
  58. # .map(fun)
  59. # .skip(3)
  60. # .collect()
  61. it.nth = function (n)
  62. map(base.nth(n))
  63. end
  64. it
  65. end,
  66. # Filter the iterator by the given predicate.
  67. # This function is lazy.
  68. # Items for which the predicate returns true are kept.
  69. # Parameters:
  70. # * pred: function(any) -> bool # the predicate
  71. # Returns a new iterator, consuming self.
  72. filter: function (pred)
  73. let base = self
  74. Iterator(
  75. function ()
  76. let iteration = base.next()
  77. while not (iteration.finished or pred(iteration.value)) do
  78. iteration = base.next()
  79. end
  80. iteration
  81. end
  82. )
  83. end,
  84. # Skip elements from the iterator.
  85. # Equivalent to calling `nth(size - 1)` on an iterator.
  86. # Returns a new iterator, consuming self.
  87. skip: function(size)
  88. if size > 0 then
  89. self.nth(size - 1)
  90. end
  91. self
  92. end,
  93. # Iterate up to some elements.
  94. # This function is lazy.
  95. # The iterator that will stop after `size` elements.
  96. # Parameters:
  97. # * size: int # how many elements to keep
  98. # Returns a new iterator, consuming self.
  99. take: function (size)
  100. let base = self
  101. let it = Iterator(
  102. function ()
  103. if size == 0 then
  104. @[ finished: true ]
  105. else
  106. size = size - 1
  107. base.next()
  108. end
  109. end
  110. )
  111. it.nth = function(n)
  112. if n + 1 > size then
  113. size = 0
  114. @[ finished: true ]
  115. else
  116. size = size - n
  117. base.nth(n)
  118. end
  119. end
  120. it
  121. end,
  122. # Fold elements into a single value.
  123. # Parameters:
  124. # * merge: function(any, any) -> any # the function to merge elements
  125. # * acc: any # the default argument for merge
  126. # Returns the resulting any, consuming self.
  127. fold: function (merge, acc)
  128. for item in self.iter() do
  129. acc = merge(item, acc)
  130. end
  131. acc
  132. end,
  133. # Collect the iterator's items.
  134. # Parameters:
  135. # * target: nil, array or function(any) # where to collect
  136. # If target is nil, the elements are collected into a new array.
  137. # If target is an array, the elements are collected by pushing to it.
  138. # If target is a function, the elements are collected by calling the function.
  139. # Returns nil if target is a function, or the resulting array otherwise.
  140. collect: function (target)
  141. let result = []
  142. let push
  143. let type = std.type(target)
  144. if type == "function" then
  145. result = nil
  146. push = target
  147. else
  148. if type == "array" then
  149. result = target
  150. end
  151. push = function (item)
  152. std.push(result, item)
  153. end
  154. end
  155. for item in self.iter() do
  156. push(item)
  157. end
  158. result
  159. end
  160. ]
  161. end
  162. # Construct an iterator for the given array.
  163. # Returns the Iterator instance.
  164. function Array(array)
  165. let ix = 0
  166. # Don't use `std.iter` in order to be able to optimize `nth`.
  167. let it = Iterator(
  168. function ()
  169. if ix >= std.len(array) then
  170. @[ finished: true ]
  171. else
  172. let value = array[ix]
  173. ix = ix + 1
  174. @[ finished: false, value: value ]
  175. end
  176. end
  177. )
  178. # Override nth with a O(1) implementation:
  179. it.nth = function (n)
  180. ix = ix + n
  181. return it.next()
  182. end
  183. return it
  184. end
  185. # Construct an iterator for the given table.
  186. # There is no guarantee of iteration order.
  187. # Returns the Iterator instance.
  188. function Dict(dict)
  189. return Iterator(std.iter(dict))
  190. end
  191. # Construct an empty iterator.
  192. # Returns the Iterator instance.
  193. function Empty()
  194. return Iterator(
  195. function ()
  196. return @[ finished: true ]
  197. end
  198. )
  199. end