fn-in.core

This namespace provides functions to:

  • inspect an element (get)
  • change an element (assoc)
  • apply a function to an element (update)
  • remove an element (dissoc)

from any of Clojure’s collection types (plus non-terminating sequences), similarly to their clojure.core namesakes. The ...-in variants operate on any heterogeneous, arbitrarily-nested data structure.

  • Elements contained in vectors, lists, and other sequences are addressed by zero-indexed integers.
  • Elements contained in maps are addressed by key, which may be any valid Clojure value, including a composite.
  • Elements contained in sets are addressed by the element’s value, which may be a composite.

Conventions:

  • c collection
  • i index/key
  • x value
  • f function
  • arg1, arg2, arg3 optional args to multi-arity function f.

assoc*

multimethod

(assoc* c i val)

Returns a new collection c with the key/index i associated with the supplied value val. Similar to clojure.core/assoc , but operates on all Clojure collections. Indexes beyond the end of a sequence are padded with nil.

Note: Because set members are addressed by their value, the assoc*-ed value may match a pre-existing set member, and the returned set may have one fewer members.

Examples:

(assoc* [11 22 33] 1 99) ;; => [11 99 33]
(assoc* {:a 11 :b 22} :b 99) ;; => {:a 11, :b 99}
(assoc* (list 11 22 33) 1 99) ;; => (11 99 33)
(assoc* #{11 22 33} 22 99) ;; => #{99 33 11}
(assoc* (range 3) 1 99) ;; => (0 99 2)
(assoc* (take 6 (iterate dec 10)) 3 99) ;; => (10 9 8 99 6 5)

;; associating an existing set member reduces the size of the set
(assoc* #{11 22 33} 22 33) ;; => #{33 11}

;; associating a value into a non-terminating sequence
(take 5 (assoc* (repeat 3) 2 99)) ;; => (3 3 99 3 3)

;; associating a value beyond a sequence's bounds causes nil-padding
(assoc* [11 22 33] 5 99) ;; => (11 22 33 nil nil 99)

assoc-in*

(assoc-in* c [i & i_s] x)

Returns collection c with a new value x associated at path vector of i elements. Similar to clojure.core/assoc-in , but operates on any heterogeneous, arbitrarily-nested collections. Supplying an empty path throws an exception. Associating beyond the end of sequence results in nil-padding.

Examples:

(assoc-in* [11 22 33 [44 55 [66 77]]] [3 2 1] :foo) ;; => [11 22 33 [44 55 [66 :foo]]]
(assoc-in* {:a {:b {:c 42}}} [:a :b :c] 99) ;; => {:a {:b {:c 99}}}
(assoc-in* (list 11 22 [33 44 (list 55)]) [2 1] :foo) ;; => (11 22 [33 :foo (55)])
(assoc-in* #{11 [22 #{33}]} [[22 #{33}] 1 33] :hello) ;; => #{11 [22 #{:hello}]}

;; heterogeneous, nested collections
(assoc-in* [11 {:a [22 {:b 33}]}] [1 :a 1 :b] 42) ;; => [11 {:a [22 {:b 42}]}]
(assoc-in* {'foo (list 11 {22/7 'baz})} ['foo 1 22/7] :new-val) ;; => {foo (11 {22/7 :new-val})}

;; associating beyond nested sequence's bounds causes nil-padding
(assoc-in* [11 22 [33 44]] [2 3] 99) ;; => [11 22 (33 44 nil 99)]

;; associating a non-existent key-val in a map merely expands the map
(assoc-in* {:a 11 :b {:c 22}} [:b :d] 99) ;; => {:a 11, :b {:c 22, :d 99}}

dissoc*

multimethod

(dissoc* c i)

Returns a new collection c with the value located at key/index i removed. Similar to clojure.core/dissoc , but operates on all Clojure collections. i must be within the bounds of a a sequence. If element at i does not exist in a map or set, the new returned collection is identical. Similarly, dissoc-ing an element from a clojure.lang.Repeat returns an indistinguishable sequence.

Examples:

(dissoc* [11 22 33] 1) ;; => [11 33]
(dissoc* {:a 11 :b 22} :a) ;; => {:b 22}
(dissoc* (list 11 22 33) 1) ;; => (11 33)
(dissoc* #{11 22 33} 22) ;; => #{33 11}
(dissoc* (take 3 (cycle [:a :b :c])) 1) ;; => (:a :c)

;; non-existent entity
(dissoc* #{11 22 33} 99) ;; => #{33 22 11}
(dissoc* {:a 11 :b 22} :c) ;; => {:a 11, :b 22}

dissoc-in*

(dissoc-in* c [i & i_s])

Remove element located at i from an arbitrarily-nested collection c. Any containing collections are preserved if i addresses a single scalar. If i addresses a nested collection, all children are removed.

Examples:

(dissoc-in* [11 [22 [33 44]]] [1 1 0]) ;; => [11 [22 [44]]]
(dissoc-in* {:a 11 :b {:c 22 :d 33}} [:b :c]) ;; => {:a 11, :b {:d 33}}
(dissoc-in* (list 11 (list 22 33)) [1 0]) ;; => (11 (33))
(dissoc-in* #{11 [22 33]} [[22 33] 0]) ;; => #{[33] 11}
(dissoc-in* [11 (range 4)] [1 2]) ;; => [11 (0 1 3)]

;; heterogeneous, nested collections
(dissoc-in* [11 {:a (list 22 [33 44])}] [1 :a 1 0]) ;; => [11 {:a (22 [44])}]
(dissoc-in* {:a 11 :b [22 #{33 44}]} [:b 1 33]) ;; => {:a 11, :b [22 #{44}]}

;; dissociating an element that is itself a nested collection; containers are dissociated
(dissoc-in* [11 [22]] [1]) ;; => [11]
(dissoc-in* {:a {:b {:c 99}}} [:a :b]) ;; => {:a {}}

;; dissociating a scalar element; containers are preserved
(dissoc-in* [11 [22 [33]]] [1 1 0]) ;; => [11 [22 []]]
(dissoc-in* [11 {:a 22}] [1 :a]) ;; => [11 {}]
(dissoc-in* [11 22 #{33}] [2 33]) ;; => [1 2 #{}]

See also:

get*

multimethod

(get* c i)

Inspect the value at location i within a collection c. Similar to clojure.core/get . Returns nil if not found.

Note: get* does not offer a not-found arity.

Examples:

(get* [11 22 33 44 55] 2) ;; => 33
(get* {:x 11 :y 22 :z 33} :y) ;; => 22
(get* (list 11 22 33 44 55) 2) ;; => 33
(get* #{11 22 33} 22) ;; => 22
(get* (range 99) 3) ;; => 3
(get* (cycle [11 22 33]) 5) ;; => 33

;; non-keyword key
(get* {[11 22 33] 'val-1 99 'val-2} 99) ;; => val-2

get-in*

(get-in* c path)

Inspects the value in heterogeneous, arbitrarily-nested collection c at path, a vector of indexes/keys. This version of clojure.core/get-in operates on all Clojure collections. An empty path vector returns the original collection c. Performance is not optimized, so it might steal your lunch money.

Examples:

(get-in* [11 22 33 [44 [55]]] [3 1 0]) ;; => 55
(get-in* {:a 11 :b {:c 22 :d {:e 33}}} [:b :d :e]) ;; => 33
(get-in* (list 11 22 [33 (list 44 (list 55))]) [2 1 1 0]) ;; => 55
(get-in* #{11 [22 [33]]} [[22 [33]] 1 0]) ;; => 33

;; empty path addresses root collection
(get-in* [11 22 33] []) ;; => [11 22 33]

;; heterogeneous, nested collections; return may be a collection
(get-in* {:a [11 22 {:b [33 [44] 55 [66]]}]} [:a 2 :b 3]) ;; => [66]

;; address of a nested set; compare to next example
(get-in* [11 {:a [22 #{33}]}] [1 :a 1   ]) ;; => #{33}

;; address of an element contained within a nested set; compare to previous example
(get-in* [11 {:a [22 #{33}]}] [1 :a 1 33]) ;; => 33

;; non-terminating sequence
(get-in* (repeat [11 22 33]) [3 1]) ;; => 22

;; nested non-terminating sequences
(get-in* (repeat (cycle [:a :b :c])) [99 5]) ;; => :c

update*

(update* c i f)(update* c i f arg1)(update* c i f arg1 arg2)(update* c i f arg1 arg2 arg3)(update* c i f arg1 arg2 arg3 & more)

Returns a new collection c with function f applied to the value at location i. If the location doesn’t exist, nil is passed to f. Additional arguments args may be supplied trailing f. Similar to clojure.core/update , but operates on all Clojure collections.

Note: Because set members are addressed by their value, the update*-ed value may match a pre-existing set member, and the returned set may have one fewer members.

Examples:

(update* [10 20 30] 1 dec) ;; => [10 19 30]
(update* {:a 10} :a dec) ;; => {:a 9}
(update* (list 10 20 30) 1 dec) ;; => (10 19 30)
(update* #{10 20 30} 20 dec) ;; => #{19 30 10}

;; function handles nil if no value exists
(update* [11 22 33] 4 (constantly :updated-val)) ;; => (11 22 33 nil :updated-val)

;; additional args
(update* {:a 99} :a #(/ %1 %2) 9) ;; => {:a 11}

;; update absorbs existing set member resulting in a smaller set
(update* #{32 33} 33 dec) ;; => #{32}

update-in*

(update-in* m ks f & args)

Returns a new collection c with the value at path vector ks updated by applying function f to the previous value. Similar to clojure.core/update-in , but operates on any heterogeneous, arbitrarily-nested collection. Additional arguments args may be supplied trailing f. If location ks does not exist, f must handle nil.

Note: Updating a set member to another previously-existing set member will decrease the size of the set.

Examples:

(update-in* [11 [22 [33]]] [1 1 0] inc) ;; => [11 [22 [34]]]
(update-in* {:a {:b {:c 99}}} [:a :b :c] inc) ;; => {:a {:b {:c 100}}}
(update-in* (list 11 [22 (list 33)]) [1 1 0] inc) ;; => (11 [22 (34)])
(update-in* #{11 [22 #{33}]} [[22 #{33}] 1 33] inc) ;; => #{[22 #{34}] 11}

;; heterogeneous nested collections
(update-in* [11 {:a 22 :b (list 33 44)}] [1 :b 1] inc) ;; => [11 {:a 22, :b (33 45)}]
(update-in* {:a [11 #{22}]} [:a 1 22] #(* % 2)) ;; => {:a [11 #{44}]}

;; beyond end of sequence
(+ nil) ;; => nil
(update-in* [11 22 33] [3] +) ;; => (11 22 33 nil)

;; non-existent key-val
(not nil) ;; => true
(update-in* {:a {:b 11}} [:a :c] not) ;; => {:a {:b 11, :c true}}

;; updating a set member to an existing value
(update-in* #{11 12} [11] inc) ;; => #{12}

;; additional args
(update-in* [11 [22 [99]]] [1 1 0] #(/ %1 %2) 3) ;; => [11 [22 [33]]]