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"}}
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)
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.