/vendor/underscore-tcl/underscore.tcl

https://github.com/arthurschreiber/tclspec · TCL · 262 lines · 110 code · 28 blank · 124 comment · 10 complexity · 5204837ef41a725e382dee67ad927ecb MD5 · raw file

  1. # underscore.tcl - Collection of utility methods
  2. #
  3. # Inspired by Underscore.js - http://documentcloud.github.com/underscore/ and
  4. # the Ruby Enumerable module.
  5. #
  6. # This package provides a collection of different utility methods, that try to
  7. # bring functional programming aspects known from other programming languages
  8. # like Ruby or JavaScript to Tcl.
  9. package provide underscore 0.1
  10. namespace eval _ {
  11. # Yields a block of code in a specific stack-level.
  12. #
  13. # This function yields the passed block of code in a seperate stack frame
  14. # (by wrapping it into an ::apply call), but allows easy access to
  15. # surrounding variables using the tcl-native upvar mechanism.
  16. #
  17. # Yielding the code in an anonymous proc prevents the leakage of variable
  18. # definitions, while still giving the block access to surrounding variables
  19. # using upvar.
  20. #
  21. # @example Calculating the first n Fibonnacci numbers
  22. # proc fib_up_to { max block } {
  23. # set i1 [set i2 1]
  24. #
  25. # while { $i1 <= $max } {
  26. # _::yield $block $i1
  27. # set tmp [expr $i + $i2]
  28. # set i1 $i2
  29. # set i2 $tmp
  30. # }
  31. # }
  32. #
  33. # fib_up_to 50 {{n} { puts $n }} ;# prints the fibonnaci sequence up to 50
  34. #
  35. # @example Automatic resource cleanup
  36. # # Guarantees that the file descriptor is closed,
  37. # # even in case of an error being raised while executing the block.
  38. # proc file_open { path mode block } {
  39. # open $fd
  40. #
  41. # # Catch any exceptions that might happen
  42. # set error [catch { _::yield $block $fd } value options]]
  43. #
  44. # catch { close $fd }
  45. #
  46. # if { $error } {
  47. # # if an exception happened, rethrow it
  48. # return {*}$options $value
  49. # } else {
  50. # # Do nothing
  51. # return
  52. # }
  53. # }
  54. #
  55. # file_open "/tmp/test" "w" {{fd} {
  56. # puts $fd "test"
  57. # }}
  58. #
  59. # If you want to return from the stack frame where the method that yields a block
  60. # was called from, you can use 'return -code return'.
  61. #
  62. # @example Returning from the stack frame that called the yielding method.
  63. # proc return_to_calling_frame {} {
  64. # _::each {1 2 3 4} {{item} {
  65. # if { $item == 2 } {
  66. # # Stops the iteration and will return "done" from "return_to_calling_frame"
  67. # return -code return "done"
  68. # }
  69. # }}
  70. # # This return will not be executed
  71. # return "fail"
  72. # }
  73. #
  74. # 'return -code break ?value?' and 'return -code continue ?value?' have special
  75. # meanings inside a block.
  76. #
  77. # @example Passing a block down, by specifying a yield level
  78. # # Reverse each, like _::each, but in reverse
  79. # proc reverse_each { list block } {
  80. # _::each [lreverse $list] {{args} {
  81. # # Include the passed block
  82. # upvar block block
  83. #
  84. # # we have to increase the yield level here, as we want to
  85. # # execute the block on the same stack level as reverse_each
  86. # # was called on
  87. # uplevel 1 [list _::yield $block {*}$args]
  88. # }}
  89. # }
  90. #
  91. # @example Passing a block down by upleveling the call to each.
  92. # # Reverse each, like _::each, but in reverse
  93. # proc reverse_each { list block } {
  94. # uplevel [list _::each [lreverse $list] $block]
  95. # }
  96. #
  97. # @param block_or_proc The block (anonymous function) or proc to be executed
  98. # with the passed arguments. If it's a block, it can be either in the form
  99. # of {args block} or {args block namespace} (see the documentation for ::apply).
  100. # @param args The arguments with which the passed block should be called.
  101. #
  102. # @return Return value of the block.
  103. proc yield { block_or_proc args } {
  104. # Stops type shimmering of $block_or_proc when calling llength directly
  105. # on it, which in turn causes the lambda expression to be recompiled
  106. # on each call to _::yield
  107. set block_dup [concat $block_or_proc]
  108. catch {
  109. if { [llength $block_dup] == 1 } {
  110. uplevel 2 [list $block_or_proc {*}$args]
  111. } else {
  112. uplevel 2 [list apply $block_or_proc {*}$args]
  113. }
  114. } result options
  115. dict incr options -level 1
  116. return -options $options $result
  117. }
  118. # Iterates over the passed list, yielding each element in turn to the
  119. # passed iterator
  120. proc each { list iterator } {
  121. foreach item $list {
  122. yield $iterator $item
  123. }
  124. return $list
  125. }
  126. proc map { list iterator } {
  127. set result [list]
  128. foreach item $list {
  129. set status [catch { yield $iterator $item } return_value options]
  130. switch -exact -- $status {
  131. 0 - 4 {
  132. # 'normal' return and errors
  133. lappend result $return_value
  134. }
  135. 3 {
  136. # 'break' should return immediately
  137. return $return_value
  138. }
  139. default {
  140. # Just pass through everything else
  141. return -options $options $return_value
  142. }
  143. }
  144. }
  145. return $result
  146. }
  147. proc reduce { list iterator memo } {
  148. foreach item $list {
  149. set memo [yield $iterator $memo $item]
  150. }
  151. return $memo
  152. }
  153. # Executes the passed iterator with each element of the passed list.
  154. # Returns true if the passed block never returns a 'falsy' value.
  155. #
  156. # When no explicit iterator is passed, all? will return true
  157. # if none of the list elements is a falsy value.
  158. proc all? { list {iterator {{x} { return $x }}} } {
  159. foreach e $list {
  160. if { [string is false [yield $iterator $e]] } {
  161. return false
  162. }
  163. }
  164. return true
  165. }
  166. interp alias {} ::_::every? {} ::_::all?
  167. namespace export all? every?
  168. # Executes the passed iterator with each element of the passed list.
  169. # Returns true if the passed block returns at least one value that
  170. # is not 'falsy'.
  171. #
  172. # When no explicit iterator is passed, any? will return true
  173. # if at least one of the list elements is not a falsy value.
  174. proc any? { list {iterator {{x} { return $x }}} } {
  175. foreach e $list {
  176. if { [expr { ![string is false [yield $iterator $e]] }] } {
  177. return true
  178. }
  179. }
  180. return false
  181. }
  182. interp alias {} ::_::some? {} ::_::any?
  183. namespace export some? any?
  184. # Returns the first n elements from the passed list.
  185. proc first { list {n 1}} {
  186. lrange $list 0 $n-1
  187. }
  188. # Returns all elements from the passed list excluding the last n.
  189. proc initial { list {n 1}} {
  190. lrange $list 0 end-$n
  191. }
  192. proc index_of { list value {is_sorted false} } {
  193. if { ![string is false $is_sorted] } {
  194. lsearch -sorted -exact $list $value
  195. } else {
  196. lsearch -exact $list $value
  197. }
  198. }
  199. # Returns a sorted copy of list. Sorting is based on the return
  200. # values of the execution of the iterator for each item.
  201. proc sort_by { list iterator } {
  202. set list_to_sort [_::map $list {{item} {
  203. upvar iterator iterator
  204. list [uplevel [list yield $iterator $item] $item
  205. }}]
  206. set sorted_list [lsort $list_to_sort]
  207. _::map $sorted_list {{pair} {
  208. lindex $pair 1
  209. }}
  210. }
  211. # Executes the passed block n times.
  212. proc times { n iterator } {
  213. for {set i 0} {$i < $n} {incr i} {
  214. yield $iterator $i
  215. }
  216. }
  217. proc take_while { list iterator } {
  218. set result [list]
  219. foreach item $list {
  220. if { ![yield $iterator $item] } {
  221. break
  222. }
  223. lappend result $item
  224. }
  225. return $item
  226. }
  227. proc group_by { list iterator } {
  228. set result [dict create]
  229. foreach item $list {
  230. dict lappend result [yield $iterator $item] $item
  231. }
  232. return $result
  233. }
  234. }