plumbing.fnk.impl

Core utilities for parsing our 'fnk'-style binding syntax.
Documented and tested through the actual 'letk','fnk', and 'defnk'
macros in plumbing.core.

The core entry points into this namespace are 'letk*' and 'fnk*',
which parse the new binding syntax and generate fnk bodies,
respectively.

For efficiency, two different methods of generating fnk bodies are
used.  If the fnk takes a fixed set of arguments (i.e., no & or
:as), then a 'positional' version of the fnk that is called like an
ordinary Clojure fn (e.g., (f a b) rather than (f {:a a :b b}) is
generated as an implementation detail, and stored in metadata of
the actual keyword fnk (which is just a thin wrapper around the
positional version).  If '& or :as are used, no such positional
function is generated.

The advantage of these 'positional' functions is that they can be
accessed using 'efficient-call-forms' or 'positional-fn' to call
the fnk without incurring the overhead of producing and then
destructuring a top-level map.  See plumbing.graph.positional for
an example use.

+none+

A sentinel value used to indicate a non-provided optional value in a positional form.

efficient-call-forms

(efficient-call-forms fnk arg-form-map)
Get [f arg-forms] that can be used to call a fnk most efficiently, using the
positional version if available, or otherwise the raw fnk.  arg-form-map
is a map from keywords representing arguments to fnk to *forms* that evaluate
to the corresponding arguments.

The basic idea is that (eval (cons f arg-forms)) would yield code for an
efficient call to fnk.  However, this form is not returned directly, because
in most cases the literal function f cannot be directly evaluated due to
a quirk in Clojure -- e.g., try (eval `(~(let [x 1] (fn [y] (+ y x))) 2)).

For examples of how this is used, see 'positional-fn' below, or the positional
compilation in plumbing.graph.positional.

ensure-schema-metadata

(ensure-schema-metadata env x)

fnk-form

(fnk-form env name? bind body)
Take an optional name, binding form, and body for a fnk, and make an
IFn/PFnk for these arguments.

For efficiency, two different methods of generating fnk bodies are
used.  If the fnk takes a fixed set of arguments (i.e., no & or
:as), then a 'positional' version of the fnk that is called like an
ordinary Clojure fn (e.g., (f a b) rather than (f {:a a :b b}) is
generated as an implementation detail, and stored in metadata of
the actual keyword fnk (which is just a thin wrapper around the
positional version).  If '& or :as are used, no such positional
function is generated.

letk-arg-bind-sym-and-body-form

(letk-arg-bind-sym-and-body-form env map-sym binding key-path body-form)
Given a single element of a single letk binding form and a current body form, return
a map {:schema-entry :body-form} where schema-entry is a tuple
[bound-key schema external-schema?], and body-form wraps body with destructuring
for this binding as necessary.

letk-input-schema-and-body-form

(letk-input-schema-and-body-form env binding-form key-path body-form)
Given a single letk binding form, value form, key path, and body
form, return a map {:input-schema :external-input-schema :map-sym :body-form}
where input-schema is the schema imposed by binding-form, external-input-schema
is like input-schema but includes user overrides for binding vectors,
map-sym is the symbol which it expects the bound value to be bound to,
and body-form wraps body in the bindings from binding-form from map-sym.

name-sym

(name-sym x)
Returns symbol of x's name.
Converts a keyword/string to symbol, or removes namespace (if any) of symbol

positional-arg-bind-sym-and-body

(positional-arg-bind-sym-and-body env binding body-form)
Given a single element of a fnk binding form and a current body form, return
a pair [[k bind-sym] new-body-form] where bind-sym is a suitable symbol to bind
to k in the fnk arglist (including tag metadata if applicable) and new-body-form
is wrapped with destructuring for this binding as necessary.

positional-arg-bind-syms-and-body

(positional-arg-bind-syms-and-body env bind body-form)
Given a fnk binding form and body form, return a pair
[bind-sym-map new-body-form] where bind-sym-map is a map from keyword args
to binding symbols and and new-body-form wraps body to do any extra processing
of nested or optional bindings above and beyond the bindings achieved by
bind-sym-vector.

positional-fn

(positional-fn fnk arg-ks)
Given argument order in arg-ks, produce an ordinary fn that can be called
with arguments in this order. arg-ks must include all required keys of fnk.

Example: (= ((positional-fn a-fnk [:b :a]) [1 2]) (a-fnk {:a 2 :b 1}))

Can only be applied to fnks with a positional form, and should yield
a function that is significantly faster than calling fnk directly by
avoiding the construction and destructuring of the outer map.  Uses 'eval',
so while the produced function is fast, the actual production of the
positional-fn is generally relatively slow.

positional-fnk-form

(positional-fnk-form fn-name external-input-schema ordered-ks->opt arg-sym-map body)
Takes an optional name, input schema, seq of ordered [key optional?] pairs,
an arg-sym-map from these keywords to symbols, and and a positional fn body
that can reference these symbols.
Produces a form generating a IFn/PFnk that can be called as a keyword function,
and has metadata containing the positional function for efficient compilation
as described in 'efficient-call-forms' and 'positional-fn' above, with
argument order the same as in input-schema by default.   Example:

(def f (eval (i/positional-fnk-form 'foo {:x s/Any (s/optional-key :y) s/Any}
                [`(+ ~'x (if (= ~'y i/+none+) 5 ~'y))])))

(= [6 3] [(f {:x 1}) (f {:x 1 :y 2})])
(= [6 3] [((i/positional-fn f [:x]) 1) ((i/positional-fn f [:y :x]) 2 1)]).

positional-info

(positional-info fnk)
If fnk has a positional function implementation, return the pair
[positional-fn positional-arg-ks] such that if positional-arg-ks is [:a :b :c],
calling (positional-fn a b c) is equivalent to calling (fnk {:a a :b b :c c}),
but faster.  Optional values to fnk can be simulated by passing +none+ as the
value, i.e., (positional-fn +none+ b +none) is like (fnk {:b b}).

qualified-sym

(qualified-sym x)
Returns qualified symbol of x, an instance of Named

schema-override

(schema-override sym schema)