Skip to content

Commit c73d49d

Browse files
committed
Close #32: lazily load implementation
Add fn to preload impl and pointers to AOT compilation if this still isn't good enough.
1 parent 0f8a816 commit c73d49d

File tree

7 files changed

+380
-185
lines changed

7 files changed

+380
-185
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
* #81(terop): Remove clj-tuple - no advantages over Clojure vector anymore
99
* Remove `java-time.util/get-static-fields-of-type`
1010
* set Java property `java-time.util.get-static-fields-of-type=true` to revert
11+
* #32: Lazily load implementation
12+
* new function `java-time/load-java-time` to force loading
1113

1214
## 0.3.3
1315

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@ Duration, on the other hand, represents a standard duration less than or equal
173173
to a single standard (24-hour) day.
174174

175175
### Caution
176+
Lazy loading and caching is used to improve the user experience, but in some
177+
cases this is not what you want.
178+
179+
The `java-time` namespace lazily loads its implementation upon the first
180+
call to any function. However, do not rely on this behavior--if you need
181+
to preload the implementation use `java-time/load-java-time`. If this is
182+
still a problem, please use AOT compilation to decrease the load time (`java-time`
183+
will transitively compile its implementation automatically when compiling files).
184+
176185
The current incarnation of the library is relatively slow while calling the 2-3
177186
arity `zoned-date-time/offset-time/offset-date-time` constructors for the
178187
*first time* in a given Clojure runtime. If you need predictable latency at the

bin/bb-test-runner.clj

+24-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,32 @@
33
(:require [clojure.test :as t]
44
[babashka.classpath :as cp]))
55

