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(Coming Soon) or higher


Add as a dependency to your project:

[io.github.paintparty/lasertag "0.10.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]}


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:java.lang.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.):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

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

(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

The ClojureScript tests:

npm run test

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