[nop] Refactor type-checking, remove vestigial utils ns
This commit is contained in:
parent
9c260b03c4
commit
ba6477c097
2 changed files with 38 additions and 185 deletions
|
|
@ -6,54 +6,51 @@
|
|||
|
||||
;;;; Fallback type tests
|
||||
|
||||
(defn- memoize-type-test
|
||||
"Unfortunately the only ~reliable way we can tell if something's
|
||||
really serializable/readable is to actually try a full roundtrip."
|
||||
[test-fn]
|
||||
(let [cache_ (enc/latom {})] ; {<type> <type-okay?>}
|
||||
(defn cache-by-type [f]
|
||||
(let [cache_ (enc/latom {})] ; {<type> <result_>}
|
||||
(fn [x]
|
||||
(let [t (type x)
|
||||
gensym? (re-find #"__\d+" (str t))
|
||||
cacheable? (not gensym?) ; Hack, but no obviously better solutions
|
||||
test (fn [] (try (test-fn x) (catch Exception _ false)))]
|
||||
(let [t (if (fn? x) ::fn (type x))]
|
||||
(if-let [result_ (get (cache_) t)]
|
||||
@result_
|
||||
(if-let [uncacheable-type? (re-find #"\d" (str t))]
|
||||
(do (f x))
|
||||
@(cache_ t #(or % (delay (f x))))))))))
|
||||
|
||||
(if cacheable?
|
||||
@(cache_ t #(if % % (delay (test))))
|
||||
(do (test)))))))
|
||||
(def seems-readable?
|
||||
(cache-by-type
|
||||
(fn [x]
|
||||
(try
|
||||
(enc/read-edn (enc/pr-edn x))
|
||||
true
|
||||
(catch Throwable _ false)))))
|
||||
|
||||
(def seems-readable? (memoize-type-test (fn [x] (-> x enc/pr-edn enc/read-edn) true)))
|
||||
(def seems-serializable?
|
||||
(let [mtt
|
||||
(memoize-type-test
|
||||
(fn [x]
|
||||
(let [class-name (.getName (class x))
|
||||
c (Class/forName class-name) ; Try 1st (fail fast)
|
||||
bas (java.io.ByteArrayOutputStream.)
|
||||
_ (.writeObject (java.io.ObjectOutputStream. bas) x)
|
||||
ba (.toByteArray bas)]
|
||||
|
||||
#_
|
||||
(cast c
|
||||
(.readObject ; Unsafe + usu. unnecessary to check
|
||||
(ObjectInputStream.
|
||||
(ByteArrayInputStream. ba))))
|
||||
|
||||
true)))]
|
||||
|
||||
(cache-by-type
|
||||
(fn [x]
|
||||
(if (instance? java.io.Serializable x)
|
||||
(if (fn? x)
|
||||
false ; Reports as true but is unreliable
|
||||
(mtt x))
|
||||
false))))
|
||||
(enc/cond
|
||||
(fn? x) false ; Falsely reports as Serializable
|
||||
|
||||
(instance? java.io.Serializable x)
|
||||
(try
|
||||
(let [c (Class/forName (.getName (class x))) ; Try 1st (fail fast)
|
||||
bas (java.io.ByteArrayOutputStream.)
|
||||
_ (.writeObject (java.io.ObjectOutputStream. bas) x)
|
||||
ba (.toByteArray bas)]
|
||||
#_
|
||||
(cast c
|
||||
(.readObject ; Unsafe + usu. unnecessary to check
|
||||
(ObjectInputStream. (ByteArrayInputStream. ba))))
|
||||
true)
|
||||
(catch Throwable _ false))
|
||||
|
||||
:else false))))
|
||||
|
||||
(comment
|
||||
(enc/qb 1e4 ; [2.52 2.53 521.34 0.63]
|
||||
(seems-readable? "Hello world") ; Cacheable
|
||||
(seems-serializable? "Hello world") ; Cacheable
|
||||
(seems-readable? (fn [])) ; Uncacheable
|
||||
(seems-serializable? (fn [])) ; Uncacheable
|
||||
))
|
||||
(enc/qb 1e6 ; [60.83 61.16 59.86 57.37]
|
||||
(seems-readable? "Hello world")
|
||||
(seems-serializable? "Hello world")
|
||||
(seems-readable? (fn []))
|
||||
(seems-serializable? (fn []))))
|
||||
|
||||
;;;; Java Serializable
|
||||
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
(ns ^:no-doc taoensso.nippy.utils
|
||||
"Private, implementation detail."
|
||||
(:require [clojure.string :as str]
|
||||
[taoensso.encore :as enc])
|
||||
(:import [java.io ByteArrayInputStream ByteArrayOutputStream Serializable
|
||||
ObjectOutputStream ObjectInputStream]))
|
||||
|
||||
;;;; Fallback type tests
|
||||
;; Unfortunately the only ~reliable way we can tell if something's
|
||||
;; really serializable/readable is to actually try a full roundtrip.
|
||||
|
||||
(defn- memoize-type-test [test-fn]
|
||||
(let [cache_ (enc/latom {})] ; {<type> <type-okay?>}
|
||||
(fn [x]
|
||||
(let [t (type x)
|
||||
gensym? (re-find #"__\d+" (str t))
|
||||
cacheable? (not gensym?) ; Hack, but no obviously better solutions
|
||||
test (fn [] (try (test-fn x) (catch Exception _ false)))]
|
||||
|
||||
(if cacheable?
|
||||
@(cache_ t #(if % % (delay (test))))
|
||||
(do (test)))))))
|
||||
|
||||
(def readable? (memoize-type-test (fn [x] (-> x enc/pr-edn enc/read-edn) true)))
|
||||
(def serializable?
|
||||
(let [mtt
|
||||
(memoize-type-test
|
||||
(fn [x]
|
||||
(let [class-name (.getName (class x))
|
||||
c (Class/forName class-name) ; Try 1st (fail fast)
|
||||
bas (ByteArrayOutputStream.)
|
||||
_ (.writeObject (ObjectOutputStream. bas) x)
|
||||
ba (.toByteArray bas)]
|
||||
|
||||
#_
|
||||
(cast c
|
||||
(.readObject ; Unsafe + usu. unnecessary to check
|
||||
(ObjectInputStream.
|
||||
(ByteArrayInputStream. ba))))
|
||||
|
||||
true)))]
|
||||
|
||||
(fn [x]
|
||||
(if (instance? Serializable x)
|
||||
(if (fn? x)
|
||||
false ; Reports as true but is unreliable
|
||||
(mtt x))
|
||||
false))))
|
||||
|
||||
(comment
|
||||
(enc/qb 1e4
|
||||
(readable? "Hello world") ; Cacheable
|
||||
(serializable? "Hello world") ; Cacheable
|
||||
(readable? (fn [])) ; Uncacheable
|
||||
(serializable? (fn [])) ; Uncacheable
|
||||
)) ; [2.52 2.53 521.34 0.63]
|
||||
|
||||
;;;;
|
||||
|
||||
(defn- is-coll?
|
||||
"Checks for explicit `IPersistentCollection` types with Nippy support.
|
||||
Tedious but preferable since a `freezable?` false positive would be much
|
||||
worse than a false negative."
|
||||
[x]
|
||||
(let [is? #(when (instance? % x) %)]
|
||||
(or
|
||||
(is? clojure.lang.APersistentVector)
|
||||
(is? clojure.lang.APersistentMap)
|
||||
(is? clojure.lang.APersistentSet)
|
||||
(is? clojure.lang.PersistentList)
|
||||
(is? clojure.lang.PersistentList$EmptyList) ; (type '())
|
||||
(is? clojure.lang.PersistentQueue)
|
||||
(is? clojure.lang.PersistentTreeSet)
|
||||
(is? clojure.lang.PersistentTreeMap)
|
||||
(is? clojure.lang.PersistentVector$ChunkedSeq)
|
||||
|
||||
(is? clojure.lang.IRecord) ; TODO Possible to avoid the interface check?
|
||||
(is? clojure.lang.LazySeq)
|
||||
|
||||
;; Too non-specific: could result in false positives (which would be a
|
||||
;; serious problem here):
|
||||
;; (is? clojure.lang.ISeq)
|
||||
|
||||
)))
|
||||
|
||||
(comment (is-coll? (clojure.lang.PersistentVector$ChunkedSeq. [1 2 3] 0 0)))
|
||||
|
||||
(defmacro ^:private is? [x c] `(when (instance? ~c ~x) ~c))
|
||||
|
||||
(defn freezable?
|
||||
"Alpha - subject to change.
|
||||
Returns truthy iff Nippy *appears* to support freezing the given argument.
|
||||
|
||||
`:allow-clojure-reader?` and `:allow-java-serializable?` options may be
|
||||
used to enable the relevant roundtrip fallback test(s). These tests are
|
||||
only **moderately reliable** since they're cached by arg type and don't
|
||||
test for pre/post serialization value equality (there's no good general
|
||||
way of doing so)."
|
||||
|
||||
;; TODO Not happy with this approach in general, could do with a refactor.
|
||||
;; Maybe return true/false/nil (nil => maybe)?
|
||||
|
||||
([x] (freezable? x nil))
|
||||
([x {:keys [allow-clojure-reader? allow-java-serializable?]}]
|
||||
(if (is-coll? x)
|
||||
(when (enc/revery? freezable? x) (type x))
|
||||
(or
|
||||
(is? x clojure.lang.Keyword)
|
||||
(is? x java.lang.String)
|
||||
(is? x java.lang.Long)
|
||||
(is? x java.lang.Double)
|
||||
(nil? x)
|
||||
|
||||
(is? x clojure.lang.BigInt)
|
||||
(is? x clojure.lang.Ratio)
|
||||
|
||||
(is? x java.lang.Boolean)
|
||||
(is? x java.lang.Integer)
|
||||
(is? x java.lang.Short)
|
||||
(is? x java.lang.Byte)
|
||||
(is? x java.lang.Character)
|
||||
(is? x java.math.BigInteger)
|
||||
(is? x java.math.BigDecimal)
|
||||
(is? x #=(java.lang.Class/forName "[B"))
|
||||
|
||||
(is? x clojure.lang.Symbol)
|
||||
|
||||
(is? x java.util.Date)
|
||||
(is? x java.util.UUID)
|
||||
(is? x java.util.regex.Pattern)
|
||||
|
||||
(when (and allow-clojure-reader? (readable? x)) :clojure-reader)
|
||||
(when (and allow-java-serializable? (serializable? x)) :java-serializable)))))
|
||||
|
||||
(comment
|
||||
(enc/qb 10000 (freezable? "hello")) ; 0.79
|
||||
(freezable? [:a :b])
|
||||
(freezable? [:a (fn [x] (* x x))])
|
||||
(freezable? (.getBytes "foo"))
|
||||
(freezable? (java.util.Date.) {:allow-clojure-reader? true})
|
||||
(freezable? (Exception. "_") {:allow-clojure-reader? true})
|
||||
(freezable? (Exception. "_") {:allow-java-serializable? true})
|
||||
(freezable? (atom {}) {:allow-clojure-reader? true
|
||||
:allow-java-serializable? true}))
|
||||
Loading…
Reference in a new issue