(ns emmy.function
"Procedures that act on Clojure's function and multimethod types, along with
extensions of the Emmy generic operations to functions.
See [the `Function`
cljdocs](https://cljdoc.org/d/org.mentat/emmy/CURRENT/doc/data-types/function)
for a discussion of generic function arithmetic."
(:refer-clojure :exclude [get get-in memoize with-meta name])
(:require [clojure.core :as core]
[clojure.core.match :refer [match]]
[emmy.generic :as g]
[emmy.util :as u]
[emmy.value :as v])
#?(:clj
(:import (clojure.lang AFunction RestFn MultiFn Keyword Symbol Var)
(java.lang.reflect Method))))

Function Algebra

this namespace extends the emmy generic operations to Clojure functions and multimethods. (Of course, this includes the generic operations themselves!)

Utilities

(defprotocol IArity
(arity [f]
"Return the cached or obvious arity of `f` if we know it. Otherwise
delegates to heavy duty reflection."))
{:impls {clojure.lang.PersistentTreeSet {:arity #object[emmy.collection$eval82908$fn__82919 0x23cba9fc "
emmy.collection$eval82908$fn__82919@23cba9fc"
]
}
clojure.lang.Symbol {:arity #object[emmy.function$eval84753$fn__84754 0x78d98873 "
emmy.function$eval84753$fn__84754@78d98873"
]
}
clojure.lang.PersistentHashSet {:arity #object[emmy.collection$eval82908$fn__82919 0x374c388d "
emmy.collection$eval82908$fn__82919@374c388d"
]
}
clojure.lang.PersistentHashMap {:arity #object[emmy.collection$eval82838$fn__82853 0x5c51ab40 "
emmy.collection$eval82838$fn__82853@5c51ab40"
]
}
clojure.lang.MultiFn {:arity #object[emmy.function$eval84757$fn__84758 0x22ab6d7f "
emmy.function$eval84757$fn__84758@22ab6d7f"
]
}
clojure.lang.PersistentArrayMap {:arity #object[emmy.collection$eval82838$fn__82853 0x1685e349 "
emmy.collection$eval82838$fn__82853@1685e349"
]
}
java.lang.Object {:arity #object[emmy.function$eval84748$fn__84749 0x405b2944 "
emmy.function$eval84748$fn__84749@405b2944"
]
}
clojure.lang.AFunction {:arity #object[emmy.function$eval84828$fn__84829 0x118191a4 "
emmy.function$eval84828$fn__84829@118191a4"
]
}
clojure.lang.PersistentTreeMap {:arity #object[emmy.collection$eval82838$fn__82853 0x6838c67e "
emmy.collection$eval82838$fn__82853@6838c67e"
]
}
clojure.lang.IPersistentVector {:arity #object[emmy.collection$eval82687$fn__82690 0x511eff9b "
emmy.collection$eval82687$fn__82690@511eff9b"
]
}}
:method-builders {#'emmy.function/arity #object[emmy.function$eval84731$fn__84732 0x4816fdf5 "
emmy.function$eval84731$fn__84732@4816fdf5"
]
}
:method-map {:arity :arity} :on emmy.function.IArity :on-interface emmy.function.IArity :sigs {:arity {:arglists ([f]) :col 4 :doc "
Return the cached or obvious arity of `f` if we know it. Otherwise↩︎ delegates10+ more elided"
:end-col 9 :end-row 2 :name arity :row 2 :tag nil}}
:var #'emmy.function/IArity}
(extend-protocol IArity
#?(:clj Object :cljs default)
(arity [o]
(or (:arity (meta o))
;; Faute de mieux, we assume the function is unary. Most math functions
;; are.
[:exactly 1]))
Symbol
(arity [_] [:exactly 0])
MultiFn
;; If f is a multifunction, then we expect that it has a multimethod
;; responding to the argument :arity, which returns the arity.
(arity [f] (f :arity)))
nil
(defn function?
"Returns true if `f` is of [[v/kind]] `::v/function`, false otherwise."
[f]
(isa? (v/kind f) ::v/function))
#object[emmy.function$function_QMARK_ 0x550759e2 "
emmy.function$function_QMARK_@550759e2"
]
(defn- with-meta
"The current Clojurescript definition of `with-meta` first tests its
argument with `js-fn?` and generates a MetaFn if so, frustrating our
definition of IWithMeta on native JS function objects. This wrapper
delegates to our definition, which allows native functions to safely
carry metadata. Note that in Clojure one is guaranteed a fresh object
with the new metadata, but in Clojurescript the target is mutated.
This function is safe to use on freshly created functions, but may
require careful consideration in other contexts."
[f m]
#?(:cljs (v/set-js-meta! f m)
:clj (clojure.core/with-meta f m)))
#object[emmy.function$with_meta 0x7ba27e0a "
emmy.function$with_meta@7ba27e0a"
]
(defn with-arity
"Appends the supplied `arity` to the metadata of `f`, knocking out any
pre-existing arity notation.
Optionally accepts a third parameter `m` of metadata to attach to the return
function, in addition to the new `:arity` key."
([f arity]
(with-arity f arity {}))
([f arity m]
(let [new-meta (-> (meta f)
(merge m)
(assoc :arity arity))]
(with-meta f new-meta))))
#object[emmy.function$with_arity 0x5d66b4c2 "
emmy.function$with_arity@5d66b4c2"
]
(defn compose
"Arity-preserving version of `clojure.core/comp`.
The arity of a composition is the arity of the rightmost (that is, first to be
applied) function term in `fns`."
[& fns]
(let [a (arity (or (last fns)
identity))]
(with-arity (apply comp fns) a)))
#object[emmy.function$compose 0x22e013ce "
emmy.function$compose@22e013ce"
]
(defn memoize
"meta-preserving version of `clojure.core/memoize`.
The returned function will have a new `:arity` entry in its metadata with the
`arity` of the original `f`; this is because the process used to figure out a
function's arity will not work across the memoization boundary."
[f]
(let [m (meta f)
m (if (:arity m)
m
(assoc m :arity (arity f)))]
(with-meta (core/memoize f) m)))
#object[emmy.function$memoize 0x4dc79639 "
emmy.function$memoize@4dc79639"
]
(defn get
"For non-functions, acts like [[clojure.core/get]]. For function
arguments (anything that responds true to [[function?]]), returns
```clojure
(comp #(clojure.core/get % k) f)
```
If `not-found` is supplied it's passed through to the
composed [[clojure.core/get]]."
([f k]
(if (function? f)
(compose #(get % k) f)
(core/get f k)))
([f k not-found]
(if (function? f)
(compose #(get % k not-found) f)
(core/get f k not-found))))
#object[emmy.function$get 0x25fdf3a5 "
emmy.function$get@25fdf3a5"
]
(defn get-in
"For non-functions, acts like [[clojure.core/get-in]]. For function
arguments (anything that responds true to [[function?]]), returns
```clojure
(comp #(clojure.core/get-in % ks) f)
```
If `not-found` is supplied it's passed through to the
composed [[clojure.core/get-in]]."
([f ks]
(if (function? f)
(compose #(get-in % ks) f)
(core/get-in f ks)))
([f ks not-found]
(if (function? f)
(compose #(get-in % ks not-found) f)
(core/get-in f ks not-found))))
#object[emmy.function$get_in 0x4d55837c "
emmy.function$get_in@4d55837c"
]
(defn- zero-like [f]
(-> (fn [& args]
(g/zero-like (apply f args)))
(with-arity (arity f) {:from :zero-like})))
#object[emmy.function$zero_like 0x6f09877b "
emmy.function$zero_like@6f09877b"
]
(defn- one-like [f]
(-> (fn [& args]
(g/one-like (apply f args)))
(with-arity (arity f) {:from :one-like})))
#object[emmy.function$one_like 0x1e8eb2fd "
emmy.function$one_like@1e8eb2fd"
]
(def I
"Identity function. Returns its argument."
identity)
#object[clojure.core$identity 0x35d613cb "
clojure.core$identity@35d613cb"
]
(defn- identity-like [f]
(with-arity identity (arity f) {:from :identity-like}))
#object[emmy.function$identity_like 0x54a40a52 "
emmy.function$identity_like@54a40a52"
]
(defn arg-shift
"Takes a function `f` and a sequence of `shifts`, and returns a new function
that adds each shift to the corresponding argument of `f`. Too many or two few
shifts are ignored.
```clojure
((arg-shift square 3) 4) ==> 49
((arg-shift square 3 2 1) 4) ==> 49
```"
[f & shifts]
(let [shifts (concat shifts (repeat 0))]
(-> (fn [& xs]
(apply f (map g/+ xs shifts)))
(with-arity (arity f)))))
#object[emmy.function$arg_shift 0x358b2dd9 "
emmy.function$arg_shift@358b2dd9"
]
(defn arg-scale
"Takes a function `f` and a sequence of `factors`, and returns a new function
that multiplies each factor by the corresponding argument of `f`. Too many or
two few factors are ignored.
```clojure
((arg-scale square 3) 4) ==> 144
((arg-scale square 3 2 1) 4) ==> 144
```"
[f & factors]
(let [factors (concat factors (repeat 1))]
(-> (fn [& xs]
(apply f (map g/* xs factors)))
(with-arity (arity f)))))
#object[emmy.function$arg_scale 0x161a464c "
emmy.function$arg_scale@161a464c"
]
(extend-protocol v/IKind
MultiFn
(kind [_] ::v/function)
#?(:clj AFunction :cljs function)
(kind [_] ::v/function)
Var
(kind [_] ::v/function)
#?@(:cljs [MetaFn
(kind [_] ::v/function)]))
nil

we record arities as a vector with an initial keyword: [:exactly m] [:between m n] [:at-least m]

#?(:clj
(do (defn ^:no-doc arity-map [f]
(let [^"[Ljava.lang.reflect.Method;" methods (.getDeclaredMethods (class f))
;; tally up arities of invoke, doInvoke, and getRequiredArity
;; methods. Filter out invokeStatic.
pairs (for [^Method m methods
:let [name (.getName m)]
:when (not (#{"withMeta" "meta" "invokeStatic"} name))]
(condp = name
"invoke" [:invoke (alength (.getParameterTypes m))]
"doInvoke" [:doInvoke true]
"getRequiredArity" [:getRequiredArity
(.getRequiredArity ^RestFn f)]))
facts (group-by first pairs)]
{:arities (into #{} (map peek) (:invoke facts))
:required-arity (second (first (:getRequiredArity facts)))
:invoke? (boolean (seq (:doInvoke facts)))}))
(defn ^:no-doc jvm-arity [f]
(let [{:keys [arities required-arity invoke?] :as m} (arity-map f)]
(cond
;; Rule one: if all we have is one single case of invoke, then the
;; arity is the arity of that method. This is the common case.
(and (= 1 (count arities))
(not required-arity)
(not invoke?))
[:exactly (first arities)]
;; Rule two: if we have invokes for the arities 0..3,
;; getRequiredArity says 3, and we have doInvoke, then we consider that
;; this function was probably produced by Clojure's core "comp"
;; function, and we somewhat lamely consider the arity of the composed
;; function 1.
(and (= #{0 1 2 3} arities)
(= 3 required-arity)
invoke?)
[:exactly 1]
;; Rule three: if we have exactly one doInvoke and getRequiredArity,
;; then the arity at least the result of .getRequiredArity.
(and required-arity
invoke?)
[:at-least (apply min required-arity arities)]
;; Rule four: If we have more than 1 `invoke` clause, return a
;; `:between`. This won't account for gaps between the arities.
(seq arities)
[:between
(apply min arities)
(apply max arities)]
:else
(u/illegal
(str "Not enough info to determine jvm-arity of " f " :" m))))))
:cljs
(do
(defn ^:no-doc variadic?
"Returns true if the supplied function is variadic, false otherwise."
[f]
(boolean
(.-cljs$core$IFn$_invoke$arity$variadic f)))
(defn ^:no-doc exposed-arities
"When CLJS functions have different arities, the function is represented as a js
object with each arity storied under its own key."
[f]
(let [pattern (re-pattern #"invoke\$arity\$\d+")
parse (fn [s]
(when-let [arity (re-find pattern s)]
(js/parseInt (subs arity 13))))
arities (->> (map parse (js-keys f))
(concat [(.-cljs$lang$maxFixedArity f)])
(remove nil?)
(into #{}))]
(if (empty? arities)
[(alength f)]
(sort arities))))
(defn ^:no-doc js-arity
"Returns a data structure indicating the arity of the supplied function."
[f]
(let [arities (exposed-arities f)]
(cond (variadic? f)
(if (= [0 1 2 3] arities)
;; Rule 3, where we assume that any function that's variadic and
;; that has defined these particular arities is a "compose"
;; function... and therefore takes a single argument.
[:exactly 1]
;; this case is where we know we have variadic args, so we set
;; a minimum. This could break if some arity was missing
;; between the smallest and the variadic case.
[:at-least (first arities)])
;; This corresponds to rule 1 in the JVM case. We have a single
;; arity and no evidence of a variadic function.
(= 1 (count arities)) [:exactly (first arities)]
;; This is a departure from the JVM rules. A potential error here
;; would occur if someone defined arities 1 and 3, but missed 2.
:else [:between
(first arities)
(last arities)])))))
#'emmy.function/jvm-arity
(def ^:no-doc reflect-on-arity
"Returns the arity of the function f. Computing arities of clojure
functions is a bit complicated. It involves reflection, so the results are
definitely worth memoizing."
(core/memoize
#?(:cljs js-arity :clj jvm-arity)))
#object[clojure.core$memoize$fn__6946 0x7d681728 "
clojure.core$memoize$fn__6946@7d681728"
]
(def ^:dynamic *strict-arity-checks*
"If true, attempting to pass two functions of incompatible arity
into any binary function, or into [[combine-arities]], will throw. False by
default."
false)
false
#?(:clj
(extend-protocol IArity
AFunction
(arity [f] (:arity (meta f) (reflect-on-arity f))))
:cljs
(extend-protocol IArity
function
(arity [f] (:arity (meta f) (reflect-on-arity f)))
MetaFn
(arity [f] (:arity (meta f) (reflect-on-arity f)))))
nil
(defn combine-arities
"Returns the joint arity of arities `a` and `b`.
The joint arity is the loosest possible arity specification compatible with
both `a` and `b`. Throws if `a` and `b` are incompatible."
([] [:at-least 0])
([a] a)
([a b]
(letfn [(fail []
(if *strict-arity-checks*
(u/illegal (str "Incompatible arities: " a " " b))
[:at-least 0]))]
;; since the combination operation is symmetric, sort the arguments
;; so that we only have to implement the upper triangle of the
;; relation.
(if (pos? (compare (first a) (first b)))
(combine-arities b a)
(match [a b]
[[:at-least k] [:at-least k2]] [:at-least (max k k2)]
[[:at-least k] [:between m n]] (let [m (max k m)]
(cond (= m n) [:exactly m]
(< m n) [:between m n]
:else (fail)))
[[:at-least k] [:exactly l]] (if (>= l k)
[:exactly l]
(fail))
[[:between m n] [:between m2 n2]] (let [m (max m m2)
n (min n n2)]
(cond (= m n) [:exactly m]
(< m n) [:between m n]
:else (fail)))
[[:between m n] [:exactly k]] (if (<= m k n)
[:exactly k]
(fail))
[[:exactly k] [:exactly l]] (if (= k l) [:exactly k] (fail)))))))
#object[emmy.function$combine_arities 0x9ea644d "
emmy.function$combine_arities@9ea644d"
]
(defn joint-arity
"Find the most relaxed possible statement of the joint arity of the given sequence of `arities`.
If they are incompatible, an exception is thrown."
[arities]
(reduce combine-arities arities))
#object[emmy.function$joint_arity 0x6563754c "
emmy.function$joint_arity@6563754c"
]
(defn seq-arity
"Returns the most general arity compatible with the aritiies of all entries in
the supplied sequence `xs` of values."
[xs]
(transduce (map arity) combine-arities xs))
#object[emmy.function$seq_arity 0x210f678a "
emmy.function$seq_arity@210f678a"
]

Generic Implementations

A ::cofunction is a type that we know how to combine with a function in a binary operation.

(derive ::v/scalar ::cofunction)
nil
(defn- unary-operation
"For a unary function `f` (like [[g/sqrt]]), returns a function of one function
`g`. The returned function acts like `(comp f g)`. For example:
```clojure
(([[unary-operation]] f) g)
;;=> (fn [x] (f (g x)))
```"
[f]
(-> (partial comp f)
(with-arity [:exactly 1])))
#object[emmy.function$unary_operation 0x1c63f5d4 "
emmy.function$unary_operation@1c63f5d4"
]
(defn coerce-to-fn
"Given a [[value/numerical?]] input `x`, returns a function of arity `arity`
that always returns `x` no matter what input it receives.
For non-numerical `x`, returns `x`."
([x arity]
(if (v/numerical? x)
(-> (constantly x)
(with-arity arity))
x)))
#object[emmy.function$coerce_to_fn 0x46713219 "
emmy.function$coerce_to_fn@46713219"
]
(defn- binary-operation
"Accepts a binary function `op`, and returns a function of two functions `f` and
`g` which will produce the pointwise operation `op` of the results of applying
both `f` and `g` to the input.
For example:
```clojure
(([[binary-operation]] op) f g)
;;=> (fn [x] (op (f x) (g x)))
```"
[op]
(letfn [(h [f g]
(let [f-arity (if (v/numerical? f) (arity g) (arity f))
g-arity (if (v/numerical? g) f-arity (arity g))
f1 (coerce-to-fn f f-arity)
g1 (coerce-to-fn g g-arity)
arity (joint-arity [f-arity g-arity])]
(with-arity (fn [& args] (op (apply f1 args) (apply g1 args))) arity)))]
(with-arity h [:exactly 2])))
#object[emmy.function$binary_operation 0x3846f68e "
emmy.function$binary_operation@3846f68e"
]
(defn- defunary
"Given a generic unary function `generic-op`, define the multimethods necessary
to introduce this operation to function arguments."
[generic-op]
(let [unary-op (unary-operation generic-op)]
(defmethod generic-op [::v/function] [a]
(unary-op a))))
#object[emmy.function$defunary 0x62aff53b "
emmy.function$defunary@62aff53b"
]
(defn- defbinary
"Given a generic binary function `generic-op` (and an optional `binary-op` to
perform the work), define the multimethods necessary to introduce this
operation to function arguments."
([generic-op] (defbinary generic-op generic-op))
([generic-op binary-op]
(let [binop (binary-operation binary-op)]
(doseq [signature [[::v/function ::v/function]
[::v/function ::cofunction]
[::cofunction ::v/function]]]
(defmethod generic-op signature [a b]
(binop a b))))))
#object[emmy.function$defbinary 0x616171ef "
emmy.function$defbinary@616171ef"
]
(defbinary g/add g/+)
nil
(defbinary g/sub g/-)
nil
(defbinary g/mul g/*)
nil
(defunary g/invert)
#object[clojure.lang.MultiFn 0x165df7c1 "
clojure.lang.MultiFn@165df7c1"
]
(defbinary g/div g/divide)
nil
(defbinary g/expt)
nil
(defunary g/sqrt)
#object[clojure.lang.MultiFn 0x79e8dd4d "
clojure.lang.MultiFn@79e8dd4d"
]
(defunary g/negate)
#object[clojure.lang.MultiFn 0x1b9c4031 "
clojure.lang.MultiFn@1b9c4031"
]
(defunary g/negative?)
#object[clojure.lang.MultiFn 0x6e310c69 "
clojure.lang.MultiFn@6e310c69"
]
(defunary g/abs)
#object[clojure.lang.MultiFn 0x721e14f0 "
clojure.lang.MultiFn@721e14f0"
]
(defunary g/floor)
#object[clojure.lang.MultiFn 0x6b6c4b9d "
clojure.lang.MultiFn@6b6c4b9d"
]
(defunary g/ceiling)
#object[clojure.lang.MultiFn 0x775a6a7c "
clojure.lang.MultiFn@775a6a7c"
]
(defunary g/integer-part)
#object[clojure.lang.MultiFn 0x12b60d1 "
clojure.lang.MultiFn@12b60d1"
]
(defunary g/fractional-part)
#object[clojure.lang.MultiFn 0x4430be99 "
clojure.lang.MultiFn@4430be99"
]
(defbinary g/quotient)
nil
(defbinary g/remainder)
nil
(defbinary g/modulo)
nil
(defunary g/sin)
#object[clojure.lang.MultiFn 0x163caf8e "
clojure.lang.MultiFn@163caf8e"
]
(defunary g/cos)
#object[clojure.lang.MultiFn 0x3be6105f "
clojure.lang.MultiFn@3be6105f"
]
(defunary g/tan)
#object[clojure.lang.MultiFn 0x428252da "
clojure.lang.MultiFn@428252da"
]
(defunary g/asin)
#object[clojure.lang.MultiFn 0x43c45b34 "
clojure.lang.MultiFn@43c45b34"
]
(defunary g/acos)
#object[clojure.lang.MultiFn 0x1380d514 "
clojure.lang.MultiFn@1380d514"
]
(defunary g/atan)
#object[clojure.lang.MultiFn 0x6a540c36 "
clojure.lang.MultiFn@6a540c36"
]
(defbinary g/atan)
nil
(defunary g/sinh)
#object[clojure.lang.MultiFn 0x1d61d948 "
clojure.lang.MultiFn@1d61d948"
]
(defunary g/cosh)
#object[clojure.lang.MultiFn 0x8dc9785 "
clojure.lang.MultiFn@8dc9785"
]
(defunary g/tanh)
#object[clojure.lang.MultiFn 0x42231f14 "
clojure.lang.MultiFn@42231f14"
]
(defunary g/square)
#object[clojure.lang.MultiFn 0x147eaac5 "
clojure.lang.MultiFn@147eaac5"
]
(defunary g/cube)
#object[clojure.lang.MultiFn 0x7e61e389 "
clojure.lang.MultiFn@7e61e389"
]
(defunary g/exp)
#object[clojure.lang.MultiFn 0x7168c357 "
clojure.lang.MultiFn@7168c357"
]
(defunary g/log)
#object[clojure.lang.MultiFn 0x20b5f4b2 "
clojure.lang.MultiFn@20b5f4b2"
]
(comment
"This comment expands on a comment from scmutils, function.scm, in the
definition of `transpose-defining-relation`:
$T$ is a linear transformation
$$T : V -> W$$
the transpose of $T$ is
$$T^t : (W -> R) -> (V -> R)$$
\\forall a \\in V, g \\in (W -> R),
T^t : g \\to g \\circ T
ie:
(T^t(g))(a) = g(T(a))")
nil
(defmethod g/transpose [::v/function] [f]
(fn [g]
(fn [a]
(g (f a)))))
#object[clojure.lang.MultiFn 0x2ecf7620 "
clojure.lang.MultiFn@2ecf7620"
]
(defunary g/determinant)
#object[clojure.lang.MultiFn 0x213217f1 "
clojure.lang.MultiFn@213217f1"
]
(defunary g/trace)
#object[clojure.lang.MultiFn 0x3ca928c1 "
clojure.lang.MultiFn@3ca928c1"
]
(defbinary g/gcd)
nil
(defbinary g/lcm)
nil
(defbinary g/exact-divide)
nil
(defbinary g/solve-linear)
nil
(defbinary g/solve-linear-right)
nil
(defunary g/dimension)
#object[clojure.lang.MultiFn 0x56adc8b2 "
clojure.lang.MultiFn@56adc8b2"
]
(defbinary g/dot-product)
nil
(defbinary g/inner-product)
nil
(defbinary g/outer-product)
nil
(defbinary g/cross-product)
nil

Complex Operations

(defbinary g/make-rectangular)
nil
(defbinary g/make-polar)
nil
(defunary g/real-part)
#object[clojure.lang.MultiFn 0x4ed57a82 "
clojure.lang.MultiFn@4ed57a82"
]
(defunary g/imag-part)
#object[clojure.lang.MultiFn 0x41ca178e "
clojure.lang.MultiFn@41ca178e"
]
(defunary g/magnitude)
#object[clojure.lang.MultiFn 0x2df77656 "
clojure.lang.MultiFn@2df77656"
]
(defunary g/angle)
#object[clojure.lang.MultiFn 0x1ab27ac4 "
clojure.lang.MultiFn@1ab27ac4"
]
(defunary g/conjugate)
#object[clojure.lang.MultiFn 0x7fd6127a "
clojure.lang.MultiFn@7fd6127a"
]

Generic Methods

(defmethod g/zero? [::v/function] [_] false)
#object[clojure.lang.MultiFn 0x78e2923f "
clojure.lang.MultiFn@78e2923f"
]
(defmethod g/one? [::v/function] [_] false)
#object[clojure.lang.MultiFn 0x710fa3bd "
clojure.lang.MultiFn@710fa3bd"
]
(defmethod g/identity? [::v/function] [_] false)
#object[clojure.lang.MultiFn 0x3e853a24 "
clojure.lang.MultiFn@3e853a24"
]
(defmethod g/zero-like [::v/function] [f] (zero-like f))
#object[clojure.lang.MultiFn 0x79337717 "
clojure.lang.MultiFn@79337717"
]
(defmethod g/one-like [::v/function] [f] (one-like f))
#object[clojure.lang.MultiFn 0x46a24adf "
clojure.lang.MultiFn@46a24adf"
]
(defmethod g/identity-like [::v/function] [f] (identity-like f))
#object[clojure.lang.MultiFn 0x787144b0 "
clojure.lang.MultiFn@787144b0"
]
(defmethod g/exact? [::v/function] [f] (compose g/exact? f))
#object[clojure.lang.MultiFn 0x1864e04e "
clojure.lang.MultiFn@1864e04e"
]
(defmethod g/freeze [::v/function] [f]
(core/get @v/object-name-map f
(cond
(instance? MultiFn f)
(if-let [m (get-method f [Keyword])]
(m :name)
f)
#?@(:clj [(instance? AFunction f)
(:name (meta f) f)]
:cljs [(instance? MetaFn f)
(:name (.-meta f) f)])
(var? f)
(g/freeze @f)
:else f)))
#object[clojure.lang.MultiFn 0x7a0db79a "
clojure.lang.MultiFn@7a0db79a"
]