Skip to content

Commit

Permalink
Basic clj-kondo support, minor doc updates, some deprecations (#66)
Browse files Browse the repository at this point in the history
- Minor updates to docstrings and ns forms
- Ignore kondo cache
- Deprecated several utility fns
- Clean up test use/requires
- Update README
   - Add kondo info
   - Clean up code snippets
   - Add deps.edn coordinate

- Basic kondo config
- Eliminate transitive lint-as
- Upgrade project release 0.4.6
- Upgrade deps
- Set min Java version at 8

- Updated bound-fn docstrings with rationale
- Minor .gitignore update
  • Loading branch information
KingMob committed Oct 5, 2022
1 parent 3e40436 commit f22d972
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 56 deletions.
2 changes: 2 additions & 0 deletions .clj-kondo/config.edn
@@ -0,0 +1,2 @@
{:config-paths ["../resources/clj-kondo.exports/potemkin/potemkin/"]}

1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -9,3 +9,4 @@ push
.nrepl*
.idea
*.iml
.cache
32 changes: 17 additions & 15 deletions README.md
Expand Up @@ -4,10 +4,16 @@

Potemkin is a collection of facades and workarounds for things that are more difficult than they should be. All functions are within the `potemkin` namespace.

### usage
### Usage

```clj
[potemkin "0.4.5"]
##### Leiningen
```clojure
[potemkin "0.4.6"]
```

##### deps.edn
```clojure
potemkin/potemkin {:mvn/version "0.4.6"}
```

### `import-vars`
Expand All @@ -18,7 +24,7 @@ The former approach places an onus on the creator of the library; the various or

`import-vars` allows functions, macros, and values to be defined in one namespace, and exposed in another. This means that the structure of your code and the structure of your API can be decoupled.

```clj
```clojure
(import-vars
[clojure.walk
prewalk
Expand All @@ -35,7 +41,7 @@ Despite this, there are only six functions which really matter: `get`, `assoc`,

For instance, here's a map which will automatically realize any delays, allowing for lazy evaluation semantics:

```clj
```clojure
(def-map-type LazyMap [m mta]
(get [_ k default-value]
(if (contains? m k)
Expand All @@ -60,7 +66,7 @@ For instance, here's a map which will automatically realize any delays, allowing

Often a map is just a view onto another object, especially when dealing with Java APIs. While we can create a function which converts it into an entirely separate object, for both performance and memory reasons it can be useful to create a map which simply acts as a delegate to the underlying objects:

```clj
```clojure
(def-derived-map StringProperties [^String s]
:base s
:lower-case (.toLowerCase s)
Expand All @@ -75,7 +81,7 @@ The reason it's so laborious to define a map-like data structure is because the

However, using `def-abstract-type`, we can avoid this:

```clj
```clojure
(def-abstract-type ASeq
(more [this]
(let [n (next this)]
Expand All @@ -86,7 +92,7 @@ However, using `def-abstract-type`, we can avoid this:

This abstract type may be used within the body of `deftype+`, which is just like a vanilla `deftype` except for the support for abstract types.

```clj
```clojure
(deftype+ CustomSeq [s]
ASeq
clojure.lang.ISeq
Expand All @@ -105,7 +111,7 @@ While `definterface` uses an entirely different convention than `defprotocol`, `

Gensyms enforce hygiene within macros, but when quote syntax is nested, they can become a pain. This, for instance, doesn't work:

```clj
```clojure
`(let [x# 1]
~@(map
(fn [n] `(+ x# ~n))
Expand All @@ -114,7 +120,7 @@ Gensyms enforce hygiene within macros, but when quote syntax is nested, they can

Because `x#` is going to expand to a different gensym in the two different contexts. One way to work around this is to explicitly create a gensym ourselves:

```clj
```clojure
(let [x-sym (gensym "x")]
`(let [~x-sym 1]
~@(map
Expand All @@ -124,18 +130,14 @@ Because `x#` is going to expand to a different gensym in the two different conte

However, this is pretty tedious, since we may need to define quite a few of these explicit gensym names. Using `unify-gensyms`, however, we can rely on the convention that any var with two hashes at the end should be unified:

```clj
```clojure
(unify-gensyms
`(let [x## 1]
~@(map
(fn [n] `(+ x## ~n))
(range 3)))
```

### `fast-bound-fn` and `fast-memoize`

Variants of Clojure's `bound-fn` and `memoize` which are significantly faster.

### License

Copyright © 2013 Zachary Tellman
Expand Down
9 changes: 4 additions & 5 deletions project.clj
@@ -1,17 +1,16 @@
(defproject potemkin "0.4.5"
(defproject potemkin "0.4.6"
:license {:name "MIT License"}
:description "Some useful facades."
:dependencies [[clj-tuple "0.2.2"]
[riddley "0.1.12"]]
:profiles {:dev {:dependencies [[criterium "0.4.3"]
:profiles {:dev {:dependencies [[criterium "0.4.6"]
[collection-check "0.1.6"]]}
:provided {:dependencies [[org.clojure/clojure "1.8.0-RC4"]]}}
:provided {:dependencies [[org.clojure/clojure "1.11.1"]]}}
:global-vars {*warn-on-reflection* true}
:test-selectors {:default #(not (some #{:benchmark}
(cons (:tag %) (keys %))))
:benchmark :benchmark
:all (constantly true)}
:java-source-paths ["src"]
:jvm-opts ^:replace ["-server" "-Xmx4g"]
:javac-options ["-source" "1.6" "-target" "1.6"]
:javac-options ["-source" "1.8" "-target" "1.8"]
:repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"})
62 changes: 62 additions & 0 deletions resources/clj-kondo.exports/potemkin/potemkin/config.edn
@@ -0,0 +1,62 @@
{:lint-as {potemkin.collections/compile-if clojure.core/if
potemkin.collections/reify-map-type clojure.core/reify
potemkin.collections/def-map-type clj-kondo.lint-as/def-catch-all
potemkin.collections/def-derived-map clj-kondo.lint-as/def-catch-all

potemkin.types/reify+ clojure.core/reify
potemkin.types/defprotocol+ clojure.core/defprotocol
potemkin.types/deftype+ clojure.core/deftype
potemkin.types/defrecord+ clojure.core/defrecord
potemkin.types/definterface+ clojure.core/defprotocol
potemkin.types/extend-protocol+ clojure.core/extend-protocol
potemkin.types/def-abstract-type clj-kondo.lint-as/def-catch-all

potemkin.utils/doit clojure.core/doseq
potemkin.utils/doary clojure.core/doseq
potemkin.utils/condp-case clojure.core/condp
potemkin.utils/fast-bound-fn clojure.core/bound-fn

potemkin.walk/prewalk clojure.walk/prewalk
potemkin.walk/postwalk clojure.walk/postwalk
potemkin.walk/walk clojure.walk/walk

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; top-level from import-vars
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Have hooks
;;potemkin/import-fn potemkin.namespaces/import-fn
;;potemkin/import-macro potemkin.namespaces/import-macro
;;potemkin/import-def potemkin.namespaces/import-def

;; Internal, not transitive
;;potemkin/unify-gensyms potemkin.macros/unify-gensyms
;;potemkin/normalize-gensyms potemkin.macros/normalize-gensyms
;;potemkin/equivalent? potemkin.macros/equivalent?

potemkin/condp-case clojure.core/condp
potemkin/doit potemkin.utils/doit
potemkin/doary potemkin.utils/doary

potemkin/def-abstract-type clj-kondo.lint-as/def-catch-all
potemkin/reify+ clojure.core/reify
potemkin/defprotocol+ clojure.core/defprotocol
potemkin/deftype+ clojure.core/deftype
potemkin/defrecord+ clojure.core/defrecord
potemkin/definterface+ clojure.core/defprotocol
potemkin/extend-protocol+ clojure.core/extend-protocol

potemkin/reify-map-type clojure.core/reify
potemkin/def-derived-map clj-kondo.lint-as/def-catch-all
potemkin/def-map-type clj-kondo.lint-as/def-catch-all}

;; leave import-vars alone, kondo special-cases it
:hooks {:macroexpand {#_#_potemkin.namespaces/import-vars potemkin.namespaces/import-vars
potemkin.namespaces/import-fn potemkin.namespaces/import-fn
potemkin.namespaces/import-macro potemkin.namespaces/import-macro
potemkin.namespaces/import-def potemkin.namespaces/import-def

#_#_potemkin/import-vars potemkin.namespaces/import-vars
potemkin/import-fn potemkin.namespaces/import-fn
potemkin/import-macro potemkin.namespaces/import-macro
potemkin/import-def potemkin.namespaces/import-def}}}
@@ -0,0 +1,56 @@
(ns potemkin.namespaces
(:require [clj-kondo.hooks-api :as api]))

(defn import-macro*
([sym]
`(def ~(-> sym name symbol) ~sym))
([sym name]
`(def ~name ~sym)))

(defmacro import-fn
([sym]
(import-macro* sym))
([sym name]
(import-macro* sym name)))

(defmacro import-macro
([sym]
(import-macro* sym))
([sym name]
(import-macro* sym name)))

(defmacro import-def
([sym]
(import-macro* sym))
([sym name]
(import-macro* sym name)))

#_
(defmacro import-vars
"Imports a list of vars from other namespaces."
[& syms]
(let [unravel (fn unravel [x]
(if (sequential? x)
(->> x
rest
(mapcat unravel)
(map
#(symbol
(str (first x)
(when-let [n (namespace %)]
(str "." n)))
(name %))))
[x]))
syms (mapcat unravel syms)
result `(do
~@(map
(fn [sym]
(let [vr (resolve sym)
m (meta vr)]
(cond
(nil? vr) `(throw (ex-info (format "`%s` does not exist" '~sym) {}))
(:macro m) `(def ~(-> sym name symbol) ~sym)
(:arglists m) `(def ~(-> sym name symbol) ~sym)
:else `(def ~(-> sym name symbol) ~sym))))
syms))]
result))
6 changes: 5 additions & 1 deletion src/potemkin.clj
@@ -1,6 +1,10 @@
(ns potemkin
(:require
[potemkin namespaces types collections macros utils]))
[potemkin.namespaces]
[potemkin.types]
[potemkin.collections]
[potemkin.macros]
[potemkin.utils]))

