Fork me on GitHub
#clojure
<
2020-12-18
>
Calum Boal00:12:58

Having some issues implementing something using core.async. What I want is to use go blocks to do a bunch of HTTP requests and process the responses. I'm approaching this coming from golang which may be where my issues are originating. The way i'm approaching this is to have a pool of go blocks trying to read from a channel which is feeding them urls, and then returning results over another channel. The issue im having is that I can't ensure all go blocks are finished working.

hiredman00:12:03

don't use while true

hiredman00:12:23

loop only when you take a non-nil from urls-chan

hiredman00:12:30

close urls-chan when you are done

hiredman00:12:53

in general using go blocks as workers is not useful

hiredman00:12:13

since they run on a limited size threadpool (usually capped at 8 threads)

hiredman00:12:54

something like async/pipeline-blocking is likely much more of what you want

Calum Boal00:12:42

Hmm yeah potentially, in go you would use go routines even though they are also capped by physical threads, but there's a runtime for switching while they're sleeping

Calum Boal00:12:48

Figured it was the same here

Calum Boal00:12:54

Managed to get this now:

(defn worker [urls-chan results-chan]
  (async/go (loop []
              (let [url (async/<! urls-chan)]
                (if (not (nil? url))
                    (let [result (is-vulnerable? url)]
                      (println url)
                      (if (and (not (nil? result)) (not (empty? result)))
                        (async/>! results-chan (string/join "\n" result)))
                      (recur)))))))

(defn printer [results-chan]
  (async/go (loop []
              (let [result (async/<! results-chan)]
                (if (not (nil? result))
                  (do
                    (println result)
                    (recur)))))))
