Bob Kuhar
Bob Kuhar

Reputation: 11130

What does :or mean in Clojure destructuring?

The source for faraday's scan function (https://github.com/ptaoussanis/faraday/blob/master/src/taoensso/faraday.clj#L1197) has a destructuring form that I am struggling to understand...

(source far/scan)
(defn scan
  "..."
  [client-opts table
   & [{:keys [attr-conds last-prim-kvs span-reqs return limit total-segments
              filter-expr
              segment return-cc?] :as opts
       :or   {span-reqs {:max 5}}}]]
...)

What does that :or {span-reqs {:max 5}} do?

Upvotes: 6

Views: 2748

Answers (3)

Leon Grapenthin
Leon Grapenthin

Reputation: 9266

:or {span-reqs {:max 5}} specifies that iff opts does not have the key :span-reqs, span-reqs will be bound to the map {:max 5}.

Note that span-reqs does not directly refer to the key :span-reqs - this would also be possible:

(defn scan
  [client-opts table
   & [{:keys [attr-conds last-prim-kvs return limit total-segments
              filter-expr
              segment return-cc?] :as opts
       foo :span-reqs
       :or {foo {:max 5}}}]])

Observe that in the :or map you can generally provide default expressions for symbols you have bound in the destructoring form.

N. B.: Be careful with putting runtime expressions in :or. As of Clojure 1.9-alpha14 they will still be evaluated regardless of whether they are needed (see http://dev.clojure.org/jira/browse/CLJ-1676).

Upvotes: 6

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91554

To put this in context I'll wrap it in an example function:

user> (defn foo [client-opts
                 table
                 & [{:keys [attr-conds
                            last-prim-kvs
                            span-reqs
                            return
                            limit
                            total-segments
                            filter-expr
                            segment return-cc?]
                     :as opts
                     :or {span-reqs {:max 5}}}]]
        (println "client-opts")
        (clojure.pprint/pprint client-opts)
        (println "table")
        (clojure.pprint/pprint table)
        (println "opts")
        (clojure.pprint/pprint opts)
        (println "the symbol attr-conds is bound to:" attr-conds)
        (println "the symbol limit is bound to:" limit)
        (println "the symbol span-reqs is bound to:" span-reqs))
#'user/foo
user> (foo :I'm-a-client-opt
           :I'm-a-table           
           {:attr-conds 1
            :span-reqs [1 2 3]
            :limit 47}
           {:attr-conds :i-will-be-ignored}
           {:limit :i-will-also-be-ignored})
client-opts
:I'm-a-client-opt
table
:I'm-a-table
opts
{:attr-conds 1, :span-reqs [1 2 3], :limit 47}
the symbol attr-conds is bound to: 1
the symbol limit is bound to: 47
the symbol span-reqs is bound to: [1 2 3]
nil

Now we see that it binds some names to parts of the first map in a list, so lets take that destructing expresion apart:

& ;; this symbol collects all the rest of the arguments into a list
[ ;; this one then does list destructuring on the list indicated by the &
 {:keys [attr-conds     ;; This block destructures the first (and only the first) entry in the vector.
         last-prim-kvs  ;; Because this is a map it will do map destructuring and 
         span-reqs      ;; bind (assigns) values to symbols in this list
         return
         limit
         total-segments
         filter-expr
         segment return-cc?]
  :as opts              ;; the entire map in the first position in the list created by the & will be bound to the name opts
  :or {span-reqs {:max 5}}}] ;; if span-reqs is not in the map at all, 
                             ;; then the map {:max 5} will be bound to the name 
                             ;; span-reqs instead

So the keyword :or in that nested destructuring expression assigns a default value to a symbol, if the third argument to the function is a map that does not provide a value for the keyword :span-reqs. It does nothing if the keyword :span-reqs is present and contains other keys without providing a value for :max. It does not merge the default values into the map in this context, it just provides a value if it's completely missing:

here is is with the value not specified at all:

user> (foo :I'm-a-client-opt
           :I'm-a-table           
           {:attr-conds 1
            ;; :span-reqs [1 2 3] ;; removed this one
            :limit 47}
           {:attr-conds :i-will-be-ignored}
           {:limit :i-will-also-be-ignored})
client-opts
:I'm-a-client-opt
table
:I'm-a-table
opts
{:attr-conds 1, :limit 47}
the symbol attr-conds is bound to: 1
the symbol limit is bound to: 47
the symbol span-reqs is bound to: {:max 5}
nil

and again with a value specified, where that value does not include a :max

user> (foo :I'm-a-client-opt
           :I'm-a-table           
           {:attr-conds 1
            :span-reqs {:min -7} ;; present but contains something else
            :limit 47}
           {:attr-conds :i-will-be-ignored}
           {:limit :i-will-also-be-ignored})
client-opts
:I'm-a-client-opt
table
:I'm-a-table
opts
{:attr-conds 1, :span-reqs {:min -7}, :limit 47}
the symbol attr-conds is bound to: 1
the symbol limit is bound to: 47
the symbol span-reqs is bound to: {:min -7}
nil

Upvotes: 2

Alan Thompson
Alan Thompson

Reputation: 29958

It is the default value. For more details please see http://clojure.org/guides/destructuring

(def my-map {:a "A" :b "B" :c 3 :d 4})
(let [{a :a, x :x, :or {x "Not found!"}, :as all} my-map]
  (println "I got" a "from" all)
  (println "Where is x?" x))
;= I got A from {:a "A" :b "B" :c 3 :d 4}
;= Where is x? Not found!

using :keys we get

(let [{:keys [a x] :or {x "Not found!"}, :as all} my-map]
  (println "I got" a "from" all)
  (println "Where is x?" x))

with the same result

Upvotes: 10

Related Questions