(potemkin.namespaces/import-vars potemkin.namespaces/import-vars) ;; totally meta

Expand Down
2 changes: 1 addition & 1 deletion src/potemkin/collections.clj
Expand Up @@ -214,7 +214,7 @@
(with-meta [this meta])
All other necessary functions will be defined so that this behaves like a normal
Clojure map. These can be overriden, if desired."
Clojure map. These can be overridden, if desired."
[name params & body]
(let [fns '{get get*
assoc assoc*
Expand Down
16 changes: 8 additions & 8 deletions src/potemkin/types.clj
Expand Up @@ -289,25 +289,25 @@
(defonce type-bodies (atom {}))

(defmacro deftype+
"A deftype that won't evaluate if an equivalent datatype with the same name already exists,
"A deftype that won't evaluate if an equivalent type with the same name already exists,
and allows abstract types to be used."
[name params & body]
(let [body (->> (list* 'deftype name params 'potemkin.types.PotemkinType body)
clean-deftype
expand-deftype
deftype*->deftype)
clean-deftype
expand-deftype
deftype*->deftype)

classname (with-meta (symbol (str (namespace-munge *ns*) "." name)) (meta name))

prev-body (when (class? (ns-resolve *ns* name))
(@type-bodies classname))]

(when-not (and prev-body
(equivalent?
(transform-deftype* identity prev-body)
(transform-deftype* identity body)))
(equivalent?
(transform-deftype* identity prev-body)
(transform-deftype* identity body)))
(swap! type-bodies assoc classname
(r/macroexpand-all body))
(r/macroexpand-all body))

body)))

