claro.projection

Powerful tree projection functions.

These will allow you to convert an infinite tree of Resolvable values to a finite form, performing transformations, injections and selection along the way.

See Projections and Advanced Projections for a detailed discussion and examples.

alias

(alias alias-key key)

This function can be used within maps to rename/copy an existing key for further projection, e.g.:

{:id                          projection/leaf
 (alias :friend-id :friend)   {:id projection/leaf}
 (alias :friend-name :friend) {:name projection/leaf}}

This would result in a map of the following shape:

{:id          1
 :friend-id   {:id 2}
 :friend-name {:name "Dr. Watson"}}

apply

(apply value template)

Project the given value using the given template.

bind

(bind bind-fn template)

A two-step projection, using a partial projection result to generate the eventual, full projection. Example:

(projection/bind
  (fn [{:keys [id]}]
    {:children
     [{:id        projection/leaf
       :parent-id (projection/value id)}]})
  {:id projection/leaf})

This will use {:id projection/leaf} to project the current value and pass the result to the given function – which is then expected to return the “actual” projection template for the current value.

This projection is useful to “remember” values in the tree.

case

added in 0.2.1

(case class template & more)

Dispatch on the class of a value (after resolution), applying the corresponding template.

(-> (->Animals)
    (projection/apply
      [(projection/case
         Dolphin {:name projection/leaf, :intelligence projection/leaf}
         Zebra   {:name projection/leaf, :number-of-stripes projection/leaf}
         :else   {:name projection/leaf})])
     (engine/run!!))
;; => [{:name "Tiger"}
;;     {:name "Dolphin", :intelligence 80}
;;     {:name "Zebra", :number-of-stripes 20}]

By specifiying a vector of classes, e.g. [Tiger Zebra] you can apply the same projection to multiple kinds of resolvables.

case-resolvable

added in 0.2.1

(case-resolvable class template & more)

Dispatch on the class of a Resolvable, applying the corresponding template.

(-> (->Animals)
    (projection/apply
      [(projection/case-resolvable
         Dolphin {:name projection/leaf, :intelligence projection/leaf}
         Zebra   {:name projection/leaf, :number-of-stripes projection/leaf}
         :else   {:name projection/leaf})])
     (engine/run!!))
;; => [{:name "Tiger"}
;;     {:name "Dolphin", :intelligence 80}
;;     {:name "Zebra", :number-of-stripes 20}]

By specifiying a vector of classes, e.g. [Tiger Zebra] you can apply the same projection to multiple kinds of resolvables.

conditional

(conditional partial-template condition template & more)

Apply the first projection whose predicate matches the value resulting from projecting partial-template.

