Skip to content

Commit ce0956b

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 ce0956b

File tree

7 files changed

+383
-185
lines changed

7 files changed

+383
-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

src/java_time.cljc

+149-86
Large diffs are not rendered by default.

src/java_time/potemkin/namespaces.cljc

-96
This file was deleted.

test/java_time/dev/gen.clj

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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 [_ (require (-> sym namespace symbol))
84+
vr (resolve sym)
85+
m (meta vr)]
86+
(if (:macro m)
87+
(import-macro sym)
88+
(import-fn sym))))
89+
syms)))
90+
91+
(def impl-info
92+
{:macros '[[java-time.clock with-clock]
93+
[java-time.util when-joda-time-loaded]]
94+
:threeten-extra-fns ['[java-time.interval interval interval?]
95+
96+
'[java-time.single-field
97+
am-pm am-pm? quarter quarter? day-of-month day-of-month?
98+
day-of-year day-of-year? year-quarter year-quarter?]]
99+
:fns ['[java-time.clock with-clock-fn]
100+
'[java-time.core
101+
zero? negative? negate abs max min
102+
before? not-after? after? not-before?
103+
supports?
104+
fields units properties property
105+
as value range min-value max-value largest-min-value smallest-max-value
106+
truncate-to time-between with-zone
107+
plus minus multiply-by
108+
;; TODO below here needs unit tests
109+
chronology leap? with-value with-min-value with-max-value with-largest-min-value with-smallest-max-value]
110+
111+
'[java-time.amount
112+
duration period period? duration?
113+
nanos micros millis seconds minutes hours standard-days
114+
days weeks months years]
115+
116+
'[java-time.properties
117+
unit? unit field? field]
118+
119+
'[java-time.temporal
120+
value-range instant instant?]
121+
122+
'[java-time.local
123+
local-date local-date-time local-time
124+
local-date? local-date-time? local-time?]
125+
126+
'[java-time.single-field
127+
year year? month month? day-of-week day-of-week? month-day month-day?
128+
year-month year-month?]
129+
130+
'[java-time.zone
131+
available-zone-ids zone-id zone-offset
132+
offset-date-time offset-time zoned-date-time
133+
system-clock fixed-clock offset-clock tick-clock clock?
134+
zone-id? zoned-date-time? offset-date-time? offset-time?
135+
with-zone-same-instant with-offset with-offset-same-instant]
136+
137+
'[java-time.mock mock-clock advance-clock! set-clock!]
138+
139+
'[java-time.convert
140+
as-map convert-amount to-java-date to-sql-date to-sql-timestamp
141+
to-millis-from-epoch]
142+
143+
'[java-time.sugar
144+
monday? tuesday? wednesday? thursday? friday? saturday? sunday?
145+
weekend? weekday?]
146+
147+
'[java-time.seqs iterate]
148+
149+
'[java-time.adjuster adjust]
150+
151+
'[java-time.format format formatter]
152+
153+
'[java-time.pre-java8 java-date sql-date sql-timestamp instant->sql-timestamp sql-time]
154+
155+
'[java-time.interval
156+
move-start-to move-end-to move-start-by move-end-by
157+
start end contains? overlaps? abuts? overlap gap]]})
158+
159+
(defn gen-java-time-ns-forms []
160+
(let [require-macros (into #{} (map first) (:macros impl-info))
161+
require-fns #_(set/difference (into #{} (map first)
162+
(concat (:threeten-extra-fns impl-info) (:fns impl-info)))
163+
require-macros)
164+
;;FIXME implementations must be loaded in this order for a stable graph traversal (I think)
165+
(into #{} (map #(symbol (str "java-time." %)))
166+
'[core properties temporal amount zone single-field local chrono
167+
convert sugar seqs adjuster interval format joda clock pre-java8 mock])]
168+
(concat
169+
[";; NOTE: This namespace is generated by java-time.dev.gen"
170+
`(~'ns ~'java-time
171+
(:refer-clojure :exclude ~'(zero? range iterate max min contains? format abs))
172+
(:require ~'[java-time.util :as jt.u]
173+
~@(sort require-macros)))
174+
(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))"
175+
(str/join " " (map #(str "'" %) (sort (disj require-fns 'java-time.mock)))))
176+
'(when *compile-files* (load-java-time))]
177+
(apply import-vars (:macros impl-info))
178+
(apply import-vars (:fns impl-info))
179+
(map #(list 'jt.u/when-threeten-extra %) (apply import-vars (:threeten-extra-fns impl-info))))))
180+
181+
(defn print-java-time-ns []
182+
(run! #(println (cond-> % (not (string? %)) pr-str)) (gen-java-time-ns-forms)))
183+
184+
(defn spit-java-time-ns []
185+
(spit "src/java_time.cljc" (with-out-str (print-java-time-ns))))
186+
187+
(comment
188+
(print-java-time-ns)
189+
(spit-java-time-ns)
190+
)

test/java_time_test.clj

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
(ns java-time-test
22
(:require [clojure.test :refer :all]
33
[java-time.util :as jt.u]
4-
[java-time :as j])
4+
[java-time :as j]
5+
)
56
(:import java.util.Locale))
67

78
(def clock (j/fixed-clock "2015-11-26T10:20:30.000000040Z" "UTC"))
@@ -1025,3 +1026,10 @@
10251026
(is (= (j/local-time 0 34 0 0)
10261027
(j/local-time fmt "12:34AM")))
10271028
(is (thrown? Exception (j/local-time fmt "12:34am"))))))
1029+
1030+
(jt.u/when-threeten-extra
1031+
(require 'java-time.dev.gen)
1032+
(deftest gen-test
1033+
(is (= (slurp "src/java_time.cljc")
1034+
(with-out-str ((resolve 'java-time.dev.gen/print-java-time-ns))))
1035+
"java-time main namespace is out of date -- call (java-time.dev.gen/spit-java-time-ns)")))

0 commit comments

Comments
 (0)