Liking cljdoc? Tell your friends :D

Lasertag

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.


Usage

Requires Clojure 1.9.0 or higher

If using with Babashka, requires Babashka v1.12.196 or higher


Add as a dependency to your project:

[io.github.paintparty/lasertag "0.11.5"]

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 
{:a :foo}
=>
{: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                     :object
 :all-tags                #{:object :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      :object
 :all-tags #{:object :js-object :map-like :coll-type}
 :type     #object[Object]}


Examples

Clojure

lasertag.core/tag vs clojure.core/type

Input valuelasertag.core/tagclojure.core/type
"hi":stringjava.lang.String
:hi:keywordclojure.lang.Keyword
"^hi$":regexjava.util.regex.Pattern
true:booleanjava.lang.Boolean
mysym:symbolclojure.lang.Symbol
nil:nilnil
[1 2 3]:vectorclojure.lang.PersistentVector
#{1 3 2}:setclojure.lang.PersistentHashSet
{:a 2, :b 3}:mapclojure.lang.PersistentArrayMap
(map inc (range 3)):seqclojure.lang.LazySeq
(range 3):seqclojure.lang.LongRange
(:a :b :c):seqclojure.lang.PersistentList
Infinity:infinityjava.lang.Double
-Infinity:-infinityjava.lang.Double
NaN:nanjava.lang.Double
1/3:numberclojure.lang.Ratio
(byte 0):numberjava.lang.Byte
(short 3):numberjava.lang.Short
(double 23.44):numberjava.lang.Double
1M:numberjava.math.BigDecimal
1:numberjava.lang.Long
(float 1.5):numberjava.lang.Float
(char a):charjava.lang.Character
(java.math.BigInteger. "171"):numberjava.math.BigInteger
(java.util.Date.):instjava.util.Date
java.util.Date:classjava.lang.Class

ClojureScript

lasertag.core/tag vs cljs.core/type

Input valuelasertag.core/ttcljs.core/type
"hi":string#object[String]
:hi:keywordcljs.core/Keyword
"^hi$":regex#object[RegExp]
true:boolean#object[Boolean]
mysym:symbolcljs.core/Symbol
nil:nilnil
[1 2 3]:vectorcljs.core/PersistentVector
#{1 3 2}:setcljs.core/PersistentHashSet
{:a 2, :b 3}:mapcljs.core/PersistentArrayMap
(map inc (range 3)):seqcljs.core/LazySeq
(range 3):seqcljs.core/IntegerRange
(:a :b :c):seqcljs.core/List
Infinity:infinity#object[Boolean]
-Infinity:-infinity#object[Boolean]
js/parseInt:function#object[Function]
(new js/Date.):inst#object[Date]
(.values #js [1 2 3]):iterable#object[Object]
(array "a" "b"):array#object[Array]
(new js/Int8Array #js ["a" "b"]):array#object[Int8Array]
(new js/Set #js[1 2 3]):set#object[Set]
(js/Promise. (fn [x] x)):promise#object[Promise]

Additional ClojureScript Examples

;; Record type

(defrecord MyRecordType [a b c d])

(def my-record-type (->MyRecordType 4 8 4 5))

(tag my-record-type) 
=> :record



;; 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) 
=> :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:

(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}

Test

The JVM tests require leiningen to be installed.

lein test

ClojureScript tests:

npm run test

Babashka tests:

bb test:bb

Status

Alpha, subject to change. Issues welcome, see 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.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close