2255eeb67076

Clean up `when-found`, add `if-found` and `gathering`
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Mon, 22 Aug 2016 20:35:31 +0000
parents 5b5e1b9adbbd
children 768bd70efb57
branches/tags (none)
files losh.lisp package.lisp

Changes

--- a/losh.lisp	Mon Aug 22 19:54:09 2016 +0000
+++ b/losh.lisp	Mon Aug 22 20:35:31 2016 +0000
@@ -249,6 +249,106 @@
                 ,@body))
       (recur ,@(mapcar #'extract-val bindings)))))
 
+(defmacro when-found (var lookup-expr &body body)
+  "Perform `body` with `var` to the results of `lookup-expr`, when valid.
+
+  `lookup-expr` should be an expression that returns two values, the first being
+  the result (which will be bound to `var`) and the second indicating whether
+  the lookup was successful.  The standard `gethash` is an example of a function
+  that behaves like this.
+
+  If the lookup was successful, `body` will be executed and its value returned.
+
+  Example:
+
+    (multiple-value-bind (val found) (gethash :foo hash)
+      (when found
+        body))
+
+    ; becomes
+
+    (when-found val (gethash :foo hash)
+      body)
+
+  "
+  (with-gensyms (found)
+    `(multiple-value-bind (,var ,found) ,lookup-expr
+       ;; We could preserve and pass along the value of found as a secondary
+       ;; return value from the form, but that would kill potential last-call
+       ;; optimization (and the ability to return multiple values from `body`).
+       (when ,found
+         ,@body))))
+
+(defmacro if-found (var lookup-expr then else)
+  "Perform `body` or `else` depending on the results of `lookup-expr`.
+
+  `lookup-expr` should be an expression that returns two values, the first being
+  the result and the second indicating whether the lookup was successful.  The
+  standard `gethash` is an example of a function that behaves like this.
+
+  If the lookup was successful, `then` will be executed with `var` bound to the
+  result, and its value returned.
+
+  Otherwise `else` will be executed, without any extra bindings.
+
+  Example:
+
+    (multiple-value-bind (val found) (gethash :foo hash)
+      (if found
+        'yes
+        'no))
+
+    ; becomes
+
+    (if-found val (gethash :foo hash)
+      'yes
+      'no)
+
+  "
+  (with-gensyms (found result)
+    `(multiple-value-bind (,result ,found) ,lookup-expr
+      (if ,found
+        (let ((,var ,result))
+          ,then)
+        ,else))))
+
+(defmacro gathering (&body body)
+  "Run `body` to gather some things and return them.
+
+  `body` will be executed with the symbol `gather` bound to a function of one
+  argument.  Once `body` has finished, a list of everything `gather` was called
+  on will be returned.
+
+  It's handy for pulling results out of code that executes procedurally and
+  doesn't return anything, like `maphash` or Alexandria's `map-permutations`.
+
+  The `gather` function can be passed to other functions, but should not be
+  retained once the `gathering` form has returned (it would be useless to do so
+  anyway).
+
+  Examples:
+
+    (gathering
+      (dotimes (i 5)
+        (gather i))
+    =>
+    (0 1 2 3 4)
+
+    (gathering
+      (mapc #'gather '(1 2 3))
+      (mapc #'gather '(a b)))
+    =>
+    (1 2 3 a b)
+
+  "
+  (with-gensyms (result)
+    `(let ((,result (make-queue)))
+      (flet ((gather (item)
+               (enqueue item ,result)))
+        (declare (dynamic-extent #'gather))
+        ,@body)
+      (queue-contents ,result))))
+
 
 ;;;; Mutation
 (defun build-zap (place expr env)
--- a/package.lisp	Mon Aug 22 19:54:09 2016 +0000
+++ b/package.lisp	Mon Aug 22 20:35:31 2016 +0000
@@ -67,7 +67,11 @@
   (:documentation "Utilities for managing control flow.")
   (:export
     #:recursively
-    #:recur))
+    #:recur
+    #:when-found
+    #:if-found
+    #:gathering
+    #:gather))
 
 (defsubpackage #:losh.mutation
   (:documentation "Utilities for mutating places in-place.")