forked from dm3/clojure.java-time
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdefconversion.clj
More file actions
102 lines (89 loc) · 3.69 KB
/
defconversion.clj
File metadata and controls
102 lines (89 loc) · 3.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
(ns java-time.defconversion
(:require [java-time.graph :as g]))
(def graph (atom (g/conversion-graph)))
(defn- check-arity [t vs]
(when-not (= (g/arity t) (count vs))
(throw (ex-info (format "Arity of %s doesn't match the values %s!" t vs)
{:types t, :values vs})))
vs)
(defn- to-seq [in]
(cond-> in
(not (sequential? in)) vector))
(defn- wrap-validation [from to f]
(fn [vs]
(let [result (apply f (check-arity from vs))]
(check-arity to (to-seq result)))))
(defn- combinations [xs f cost]
(let [idxs (g/continuous-combinations (count xs))]
(for [combo idxs]
[(fn [& vs]
(let [res (to-seq (apply f vs))]
(subvec res (first combo) (inc (last combo)))))
(g/types (subvec xs (first combo) (inc (last combo))))
(cond-> cost
;; TODO: mark as lossy conversion
;; currently we just incur a 0.5*number of types dropped penalty
(not= (count idxs) (count xs))
(+ (* 0.5 (- (count xs) (count combo)))))])))
(def ^:dynamic *fail-on-duplicate-conversion?* true)
(defn conversion!
([from to f] (conversion! from to f 1))
([from-type-vec to-type-vec f cost]
(let [from (g/types (to-seq from-type-vec))
tos (combinations (to-seq to-type-vec) f cost)]
(doseq [[f to cost] tos]
(swap! graph
(fn [g]
(if-let [existing (g/get-conversion g from to)]
(if *fail-on-duplicate-conversion?*
(throw (ex-info (format "Conversion %s -> %s already exists: %s!" from to existing)
{:from from, :to to, :existing existing}))
g)
(let [f (wrap-validation from to f)]
(g/assoc-conversion g from to f cost)))))))))
(defn types-of [xs]
(g/types (map type xs)))
(defn ^:internal call-conversion [nm tp args]
(if-let [[path f] (g/conversion-fn
@graph
(types-of args)
(g/types [tp]))]
(or (try (first (f args))
(catch Exception e
(throw
(ex-info "Conversion failed"
{:path (:path path), :arguments args, :to tp}
e))))
(throw (ex-info
(format "Conversion from %s to %s returned nil!" args tp)
{:arguments args, :to tp, :constructor nm})))
(throw (ex-info (format "Could not convert %s to %s!" args tp)
{:arguments args, :to tp, :constructor nm}))))
(defn- gen-implicit-arities [nm tp arities]
(for [arity arities]
(let [args (mapv #(gensym (str "arg" (inc %))) (range arity))]
`(~args (call-conversion ~nm ~tp ~args)))))
(defn get-path [from to]
(let [[p _] (g/conversion-fn @graph
(g/types (to-seq from))
(g/types (to-seq to)))]
(select-keys p [:path :cost])))
(defmacro deffactory [nm docstring returnskw tp implicit-arities-kw implicit-arities & fn-bodies]
(assert (string? docstring))
(assert (= :returns returnskw))
(assert (= :implicit-arities implicit-arities-kw))
(let [^Class tpcls (resolve tp)
_ (assert (class? tpcls) (str tp " is not resolvable"))
tp (-> ^Class tpcls .getName symbol)
fn-name (with-meta nm {:tag tp})
explain-fn-name (symbol (str "path-to-" nm))
predicate-name (symbol (str nm "?"))]
`(do (defn ~fn-name ~docstring
~@(concat
fn-bodies
(gen-implicit-arities nm tp implicit-arities)))
(defn ~predicate-name
~(str "Returns true if `v` is an instance of " tp ", otherwise false.")
{:arglists '[[~'v]]}
[v#]
(instance? ~tp v#)))))