Expand Down
36 changes: 23 additions & 13 deletions src/potemkin/utils.clj
Expand Up @@ -6,9 +6,12 @@
[java.util.concurrent
ConcurrentHashMap]))

(defmacro fast-bound-fn
"Creates a variant of bound-fn which doesn't assume you want a merged
context between the source and execution environments."
(defmacro ^{:deprecated true
:no-doc true
:superseded-by "clojure.core/bound-fn"} fast-bound-fn
"Quite probably not faster than core bound-fn these days.
~45% slower in personal testing. Be sure to profile your use case."
[& fn-body]
(let [{:keys [major minor]} *clojure-version*
use-thread-bindings? (and (= 1 major) (< minor 3))
Expand All @@ -31,16 +34,20 @@
(finally
(clojure.lang.Var/resetThreadBindingFrame curr-frame#)))))))))

(defn fast-bound-fn*
"Creates a function which conveys bindings, via fast-bound-fn."
(defn ^{:deprecated true
:no-doc true
:superseded-by "clojure.core/bound-fn*"} fast-bound-fn*
"Quite probably not faster than core bound-fn* these days.
~45% slower in personal testing. Be sure to profile your use case."
[f]
(fast-bound-fn [& args]
(apply f args)))

(defn retry-exception? [x]
(defn ^:no-doc retry-exception? [x]
(= "clojure.lang.LockingTransaction$RetryEx" (.getName ^Class (class x))))

(defmacro try*
(defmacro ^:deprecated ^:no-doc try*
"A variant of try that is fully transparent to transaction retry exceptions"
[& body+catch]
(let [body (take-while
Expand Down Expand Up @@ -85,24 +92,27 @@

;;; fast-memoize

(definline re-nil [x]
(definline ^:no-doc re-nil [x]
`(let [x# ~x]
(if (identical? ::nil x#) nil x#)))

(definline de-nil [x]
(definline ^:no-doc de-nil [x]
`(let [x# ~x]
(if (nil? x#) ::nil x#)))

(defmacro memoize-form [m f & args]
(defmacro ^:no-doc memoize-form [m f & args]
`(let [k# (t/vector ~@args)]
(let [v# (.get ~m k#)]
(if-not (nil? v#)
(re-nil v#)
(let [v# (de-nil (~f ~@args))]
(re-nil (or (.putIfAbsent ~m k# v#) v#)))))))

(defn fast-memoize
"A version of `memoize` which has equivalent behavior, but is faster."
(defn ^{:deprecated true
:no-doc true
:superseded-by "clojure.core/memoize"} fast-memoize
"Quite possibly not faster than core memoize any more.
See https://github.com/clj-commons/byte-streams/pull/50 and profile your use case."
[f]
(let [m (ConcurrentHashMap.)]
(fn
Expand Down Expand Up @@ -131,7 +141,7 @@
;;;

(defmacro doit
"A version of doseq that doesn't emit all that inline-destroying chunked-seq code."
"An iterable-based version of doseq that doesn't emit inline-destroying chunked-seq code."
[[x it] & body]
(let [it-sym (gensym "iterable")]
`(let [~it-sym ~it
Expand Down

0 comments on commit f22d972

Please sign in to comment.