(projection/conditional
  {:type projection/leaf}
  (comp #{:animal} :type) {:left-paw projection/leaf}
  (comp #{:human} :type)  {:left-hand projection/leaf})

:else can be given to denote the default case:

(projection/conditional
  {:type projection/leaf}
  (comp #{:animal} :type) {:left-paw projection/leaf}
  :else                   {:left-hand projection/leaf})

Note that, sometimes, you can express this just as well using a bind or let projection:

(projection/let [{:keys [type]} {:type projection/leaf}]
  (case type
    :animal {:left-paw projection/leaf}
    :human  {:left-hand projection/leaf}))

conditional-union

(conditional-union partial-template condition template & more)

Apply and merge all projections whose predicates match the value resulting from projecting partial-template.

(projection/conditional-union
  {:type          projection/leaf
   :has-children? projection/leaf}
  (comp #{:animal} :type) {:left-paw projection/leaf}
  (comp #{:human} :type)  {:left-hand projection/leaf}
  :has-children?          {:children [{:name projection/leaf}]})

The matching projections have to produce maps with disjunct sets of keys.

default

(default template default-value)

Apply the given projection to any non-nil value or the given default.

(projection/default {:name projection/leaf} unknown-person)

Note that this will cause the default value to be injected into a result map even if the respective key is missing.

extract

(extract template k)(extract k)

Extract a subtree/leaf located under the given key.

(-> (->Person 1)
    (projection/apply (extract :name))
    (engine/run!!))
;; => "Sherlock"

For non-leaf values, a template can be given that will be applied before extraction.

extract-in

added in 0.2.13

(extract-in template ks)(extract-in ks)

Extract a subtree/leaf located under the given path.

(-> {:sherlock (->Person 1)}
    (projection/apply (extract-in [:sherlock :name]))
    (engine/run!!))
;; => "Sherlock"

For non-leaf values, a template can be given that will be applied before extraction.

finite-value

added in 0.2.3

(finite-value value)

Like value but will not apply any further projection to the given value. This means that you can use this to inject arbitrary (but finite!) subtrees into your data.

(If you still give a potentially infinite resolvable, you’ll hit claro’s resolution limits.)

juxt

added in 0.2.13

(juxt & templates)

Creates a vector with results of projecting the current value with each of the given templates (maintaining order):

(projection/juxt
  (projection/extract :id)
  (projection/extract :name))

This, for example, will convert a map with :id and :name keys to a tuple.

juxt*

added in 0.2.13

(juxt* templates)

Creates a vector with results of projecting the current value with each of the given templates (maintaining order):

(projection/juxt*
  [(projection/extract :id)
   (projection/extract :name)])

This, for example, will convert a map with :id and :name keys to a tuple.

leaf

Projection template for leaf values (equivalent to nil but preferable since more explicit).

let

macro

(let bindings & body)

Syntactic sugar for the bind projection.

(projection/let [{:keys [id]} {:id projection/leaf}]
  {:children [{:id        projection/leaf
               :parent-id (projection/value id)}]})

is equal to:

(projection/bind
  (fn [{:keys [id]}]
    {:children [{:id        projection/leaf
                 :parent-id (projection/value id)}]})
  {:id projection/leaf})

Multiple binding templates are supported (although you’ll usually want to only use one):

(projection/let [{:keys [id]}   {:id projection/leaf}
                 {:keys [name]} {:name projection/leaf}]
  ...)

levels

(levels n)

Generate Projection template representing the first n levels of a value. Leafs up to the given level will be maintained.

E.g. for the following value:

{:a 0
 :b {:c 1}
 :d {:e [{:f 2}]}}

Result for n == 1:

{:a 0, :b {}, :d {}}

Result for n == 2;

{:a 0, :b {:c 1}, :d {:e []}}

For n >= 3, the full value will be returned.

maybe

(maybe template)

Apply projection template if the value is not nil, otherwise just keep the nil.

(projection/maybe {:name projection/leaf})

Note that this will cause a nil to be injected into a result map even if the respective key is missing.

maybe-parameters

added in 0.2.18

(maybe-parameters params rest-template)

Like parameters but will ignore nil values.

merge

added in 0.2.5

(merge & templates)

Like union but will merge overlapping keys like clojure.core/merge.

merge*

added in 0.2.5

(merge* templates)

Like union* but will merge overlapping keys like clojure.core/merge.

parameters

(parameters params rest-template)

Set some fields within a Resolvable record before resolution. Note that:

  • You can only set fields that currently have the value nil (i.e. no overriding of already set fields).
  • You can only set fields the record already contains (i.e. records have to explicitly contain even optional parameter fields).

These restrictions are intended to make resolution more predictable. Note that you can always use prepare directly to perform arbitrary injections.

prepare

(prepare f rest-template)

A projection applying a transformation function to a value (before resolution!), with rest-template being used to further project the resulting value.

remove-nil-elements

(remove-nil-elements & [template])

A projection to remove all nil elements from a seq, before applying the given template to it. If no template is given, the seq without nil values will be returned directly (and needs to have another projection applied if infinite subtrees are possible).

sort-by

added in 0.2.19

(sort-by sort-template)(sort-by sort-template output-template)

A projection sorting the sequence that’s currently being resolved. sort-template is applied to each element of the sequence to generate a value to sort by, while `output-template´ is used to further project the resulting sorted sequence.

(-> [{:index 3, :value 'third}
     {:index 1, :value 'first}
     {:index 2, :value 'second}]
    (projection/apply
      (projection/sort-by
        (projection/extract :index)
        [{:value projection/leaf}]))
    (engine/run!!))
;; => [{:value 'first}, {:value 'second}, {:value 'third}]

If no output-template is given, the resulting tree may not be infinite (or a further projection has to be applied externally).

transform

(transform f input-template)(transform f input-template output-template)

A projection applying a transformation function to a fully resolved value. input-template is used to project the initial value, output-template will be used to further project the resulting value.

For example, to extract the :name key from a seq of maps:

(-> [{:name "Zebra"}, {:name "Tiger"}]
    (projection/apply
      [(projection/transform :name {:name projection/leaf} projection/leaf)])
    (engine/run!!))
;; => ["Zebra" "Tiger"]

If no output-template is given, you have to apply projections to potentially infinite subtrees within the transformation function.

If the transformation won’t introduce any new resolvables, transform-finite should be preferred due to its better performance with deeply nested trees.

transform-finite

added in 0.2.15

(transform-finite f input-template)

Like transform but assuming that f produces a finite value, i.e. one without any further resolvables.

For transformations on deeply nested structures this will perform better than transform since it avoids re-inspection of the tree.

union

(union & templates)

Syntactic sugar for union* allowing for projections-to-merge to be given as single parameters:

(projection/union
  {:id projection/leaf}
  (projection/case-resolvable
    Zebra   {:number-of-stripes projection/leaf}
    Dolphin {:intelligence projection/leaf}
    :else   {}))

Note that the the templates have to produce maps with disjunct sets of keys.

union*

(union* templates)

Apply all projection templates to the value, merging them together into a final value.

(projection/union
  [{:id projection/leaf}
   (projection/case-resolvable
     Zebra   {:number-of-stripes projection/leaf}
     Dolphin {:intelligence projection/leaf}
     :else   {})])

Note that the the templates have to produce maps with disjunct sets of keys.

unsafe

Projection template for any kind of value. If this is used in places where infinite subtrees can occur, engine executions will run forever or exceed the maximum resolution cost.

value

(value value & [template])

A projection that replaces any value it encounters with the given one. template will be used for further projection, if given, otherwise leaf is used.

Note that this projection can be used to inject values into a map, i.e. the result of

{:id       projection/leaf
 :name     projection/leaf
 :visible? (projection/value true)}

will always contain a key :visible? with value true – even if the original map had no such key.