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
99 lines (87 loc) · 3.54 KB
/
defconversion.clj
File metadata and controls
99 lines (87 loc) · 3.54 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
(ns java-time.defconversion
(:refer-clojure :exclude (vector))
(:require [java-time.graph :as g]
[clj-tuple :refer (vector)]))
(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]
(if (sequential? in)
in
(vector in)))
(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]
(vector (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))))
(if (= (count idxs) (count xs))
cost
;; TODO: mark as lossy conversion
;; currently we just incur a 0.5*number of types dropped penalty
(+ cost (* 0.5 (- (count xs) (count combo)))))))))
(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)]
(throw (ex-info (format "Conversion %s -> %s already exists: %s!" from to existing)
{:from from, :to to, :existing existing}))
(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- call-conversion [nm tp args]
`(if-let [[path# fn#] (g/conversion-fn
@graph
(types-of ~args)
(g/types ~(to-seq tp)))]
(if-let [result#
(try (fn# ~args)
(catch Exception e#
(throw
(ex-info "Conversion failed"
{:path (:path path#), :arguments ~args, :to ~tp} e#))))]
(if (instance? clojure.lang.ISeq ~tp)
result#
(first result#))
(throw (ex-info
(format "Conversion from %s to %s returned nil!"
~args ~tp ~(str nm))
{:arguments ~args, :to ~tp, :constructor ~nm})))
(throw (ex-info (format "Could not convert %s to %s!" ~args ~tp ~(str nm))
{: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 _ tp _ implicit-arities & fn-bodies]
(let [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 "True if an instance of " tp ".")
[v#]
(instance? ~tp v#)))))