Lasertag 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.
For a quick summary of the functionality, check out this table.
Requires Clojure 1.9.0
or higher
If using with Babashka, requires Babashka v1.12.196
(Coming Soon) or higher
Add as a dependency to your project:
[io.github.paintparty/lasertag "0.9.0"]
Require it:
(require '[lasertag.core :refer [tag tag-map]])
Or import into your namespace:
(ns myns.core
(:require
[lasertag.core :refer [tag tag-map]]))
The function lasertag.core/tag
will return a descriptive tag:
(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:
(tag 1 {:format :string}) ;; => "number"
(tag 1 {:format :symbol}) ;; => number
The function lasertag.core/tag-map
will return a map with additional info.
;; 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.
: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:
(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:
(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:
(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]}
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 |
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] |
;; 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
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:
(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:
ƒ concat() { [native code] }
Calling clojure.pprint/pprint
or clojure.core/println
on either of the values above would give you this:
#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
(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}
The JVM tests require leiningen to be installed.
lein test
The ClojureScript tests:
npm run test
Alpha, subject to change. Issues welcome, see contributing.
Issues for bugs, improvements, or features are very welcome. Please file an issue for discussion before starting or issuing a PR.
Copyright © 2024 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.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close