summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md3
-rw-r--r--lib/pure/sugar.nim26
-rw-r--r--tests/closure/tcapture.nim12
3 files changed, 41 insertions, 0 deletions
diff --git a/changelog.md b/changelog.md
index 18eece00c..429d0bfe0 100644
--- a/changelog.md
+++ b/changelog.md
@@ -42,6 +42,9 @@
   `sorted` does.
 - Added `sugar.collect` that does comprehension for seq/set/table collections.
 
+- Added `sugar.capture` for capturing some local loop variables when creating a closure.
+  This is an enhanced version of `closureScope`.
+
 ## Library changes
 
 - `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations`
diff --git a/lib/pure/sugar.nim b/lib/pure/sugar.nim
index 73ac44d04..c13695ebf 100644
--- a/lib/pure/sugar.nim
+++ b/lib/pure/sugar.nim
@@ -10,6 +10,8 @@
 ## This module implements nice syntactic sugar based on Nim's
 ## macro system.
 
+include system/inclrtl
+
 import macros
 
 proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
@@ -180,6 +182,30 @@ macro distinctBase*(T: typedesc): untyped =
     typeSym = getTypeImpl(typeSym)[0]
   typeSym.freshIdentNodes
 
+macro capture*(locals: openArray[typed], body: untyped): untyped {.since: (1, 1).} =
+  ## Useful when creating a closure in a loop to capture some local loop variables
+  ## by their current iteration values. Example:
+  ##
+  ## .. code-block:: Nim
+  ##   import strformat, sequtils, sugar
+  ##   var myClosure : proc()
+  ##   for i in 5..7:
+  ##     for j in 7..9:
+  ##       if i * j == 42:
+  ##         capture [i, j]:
+  ##           myClosure = proc () = echo fmt"{i} * {j} = 42"
+  ##   myClosure() # output: 6 * 7 == 42
+  ##   let m = @[proc (s: string): string = "to " & s, proc (s: string): string = "not to " & s]
+  ##   var l = m.mapIt(capture([it], proc (s: string): string = it(s)))
+  ##   let r = l.mapIt(it("be"))
+  ##   echo r[0] & ", or " & r[1] # output: to be, or not to be
+  var params = @[newIdentNode("auto")]
+  for arg in locals:
+    params.add(newIdentDefs(ident(arg.strVal), freshIdentNodes getTypeImpl arg))
+  result = newNimNode(nnkCall)
+  result.add(newProc(newEmptyNode(), params, body, nnkProcDef))
+  for arg in locals: result.add(arg)
+
 when (NimMajor, NimMinor) >= (1, 1):
   macro outplace*[T](arg: T, call: untyped; inplaceArgPosition: static[int] = 1): T =
     ## Turns an `in-place`:idx: algorithm into one that works on
diff --git a/tests/closure/tcapture.nim b/tests/closure/tcapture.nim
new file mode 100644
index 000000000..304a76285
--- /dev/null
+++ b/tests/closure/tcapture.nim
@@ -0,0 +1,12 @@
+discard """
+  output: '''
+to be, or not to be'''
+  joinable: false
+"""
+
+import sequtils, sugar
+
+let m = @[proc (s: string): string = "to " & s, proc (s: string): string = "not to " & s]
+var l = m.mapIt(capture([it], proc (s: string): string = it(s)))
+let r = l.mapIt(it("be"))
+echo r[0] & ", or " & r[1]
\ No newline at end of file