https://github.com/paintparty/lasertag
Lasertag is a library for categorizing types of values in Clojure, ClojureScript, and Babashka
https://github.com/paintparty/lasertag
babashka clojure clojurescript fireworks pretty-printing tagging taxonomy value-types
Last synced: about 2 months ago
JSON representation
Lasertag is a library for categorizing types of values in Clojure, ClojureScript, and Babashka
- Host: GitHub
- URL: https://github.com/paintparty/lasertag
- Owner: paintparty
- License: other
- Created: 2023-11-17T07:48:32.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-03-12T22:12:19.000Z (about 2 months ago)
- Last Synced: 2025-03-12T23:21:43.271Z (about 2 months ago)
- Topics: babashka, clojure, clojurescript, fireworks, pretty-printing, tagging, taxonomy, value-types
- Language: Clojure
- Homepage:
- Size: 271 KB
- Stars: 3
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# LasertagLasertag is a library for categorizing types of values in Clojure, ClojureScript, and Babashka. This library fell out of work on the colorizing pretty-printing engine that powers [Fireworks](https://github.com/paintparty/fireworks).
For a quick summary of the functionality, check out [this table](examples).
## Usage
Requires Clojure `1.9.0` or higherIf using with Babashka, requires Babashka `v1.12.196` or higher
Add as a dependency to your project:
```clojure
[io.github.paintparty/lasertag "0.10.0"]
```Require it:
```Clojure
(require '[lasertag.core :refer [tag tag-map]])
```Or import into your namespace:
```Clojure
(ns myns.core
(:require
[lasertag.core :refer [tag tag-map]]))
```The function `lasertag.core/tag` will return a descriptive tag:
```Clojure
(tag 1) ;; => :number
(tag 1.5) ;; => :number
(tag "hi") ;; => :string
(tag :hi) ;; => :keyword
(tag "#^hi$") ;; => :regex
(tag [1 2 3]) ;; => :vector
(tag '(1 2 3)) ;; => :seq
(tag (range 3)) ;; => :seq
```The tag is a keyword by default but you can pass an options map if you want a string or symbol:
```Clojure
(tag 1 {:format :string}) ;; => "number"
(tag 1 {:format :symbol}) ;; => number
```The function `lasertag.core/tag-map` will return a map with additional info.
```Clojure
;; string
(tag-map "hi")
=>
{:tag :string
:type java.lang.String
:all-tags #{:string}
:classname "java.lang.String"};; map
=>
{:tag :map
:type clojure.lang.PersistentArrayMap
:all-tags #{:coll
:array-map
:coll-type
:map-like
:map
:carries-meta}
:classname "clojure.lang.PersistentArrayMap"
:coll-size 1};; function in ClojureScript
(ns foo.core)
(defn xy [x y] (+ x y))(tag-map xy)
=>
{:tag :function
:type #object[Function]
:fn-name "xy"
:fn-ns "visual_testing.shared"
:fn-args [x y]
:all-tags #{:function}
:classname "Function"};; JS function
(tag-map js/ParseFloat)
=>
{:tag :function
:all-tags #{:function}
:type js/ParseFloat
:fn-name "parseFloat"
:fn-args [s]
:js-built-in-method-of js/Number
:js-built-in-function? true}
```
> [!WARNING]
> Currently, the `:fn-args` entry is only available in ClojureScript.
> The `:fn-name` will not work as expected in ClojureScript advanced compilation.With `tag-map`, There are 3 additional params you can pass with the optional second argument (options map). Setting these to `false` will exclude certain information. Depending on how you are using `tag-map`, this could help with performance.
```clojure
:include-all-tags?
:include-function-info?
:include-js-built-in-object-info?
```The following example excludes the `:all-tags` entry, as well as the related `:coll-type?`, `:map-like?`, `:number-type?` and `:coll-size?` entries:
```clojure
(tag-map xy)
=>
{:tag :function
:all-tags #{:function}
:type #object[Function]
:fn-name "xy"
:fn-ns "myns.core"
:fn-args [x y]}(tag-map xy {:include-all-tags? false})
=>
{:tag :function
:type #object[Function]
:fn-name "xy"
:fn-ns "myns.core"
:fn-args [x y]}
```Excluding the function-info related entries:
```clojure
(tag-map xy)
=>
{:tag :function
:all-tags #{:function}
:type #object[Function]
:fn-args [x y]
:fn-name "xy"
:fn-ns "myns.core"}(tag-map xy {:include-function-info? false})
=>
{:tag :function
:all-tags #{:function}
:type #object[Function]}
```Excluding the JS built-in-object related entries:
```clojure
(tag-map js/JSON)
=>
{:tag :js/Object
:all-tags #{:js/Object :map-like :coll-type}
:type #object[Object]
:js-built-in-object? true
:js-built-in-object-name "JSON"}(tag-map js/JSON {:include-js-built-in-object-info? false})
=>
{:tag :js/Object
:all-tags #{:js/Object :map-like :coll-type}
:type #object[Object]}
```
## Examples
### Clojure
`lasertag.core/tag` vs `clojure.core/type`| Input value | `lasertag.core/tag` | `clojure.core/type` |
| :--- | :--- | :--- |
| `"hi"` | `:string` | `java.lang.String` |
| `:hi` | `:keyword` | `clojure.lang.Keyword` |
| `"^hi$"` | `:regex` | `java.util.regex.Pattern` |
| `true` | `:boolean` | `java.lang.Boolean` |
| `mysym` | `:symbol` | `clojure.lang.Symbol` |
| `nil` | `:nil` | `nil` |
| `[1 2 3]` | `:vector` | `clojure.lang.PersistentVector` |
| `#{1 3 2}` | `:set` | `clojure.lang.PersistentHashSet` |
| `{:a 2, :b 3}` | `:map` | `clojure.lang.PersistentArrayMap` |
| `(map inc (range 3))` | `:seq` | `clojure.lang.LazySeq` |
| `(range 3)` | `:seq` | `clojure.lang.LongRange` |
| `(:a :b :c)` | `:seq` | `clojure.lang.PersistentList` |
| `Infinity` | `:infinity` | `java.lang.Double` |
| `-Infinity` | `:-infinity` | `java.lang.Double` |
| `NaN` | `:nan` | `java.lang.Double` |
| `1/3` | `:number` | `clojure.lang.Ratio` |
| `(byte 0)` | `:number` | `java.lang.Byte` |
| `(short 3)` | `:number` | `java.lang.Short` |
| `(double 23.44)` | `:number` | `java.lang.Double` |
| `1M` | `:number` | `java.math.BigDecimal` |
| `1` | `:number` | `java.lang.Long` |
| `(float 1.5)` | `:number` | `java.lang.Float` |
| `(char a)` | `:char` | `java.lang.Character` |
| `(java.math.BigInteger. "171")` | `:number` | `java.math.BigInteger` |
| `(java.util.Date.)` | `:inst` | `java.util.Date` |
| `java.util.Date` | `:java.lang.Class` | `java.lang.Class` |
### ClojureScript
`lasertag.core/tag` vs `cljs.core/type`
| Input value | `lasertag.core/tt`| `cljs.core/type` |
| :--- | :--- | :--- |
| `"hi"` | `:string` | `#object[String]` |
| `:hi` | `:keyword` | `cljs.core/Keyword` |
| `"^hi$"` | `:regex` | `#object[RegExp]` |
| `true` | `:boolean` | `#object[Boolean]` |
| `mysym` | `:symbol` | `cljs.core/Symbol` |
| `nil` | `:nil` | `nil` |
| `[1 2 3]` | `:vector` | `cljs.core/PersistentVector` |
| `#{1 3 2}` | `:set` | `cljs.core/PersistentHashSet` |
| `{:a 2, :b 3}` | `:map` | `cljs.core/PersistentArrayMap` |
| `(map inc (range 3))` | `:seq` | `cljs.core/LazySeq` |
| `(range 3)` | `:seq` | `cljs.core/IntegerRange` |
| `(:a :b :c)` | `:seq` | `cljs.core/List` |
| `Infinity` | `:infinity` | `#object[Boolean]` |
| `-Infinity` | `:-infinity` | `#object[Boolean]` |
| `js/parseInt` | `:function` | `#object[Function]` |
| `(new js/Date.)` | `:int` . | `#object[Date]` |
| `(.values #js [1 2 3])` | `:js/Iterator` | `#object[Object]` |
| `(array "a" "b")` | `:js/Array` | `#object[Array]` |
| `(new js/Int8Array #js ["a" "b"])` | `:js/Int8Array` | `#object[Int8Array]` |
| `(new js/Set #js[1 2 3])` | `:js/Set` | `#object[Set]` |
### Additional ClojureScript Examples
```clojure
;; Record type(defrecord MyRecordType [a b c d])
(def my-record-type (->MyRecordType 4 8 4 5))
(tag my-record-type)
=> :myns.core/MyRecordType;; Multimethod definition
(defmulti different-behavior (fn [x] (:x-type x)))
(tag different-behavior)
=> :defmulti;; Javascript Promise
(def my-promise (js/Promise. (fn [x] x)))
(tag my-promise)
=> :js/Promise
```### Instance methods on JavaScript built-ins
`lasertag.core/tag-map` can help to differentiate between an instance method on a JS built-in that might have the same name as another instance method on a different JS built-in.
Consider the following 2 values. Both are instance methods on JS built-ins. Both are named `concat`, although they are different functions that expect different inputs and yield different outputs:
```Clojure
(aget "hi" "concat")
(aget #js [1 2 3] "concat")
```
Calling `js/console.log` on either of the values above would result in the same (somewhat cryptic) result in a browser dev console, the exact format of which may vary depending on the browser:
```Clojure
ƒ concat() { [native code] }
```
Calling `clojure.pprint/pprint` or `clojure.core/println`
on either of the values above would give you this:```Clojure
#object[concat]
```
If you need enhanced reflection in situations like this, the result of `lasertag.core/tag-map` offers the following 2 entries:
`:js-built-in-method-of`
`:js-built-in-method-of-name````Clojure
(tag-map (aget "hi" "concat"))
=>
{:js-built-in-method-of #object[String]
:js-built-in-method-of-name "String"
:js-built-in-function? true
:fn-name "concat"
:type #object[Function]
:all-tags #{:function}
:fn-args []
:tag :function}(tag-map (aget #js [1 2 3] "concat"))
=>
{:js-built-in-method-of #object[Array]
:js-built-in-method-of-name "Array"
:js-built-in-function? true
:fn-name "concat"
:type #object[Function]
:all-tags #{:function}
:fn-args []
:tag :function}
```
## Test
The JVM tests require [leiningen](https://leiningen.org/) to be installed.```Clojure
lein test
```The ClojureScript tests:
```Clojure
npm run test
```
## Status
Alpha, subject to change. Issues welcome, see [contributing](#contributing).
## Contributing
Issues for bugs, improvements, or features are very welcome. Please file an issue for discussion before starting or issuing a PR.
## License
Copyright © 2024-2025 Jeremiah Coyle
This program and the accompanying materials are made available under the
terms of the Eclipse Public License 2.0 which is available at
http://www.eclipse.org/legal/epl-2.0.This Source Code may also be made available under the following Secondary
Licenses when the conditions for such availability set forth in the Eclipse
Public License, v. 2.0 are satisfied: GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or (at your
option) any later version, with the GNU Classpath Exception which is available
at https://www.gnu.org/software/classpath/license.html.