6-
(def test-nsyms ['java-time-test
6+
(def require-nsyms ['java-time.potemkin.util
7+
'java-time.joda
8+
'java-time.core
9+
'java-time.properties
10+
'java-time.util
11+
'java-time.temporal
12+
'java-time.amount
13+
'java-time.zone
14+
'java-time.single-field
15+
'java-time.local
16+
'java-time.chrono
17+
'java-time.convert
18+
'java-time.sugar
19+
'java-time.seqs
20+
'java-time.adjuster
21+
'java-time.interval
22+
'java-time.format
23+
'java-time.clock
24+
'java-time.pre-java8
25+
'java-time
26+
])
27+
;;TODO
28+
(def test-nsyms [;'java-time-test
729
'java-time.graph-test])
830

9-
(some->> (seq test-nsyms)
31+
(some->> (seq (concat require-nsyms test-nsyms))
1032
(apply require))
1133

1234
(def test-results

dev/java_time/dev/gen.clj

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
(ns java-time.dev.gen
2+
(:require [clojure.string :as str]
3+
[clojure.set :as set]))
4+
5+
(defn normalize-argv [argv]
6+
{:post [(or (empty? %)
7+
(apply distinct? %))]}
8+
(into [] (map-indexed (fn [i arg]
9+
(if (symbol? arg)
10+
(do (assert (not (namespace arg)))
11+
(if (some #(Character/isDigit (char %)) (name arg))
12+
(symbol (apply str (concat
13+
(remove #(Character/isDigit (char %)) (name arg))
14+
[i])))
15+
arg))
16+
(symbol (str "arg" i)))))
17+
argv))
18+
19+
(defn import-fn [sym]
20+
(let [vr (find-var sym)
21+
m (meta vr)
22+
n (:name m)
23+
arglists (:arglists m)
24+
protocol (:protocol m)
25+
forward-meta (into (sorted-map) (select-keys m [:doc :tag :deprecated]))
26+
forward-meta (cond-> forward-meta
27+
(nil? (:tag forward-meta)) (dissoc :tag))
28+
impl '+impl+]
29+
(when (:macro m)
30+
(throw (IllegalArgumentException.
31+
(str "Calling import-fn on a macro: " sym))))
32+
(list 'let [impl (list 'delay
33+
(list 'load-java-time)
34+
(list 'deref (list 'resolve (list 'quote sym))))]
35+
(list* 'defn n
36+
(concat
37+
(some-> (not-empty forward-meta) list)
38+
(map (fn [argv]
39+
(let [argv (normalize-argv argv)]
40+
(list argv
41+
(if (some #{'&} argv)
42+
(list* 'apply (list 'deref impl) (remove #{'&} argv))
43+
(list* (list 'deref impl) argv)))))
44+
arglists))))))
45+
46+
(defn import-macro [sym]
47+
(let [vr (find-var sym)
48+
m (meta vr)
49+
_ (when-not (:macro m)
50+
(throw (IllegalArgumentException.
51+
(str "Calling import-macro on a non-macro: " sym))))
52+
n (:name m)
53+
arglists (:arglists m)]
54+
(list* 'defmacro n
55+
(concat
56+
(some-> (not-empty (into (sorted-map) (select-keys m [:doc :deprecated])))
57+
list)
58+
(map (fn [argv]
59+
(let [argv (normalize-argv argv)]
60+
(list argv
61+
(if (some #{'&} argv)
62+
(list* 'list* (list 'quote sym) (remove #{'&} argv))
63+
(list* 'list (list 'quote sym) argv)))))
64+
arglists)))))
65+
66+
(defn import-vars
67+
"Imports a list of vars from other namespaces."
68+
[& syms]
69+
(let [unravel (fn unravel [x]
70+
(if (sequential? x)
71+
(->> x
72+
rest
73+
(mapcat unravel)
74+
(map
75+
#(symbol
76+
(str (first x)
77+
(when-let [n (namespace %)]
78+
(str "." n)))
79+
(name %))))
80+
[x]))
81+
syms (mapcat unravel syms)]
82+
(map (fn [sym]
83+
(let [vr (requiring-resolve sym)
84+
m (meta vr)]
85+
(if (:macro m)
86+
(import-macro sym)
87+
(import-fn sym))))
88+
syms)))
89+
90+
(def impl-info
91+
{:macros '[[java-time.clock with-clock]
92+
[java-time.util when-joda-time-loaded]]
93+
:threeten-extra-fns ['[java-time.interval interval interval?]
94+
95+
'[java-time.single-field
96+
am-pm am-pm? quarter quarter? day-of-month day-of-month?
97+
day-of-year day-of-year? year-quarter year-quarter?]]
98+
:fns ['[java-time.clock with-clock-fn]
99+
'[java-time.core
100+
zero? negative? negate abs max min
101+
before? not-after? after? not-before?
102+
supports?
103+
fields units properties property
104+
as value range min-value max-value largest-min-value smallest-max-value
105+
truncate-to time-between with-zone
106+
plus minus multiply-by
107+
;; TODO below here needs unit tests
108+
chronology leap? with-value with-min-value with-max-value with-largest-min-value with-smallest-max-value]
109+
110+
'[java-time.amount
111+
duration period period? duration?
112+
nanos micros millis seconds minutes hours standard-days
113+
days weeks months years]
114+
115+
'[java-time.properties
116+
unit? unit field? field]
117+
118+
'[java-time.temporal
119+
value-range instant instant?]
120+
121+
'[java-time.local
122+
local-date local-date-time local-time
123+
local-date? local-date-time? local-time?]
124+
125+
'[java-time.single-field
126+
year year? month month? day-of-week day-of-week? month-day month-day?
127+
year-month year-month?]
128+
129+
'[java-time.zone
130+
available-zone-ids zone-id zone-offset
131+
offset-date-time offset-time zoned-date-time
132+
system-clock fixed-clock offset-clock tick-clock clock?
133+
zone-id? zoned-date-time? offset-date-time? offset-time?
134+
with-zone-same-instant with-offset with-offset-same-instant]
135+
136+
'[java-time.mock mock-clock advance-clock! set-clock!]
137+
138+
'[java-time.convert
139+
as-map convert-amount to-java-date to-sql-date to-sql-timestamp
140+
to-millis-from-epoch]
141+
142+
'[java-time.sugar
143+
monday? tuesday? wednesday? thursday? friday? saturday? sunday?
144+
weekend? weekday?]
145+
146+
'[java-time.seqs iterate]
147+
148+
'[java-time.adjuster adjust]
149+
150+
'[java-time.format format formatter]
151+
152+
'[java-time.pre-java8 java-date sql-date sql-timestamp instant->sql-timestamp sql-time]
153+
154+
'[java-time.interval
155+
move-start-to move-end-to move-start-by move-end-by
156+
start end contains? overlaps? abuts? overlap gap]]})
157+
158+
(defn gen-java-time-ns-forms []
159+
(let [require-macros (into #{} (map first) (:macros impl-info))
160+
require-fns #_(set/difference (into #{} (map first)
161+
(concat (:threeten-extra-fns impl-info) (:fns impl-info)))
162+
require-macros)
163+
;;FIXME implementations must be loaded in this order for a stable graph traversal (I think)
164+
(into #{} (map #(symbol (str "java-time." %)))
165+
'[core properties temporal amount zone single-field local chrono
166+
convert sugar seqs adjuster interval format joda clock pre-java8 mock])]
167+
(concat
168+
[";; NOTE: This namespace is generated by java-time.dev.gen"
169+
`(~'ns ~'java-time
170+
(:refer-clojure :exclude ~'(zero? range iterate max min contains? format abs))
171+
(:require ~'[java-time.util :as jt.u]
172+
~@(sort require-macros)))
173+
(format "(let [lock (Object.) do-load (delay (locking lock (require %s #?@(:bb [] :default ['java-time.mock]))))]\n (defn load-java-time \"Load java-time implementation\" [] @do-load))"
174+
(str/join " " (map #(str "'" %) (sort (disj require-fns 'java-time.mock)))))
175+
'(when *compile-files* (load-java-time))]
176+
(apply import-vars (:macros impl-info))
177+
(apply import-vars (:fns impl-info))
178+
(map #(list 'jt.u/when-threeten-extra %) (apply import-vars (:threeten-extra-fns impl-info))))))
179+
180+
(defn print-java-time-ns []
181+
(run! #(println (cond-> % (not (string? %)) pr-str)) (gen-java-time-ns-forms)))
182+
183+
(defn spit-java-time-ns []
184+
(spit "src/java_time.cljc" (with-out-str (print-java-time-ns))))
185+
186+
(comment
187+
(print-java-time-ns)
188+
(spit-java-time-ns)
189+
)

0 commit comments

Comments
 (0)