(defn -main
  [& args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan)
        worker-chans (vec [])
        printer-chans (vec [])]
    (dotimes [_ 10]
      (conj worker-chans (worker urls-chan results-chan)))
    (conj printer-chans (printer results-chan))
    (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
      (async/>!! urls-chan ln))
    (async/close! urls-chan)
    (mapv #(async/<!! %) worker-chans)
    (async/close! results-chan)
    (mapv #(async/<!! %) printer-chans)))

Calum Boal00:12:15

It's not working though haha, it was almost working a min ago

hiredman00:12:09

my initial suspicion is you are doing some blocking operations in go blocks, which is gumming up the threadpool

hiredman00:12:31

no, the go macro is just a macro

hiredman00:12:38

it is a syntax transformation

hiredman00:12:13

it can't turn your blocking io (or blocking on promises, locks, etc) in to not blocking

Calum Boal01:12:57

Current issue is still that it's just exiting before doing anything 😞

Calum Boal01:12:38

Doesn't even take a single value from url-chan in the worker

jjttjj01:12:08

@U01D6KT2PJM what does is-vulnerable? return? A boolean? When I tested it I was getting an error from calling empty? on result with result being a boolean

Calum Boal01:12:46

Think i've figured out the issue, just way too tired haha

Calum Boal01:12:59

I can post the full code if you like, but nah it was a boolean now it returns a string

jjttjj01:12:46

btw if you don't have it already it might help to have an uncaught exception handler, like this:

;;
;; Assuming require [clojure.tools.logging :as log]
(Thread/setDefaultUncaughtExceptionHandler
 (reify Thread$UncaughtExceptionHandler
   (uncaughtException [_ thread ex]
     (log/error ex "Uncaught exception on" (.getName thread)))))
because that's the only way I was seeing the error

Calum Boal01:12:32

(ns the-great-escape.core
  (:require
   [clj-http.client :as client]
   [hickory.core :as html]
   [hickory.select :as s]
   [clojure.core.async :as async]
   [clojure.string :as string]
   [lambdaisland.uri :refer [uri]])
  (:gen-class))

(defn doc->hickory [doc]
  (-> (html/parse doc)
      (html/as-hickory)))

(defn fetch-and-parse-page [url]
  (-> (client/get url
                  {:cookie-policy :none
                   :redirect-strategy :none})
      (:body)
      (doc->hickory)))

(defn extract-asset-tags [body]
  (s/select (s/or
             (s/attr :href)
             (s/attr :src))
            body))

(defn extract-href-urls [tags]
  (map #(get-in %1 [:attrs :href]) tags))

(defn extract-src-urls [tags]
  (map #(get-in %1 [:attrs :src]) tags))

(defn extract-asset-urls [tags]
  (->> (extract-href-urls tags)
       (filter #(not (nil? %1)))))

(defn parse-urls [urls]
  (map uri urls))

(defn local-asset? [base-domain {:keys [host]}]
  (or (nil? host) (= base-domain host)))

(defn filter-local-paths [base-domain parsed-urls]
    (->> (filter (partial local-asset? base-domain) parsed-urls)
         (map :path)
         (filter #(not (nil? %1)))
         ))

(defn url->parts-map [url]
  (let [parts (string/split url #"/")]
    {:dir (str "/" (second parts))
     :path (str "/" (string/join "/" (drop 2 parts)))}))

(defn urls->parts-maps [urls]
  (map url->parts-map urls))
(defn get-unique-dirs [parts-map]
  (map #(first (val %1)) (group-by :dir parts-map)))

(defn filter-bad-traversals [parts-map]
  (filter #(and (not= (:dir %1) "/") (not= (:path %1) "/")) parts-map))

(defn build-original-url [base-url parts-map]
  (str (assoc base-url :path (str (:dir parts-map) (:path parts-map)))))

(defn build-traversal-url [base-url parts-map]
  (str (assoc base-url :path (str (:dir parts-map) ".." (:dir parts-map) (:path parts-map)))))

(defn request-and-hash [url]
  (-> (client/get url {:redirect-strategy :none
                       :cookie-policy :none})
      (:body)
      (hash)))


(defn doc->candidates [base-domain doc]
  (->> doc
       (extract-asset-tags)
       (extract-asset-urls)
       (parse-urls)
       (filter-local-paths base-domain)
       (urls->parts-maps)
       (get-unique-dirs)
       (filter-bad-traversals)))

(defn traversal-comparison [base-url candidate]
  (try
    (let [baseline (request-and-hash (build-original-url base-url candidate))
          traversal (request-and-hash (build-traversal-url base-url candidate))
          blank (hash "")]
      (if (not= baseline blank)
        (if (= baseline traversal)
          (build-traversal-url base-url candidate))))
    (catch Exception _
        nil)))

(defn is-vulnerable? [url]
  (try 
  (let [url (uri url)
        doc (fetch-and-parse-page (str url))
        candidates (doc->candidates (:host url) doc)]
    (filter #(not (nil? %1)) (map #(traversal-comparison url %1) candidates)))
  (catch Exception _
    nil)))


(defn stdin-urls []
  (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
    (println ln)))

(defn worker [urls-chan results-chan]
  (async/go (loop []
              (let [url (async/<! urls-chan)]
                (if (some? url)
                    (let [result (is-vulnerable? url)]
                      (if (and (not (nil? result)) (not (empty? result)))
                        (async/>! results-chan (string/join "\n" result)))
                      (recur)))))))

(defn printer [results-chan]
  (async/go (loop []
              (let [result (async/<! results-chan)]
                (if (some? result)
                  (do
                    (println result)
                    (recur)))))))

(defn spawn-workers [urls-chan results-chan n]
  (loop [workers []
         x 0]
    (if (= x n)
      (identity workers)
      (recur (conj workers (worker urls-chan results-chan)) (inc x)))))

(defn -main
  [& args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan 100)
        worker-chans (spawn-workers urls-chan results-chan 10)
        printer-chans (into [] (printer results-chan))]
    (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
      (async/>!! urls-chan ln))
    (async/close! urls-chan)
    (mapv #(async/<!! %) worker-chans)
    (async/close! results-chan)
    (mapv #(async/<!! %) printer-chans)))

Calum Boal01:12:53

urls over stdin

Calum Boal01:12:04

Don't worry about it though, think i'm close to figuring it out

Calum Boal01:12:08

(defn worker [urls-chan results-chan]
  (async/go (loop []
              (let [url (async/<! urls-chan)]
                (if (some? url)
                    (let [result (is-vulnerable? url)]
                      (if (and (not (nil? result)) (not (empty? result)))
                        (async/>! results-chan (string/join "\n" result)))
                      (recur)))))))

(defn printer [results-chan]
  (async/go (loop []
              (let [result (async/<! results-chan)]
                (if (some? result)
                  (do
                    (println result)
                    (recur)))))))

(defn spawn-workers [urls-chan results-chan n]
  (loop [workers []
         x 0]
    (if (= x n)
      (identity workers)
      (recur (conj workers (worker urls-chan results-chan)) (inc x)))))

(defn -main
  [& args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan 100)
        worker-chans (spawn-workers urls-chan results-chan 10)
        printer-chans (conj [] (printer results-chan))]
    (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
      (async/>!! urls-chan ln))
    (async/close! urls-chan)
    (mapv #(async/<!! %) worker-chans)
    (async/close! results-chan)
    (mapv #(async/<!! %) printer-chans)))

Calum Boal00:12:03

(defn worker [urls-chan results-chan]
  (async/go (while true
              (let [url (async/<! urls-chan)
                    result (is-vulnerable? url)]
                (if (and (not (nil? result)) (not (empty? result)))
                  (async/>! results-chan (string/join "\n" result)))))))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (let [urls-chan (async/chan 100)
        results-chan (async/chan 100)]
    (dotimes [_ 10]
      (worker urls-chan results-chan))
    (async/go (while true
          (println (async/<! results-chan))))
  (doseq [ln (line-seq (java.io.BufferedReader. *in*))]
    (async/>!! urls-chan ln))))

kwladyka00:12:42

Anyone have an example of java.util.logging formatter in Clojure? The best to print custom JSON :)

hiredman00:12:24

https://gist.github.com/hiredman/64bc7ee3e89dbdb3bb2d92c6bddf1ff6 is a mini logging library built on java.util.logging that logs clojure data

👍 6
kwladyka00:12:19

thank you, I check this one!

dpsutton06:12:26

on http://clojure.org, the section about typehinting (in the weird characters guide) in the reader gives the following example for typehinting: (def ^Integer five 5). Yet all over Clojure's source code, ^long is used (although possibly in different contexts, typehinting locals and return types for functions). When I typehint a simple var like that if i use ^Long i get its tag as java.lang.Long and when typehinted as ^long i get #function[clojure.core/long]. Are these things semantically different? One obviously wrong?

Alex Miller (Clojure team)07:12:05

they are semantically different

Alex Miller (Clojure team)07:12:25

Java has 7 primitive types with lowercase letters - int, long, etc

Alex Miller (Clojure team)07:12:46

it also has object-based wrappers around those primitive types java.lang.Integer, java.lang.Long, etc

Alex Miller (Clojure team)07:12:23

in many places these will automatically converted if needed so that hides a lot of mismatches

Alex Miller (Clojure team)07:12:01

I'd say that particular example is confusing

Alex Miller (Clojure team)07:12:38

by default, Clojure will read integer numbers as java.lang.Long objects

Alex Miller (Clojure team)07:12:29

so ^Long there would probably make more sense, but this is case is probably not a good place to really need/want that

dpsutton07:12:55

I think what really confused me was when i type hinted the var with ^long, I would get errors of > Syntax error (IllegalArgumentException) compiling . at (REPL:1:31). > Unable to resolve classname: clojure.core$long@67ec8477 when the compiler was using that typehint.

dpsutton07:12:48

and that made me want to find some proper documentation instead of just the kinda rule of thumb nature of my knowledge of type hinting

dpsutton07:12:14

(def ^long year-threshold 20)
#'user/year-threshold
(def ^:private past-threshold (.. (LocalDateTime/now)
                                  (minus year-threshold ChronoUnit/YEARS)
                                  (toInstant ZoneOffset/UTC)
                                  (getEpochSecond)))
Syntax error (IllegalArgumentException) compiling . at (REPL:1:31).
Unable to resolve classname: clojure.core$long@67ec8477

phronmophobic07:12:51

can you type hint a def? I'm reading https://clojure.org/reference/java_interop#typehints > They can be placed on function parameters, let-bound names, var names (when defined), and expressions but I'm not sure

lassemaatta07:12:53

or atleast the part discussing {:tag 'long}

phronmophobic07:12:20

ah ok, @UC506CB5W’s link seems to show that you can.

Alex Miller (Clojure team)07:12:48

you can type hint a def, you just have to be aware that type hints on the var (as in def, but also the function name in defn) are evaluated, so ^long will evaluate to the long function object, which is no good. defn type hints on the arg vector and in the arg vector are handled by defn macro and are not evaluated

dpsutton07:12:16

ah ok. that's the secret sauce that i was missing in documentation. I didn't understand why inline typehints could be ^long but on a var like that had to be ^Long. I thought i remembered that the clojure.core/long function was identified with the `'long' and classname typehint, but that might be cljs or might just be me just completely making stuff up

dpsutton07:12:50

thanks as always for your answers @U064X3EF3

zendevil.eth07:12:39

I’m using the monger database, which is showing the results from the find function. It’s showing the results as empty even though I have items in my database. Here’s the code:

(ns humboiserver.db.core
  (:require
   [monger.core :as mg]
   [monger.gridfs :as gfs]
   [monger.query :as q]
   [monger.collection :as mc]
   [cheshire.core :as json]
   )
  (:import
   org.bson.types.ObjectId))

(let [{:keys [conn db]}
      (mg/connect-via-uri “:/<dbname>?retryWrites=true&w=majority”)]
  (defn find [coll query] (mc/find-maps db coll query))

Here’s the usage:
(db/find “featured” {})

And here’s the featured database: [![enter image description here][1]][1] [1]: https://i.stack.imgur.com/kWZbS.png Yet doing the find is returning an empty sequence. How to fix this?

Gerome09:12:28

Hello! Is there an equivalent to a tap function in clojure? Something that does this: (fn [f x] (f x) x) ?

Gerome09:12:26

It would be most useful for stuff like (tap println (do-some-stuff data)) and is a little easier to read and write than what I usually do: (let [x (do-some-stuff data)] (println x) x).

Gerome09:12:18

Oh! This is awesome! Thanks! @admin055

👍 3
Lu11:12:23

Hi! I am not sure this question belongs here, so if you think there’s a better channel please tell me. Anyways, for all mac users, how do you switch jdk globally - not limited to current shell. I can’t seem to find a one script solution i.e. switch jdk 11 that sets the jdk globally.

phoenixjj11:12:55

I have set JAVA_HOME in ~/.zshrc.

Lu11:12:43

I see but say you change JAVA_HOME in current shell, then when a new shell is opened it will default to whatever is in ~/.zshrc. To fix this I resorted to some emacs helpers that allow me to manage the jdk version emacs uses from within emacs itself.. but yeah ideally it would be great to be able to do this in a shell. Thanks for the response anyways 🙂

dharrigan12:12:54

Maybe sdkman would help?

Lu12:12:16

It kinda helps but from the docs it doesn’t seem you can change a default besides right after installing a new jdk through the prompt 🙂

dharrigan12:12:09

Or is that shell only?

borkdude12:12:35

@lucio Check out jenv

☝️ 3
Lu12:12:59

awesome it seems it does exactly what I need. Will try it out now.. @borkdude thanks!

simongray12:12:21

@lucio I’ll vouch for jenv too.

👍 3
kwladyka17:12:00

Java file foo/bar/Class.java is foo.bar.Class. Clojure

(ns foo.bar)
(def custom (proxy [SimpleFormatter] []
                (format [^LogRecord record]
                  (println record)
                  "bar")))
is? What is the “path” to custom? It looks it is not api.logs.mycustom. I am trying to figure out settings for java.util.logging java.util.logging.ConsoleHandler.formatter = api.logs.custom How should I prepare this class to get this “path”?

dpsutton17:12:48

(import '(java.util.logging ConsoleHandler
                            Logger
                            LogRecord
                            SimpleFormatter
                            Level))

(defonce ^Logger logger (doto (Logger/getLogger "clojure")
                          (.setUseParentHandlers false)
                          (.addHandler
                           (doto (ConsoleHandler.)
                             (.setLevel Level/ALL)
                             (.setFormatter
                              (proxy [SimpleFormatter] []
                                (format [^LogRecord record]
                                  (let [sb (StringBuilder.)]
                                    (.append sb "#:log{")
                                    (.append sb ":z ")
                                    (.append sb (pr-str (str (java.time.Instant/ofEpochMilli (.getMillis record)))))
                                    ;; (.append sb " :b ")
                                    ;; (.append sb (format "%02d" (.getSequenceNumber record)))
                                    ;; (.append sb " :c ")
                                    ;; (.append sb (format "%02d" (.getThreadID record)))
                                    (.append sb " :v :")
                                    (.append sb (.toLowerCase (.getName (.getLevel record))))
                                    (.append sb " :n ")
                                    (.append sb (.getSourceClassName record))
                                    (.append sb " :l ")
                                    (.append sb (.getSourceMethodName record))
                                    (.append sb " :m ")
                                    (.append sb (pr-str (.getMessage record)))
                                    (doseq [p (seq (.getParameters record))
                                            :when (map? p)
                                            [k v] (seq p)]
                                      (doto sb
                                        (.append " ")
                                        (cond->
                                            (namespace k) (.append (pr-str k))
                                            (not (namespace k)) (-> (.append ":_/") (.append (name k))))
                                        (.append " ")
                                        (.append (pr-str v))))
                                    (when-let [t (.getThrown record)]
                                      (.append sb " :thrown \n")
                                      (.append sb (pr-str t)))
                                    (.append sb "}\n")
                                    (str sb)))))))))

(doseq [level '[severe warning info config fine finer finest]]
  (intern *ns*
          level
          (fn
            ([&form &env msg]
             (let [ns (name (ns-name *ns*))]
               `(.logp logger ~(symbol "java.util.logging.Level" (.toUpperCase (name level))) ~ns ~(str (:line (meta &form))) (print-str ~msg))))
            ([&form &env msg parameters]
             (let [ns (name (ns-name *ns*))]
               `(let [p# ~parameters]
                  (if (map? p#)
                    (.logp  logger
                            ~(symbol "java.util.logging.Level" (.toUpperCase (name level)))
                            ~ns
                            ~(str (:line (meta &form)))
                            (print-str ~msg)
                            p#)
                    (.logp  logger
                            ~(symbol "java.util.logging.Level" (.toUpperCase (name level)))
                            ~ns
                            ~(str (:line (meta &form)))
                            (print-str ~msg)
                            ^Throwable p#)))))))
  (.setMacro ^clojure.lang.Var (ns-resolve *ns* level)))

dpsutton17:12:12

i modified that snippet so it uses the fully qualified java.util.logging.Level/ stuff so you can use it from other namespaces

p-himik17:12:54

Just in case you still need to access Clojure vars from Java: https://clojure.org/reference/java_interop#_calling_clojure_from_java

kwladyka17:12:02

@U11BV7MTK Do you know how to refer to this from configuration file for JUL? I know how to achieve this in pure Clojure / Java, but I wanted to use config file to point the formatter.

kwladyka17:12:12

But I don’t know how to point to this heh

kwladyka17:12:46

java.util.logging.ConsoleHandler.formatter = api.logs.mycustom <- exactly this line of config

dpsutton17:12:53

no i was just playing around with it this morning. I think the point of it is that it uses JUL and ignores the config as it makes its own logger named clojure and its configured inline there

kwladyka17:12:49

maybe I will end with this. I wanted to have only formatter in Clojure and set everything in config file

kwladyka17:12:03

but I don’t know how to refer to the class made by proxy in clj file heh

dpsutton17:12:39

you want to use this from java?

kwladyka17:12:58

not sure what you mean

dpsutton17:12:12

why are you writing a line like this: java.util.logging.ConsoleHandler.formatter = api.logs.mycustom ?

kwladyka17:12:39

(ns foo.bar)
(def custom (proxy [SimpleFormatter] []
                (format [^LogRecord record]
                  (println record)
                  "bar")))
I want to use above here in JUL.properties: java.util.logging.ConsoleHandler.formatter = api.logs.custom

kwladyka17:12:12

JUL.properties is config file for java.util.logging

kwladyka17:12:24

like log4j2.properties

dpsutton17:12:29

dunno sorry

kwladyka17:12:04

you can use config file by "-Djava.util.logging.config.file=JUL.properties"

kwladyka17:12:25

so then I could only use Clojure for Formatter code

kwladyka17:12:37

And keep everything native

dpsutton17:12:03

i think this snippet is kinda intended to just be evaluated in a random namespace and used there almost transiently. its a quick in the repl thing and not really what you're looking for. and for your usecase i think you'd need to aot the file for it to work as that classfile needs to exist already (but i'm not sure about that)

kwladyka18:12:55

BTW What / Why are you doing in (doseq [level '[severe warning info config fine finer finest]] ?

kwladyka18:12:35

Why not just overwrite default root logger?

kwladyka18:12:53

(let [root-logger (.getLogger (LogManager/getLogManager) "")
        formatter (proxy [SimpleFormatter] []
                    (format [^LogRecord record]
                      (println record)
                      "bar"))
        console-handler (doto (ConsoleHandler.)
                          ;(.setUseParentHandlers false)
                          (.setFormatter formatter))]

    (doseq [handler (.getHandlers root-logger)]
      (.removeHandler root-logger handler))

    (doto root-logger
      (.setLevel Level/INFO)
      (.addHandler console-handler)))
this is what I have so far

kwladyka18:12:47

Do you understand when use Formatter vs SimpleFormatter?

kwladyka14:12:40

(def ANSI-colours {:reset "\u001B[0m"
                   :black "\u001B[30m"
                   :red "\u001B[31m"
                   :green "\u001B[32m"
                   :yellow "\u001B[33m"
                   :blue "\u001B[34m"
                   :purple "\u001B[35m"
                   :cyan "\u001B[36m"
                   :white "\u001B[37m"})

(defn getCause [^Throwable thrown]
  (when-let [cause ^Throwable (.getCause thrown)]
    {:class (.getClass cause)
     :message (.getMessage cause)
     :stack-trace (clojure.string/join "\n" (map str (.getStackTrace cause)))}))

(defn getThrown [^LogRecord record]
  (when-let [thrown ^Throwable (.getThrown record)]
    (let [cause (getCause thrown)]
      (cond->
        {:class (.getClass thrown)
         :message (.getMessage thrown)
         :stack-trace (clojure.string/join "\n" (map str (.getStackTrace thrown)))}
        cause (assoc :cause cause)))))

(defn record->map [^LogRecord record]
  (let [thrown (getThrown record)
        level (.getLevel record)]
    (cond->
      {:severity (.getName level)
       :message (.getMessage record)
       :logger-name (.getLoggerName record)}
      (= Level/SEVERE level) (assoc "@type" "")
      thrown (assoc :thrown thrown))))

(def root-logger (let [root-logger (.getLogger (LogManager/getLogManager) "")
                       formatter (proxy [SimpleFormatter] []
                                   (^String format [^LogRecord record]
                                     (let [color (if (= Level/SEVERE (.getLevel record))
                                                   (:red ANSI-colours)
                                                   (:white ANSI-colours))]
                                       (str color
                                            (json/write-value-as-string
                                              (record->map record)
                                              #_(json/object-mapper {:pretty true}))
                                            (:reset ANSI-colours)
                                            (System/getProperty "line.separator")))))
                       console-handler (doto (ConsoleHandler.)
                                         ;(.setUseParentHandlers false)
                                         (.setFormatter formatter))]

                   (doseq [handler (.getHandlers root-logger)]
                     (.removeHandler root-logger handler))

                   (doto root-logger
                     (.setLevel Level/INFO)
                     (.addHandler console-handler))))
here I share with you my solution for jsonPayload for google cloud logging

borkdude22:12:50

I know this isn't a very scientific benchmark, but I get the same-ish numbers with just nth and a self inlined version (.nth ^clojure.lang.Indexed [0 1] 1) here:

user=> (time (dotimes [_ 100000000000] (.nth ^clojure.lang.Indexed [0 1] 1)))
"Elapsed time: 1937.860586 msecs"
nil
user=> (time (dotimes [_ 100000000000] (nth [0 1] 1)))
"Elapsed time: 1901.539964 msecs"
which kind of surprised me since RT/nth does an extra step right? Maybe that step is just very very cheap or JIT-optimized.

phronmophobic22:12:27

just based on the implementation, I'm not sure how much extra it's doing:

(defn nth
  "Returns the value at the index. get returns nil if index out of
  bounds, nth throws an exception unless not-found is supplied.  nth
  also works for strings, Java arrays, regex Matchers and Lists, and,
  in O(n) time, for sequences."
  {:inline (fn  [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~@nf)))
   :inline-arities #{2 3}
   :added "1.0"}
  ([coll index] (. clojure.lang.RT (nth coll index)))
  ([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))

phronmophobic22:12:12

how does it compare to the (. clojure.lang.RT (nth ~c ~i ~@nf)) version?

borkdude22:12:21

I mean the RT#nth version is doing something extra

kwladyka22:12:23

I don’t have time to dig into it but maybe ^clojure.lang.Indexed make a difference?

hiredman22:12:12

why do you think one version is doing something extra?

hiredman22:12:59

that kind of thing is, to make a crazily broad claim, basically free

borkdude22:12:47

Right. And in clojure itself - less free but maybe not so bad either?

borkdude22:12:55

(instance? ...) I mean

hiredman22:12:11

it is complicated, but very often exactly the same

hiredman22:12:18

an instance check compiles to a single jvm instruction in java, in clojure we have a function instance?, and calling that would be more expensive, but several features of clojure (definline, and compiler intrinsics) will usually turn it into a single instruction

borkdude22:12:14

I don't see any :inline stuff on instance? itself though

hiredman22:12:27

(the compiler recognizes some static methods and replaces them with bytecode directly instead of a method call)

borkdude22:12:35

right. clever :)

paulocuneo23:12:04

how does it compare to (.get ^List [0 1] 1) ?

borkdude23:12:40

getting something from a list is O(n) right

borkdude23:12:00

indexed is way faster

borkdude23:12:24

but it depends on what the List implementation is

hiredman23:12:36

ah, actually it isn't definline and the intrinsics stuff

borkdude23:12:57

in Clojure a list is a linked list, with O(n) access - but in Java it could also be an ArrayList which probably is indexed

hiredman23:12:04

the compiler special cases known calls to instance?

borkdude23:12:44

that explains why it's so blazing fast

hiredman23:12:24

if it sees a call where the first thing is a reference to the var clojure.core/instance? it does the single bytecode instruction (with maybe an extra instruction to box the boolean)

hiredman23:12:26

that must predate the more general pattern of using definline to emit a static method call, and the compiler having a mapping between certain static methods and the bytecode to emit instead

hiredman23:12:46

it is important to keep in mind instance? is not the same thing as satisfies?

borkdude23:12:59

yeah - satisfies? can be slow I've heard

hiredman23:12:53

reminds me to remove a call to satisfies? from a patch