Fork me on GitHub
#clojure
<
2021-11-11
>
reefersleep09:11:59

Is hickory abandoned? https://github.com/davidsantiago/hickory There are many unaddressed issues and pull requests. If I look at davidsantiago’s activity on github, he made 0 contributions this year, and 1 contribution last year. Anyone know more?

simongray13:11:50

Seems so. I’ve been using it, but have replaced most of that I used it for with my own stuff.

👍 1
pez11:11:27

I can’t figure out how to get the REPL to tell me the version of cider-nrepl that is loaded. I asked here, since my google-fu was not enough to find the answer out there: https://ask.clojure.org/index.php/11268/tools-deps-how-list-dependencies-and-their-versions-from-repl

flowthing11:11:51

Guess one option would be to parse it from (System/getProperty "java.class.path").

🙏 1
delaguardo11:11:35

cider.nrepl.version/version

🙏 1
pez12:11:44

I hope you’re both considering to answer at http://ask.clojure.org. 😃

pez12:11:36

(-> (System/getProperty "java.class.path")
    (clojure.string/split #":")
    (->> (map #(clojure.string/replace % #"^/Users/pez/.m2/repository" ""))))
A bit brute, but does the work.

flowthing13:11:37

FWIW, if you care about Windows compatibility, you might want to use .File/pathSeparator instead of :.

pez13:11:07

What’s used on Windows?

pez13:11:26

Interesting. I wonder why…

flowthing13:11:47

You could also probably use the tools.deps.alpha API to get the information if you don't mind the dependency.

pez13:11:40

I’m interested in all the ways it can be done.

dpsutton13:11:17

You can’t use semicolon because that’s a path component on windows. C:\foo

👍 1
genRaiy12:11:00

advice question ... I have something that works but it doesn't feel as clean as I think it should be ...

genRaiy12:11:06

(seq (reduce (fn [form param]
               (conj form (keyword param) param))
             ['prn] '[x y]))
=> (prn :x x :y y)

kwladyka13:11:54

Did you try to write macro?

genRaiy13:11:16

I didn't. I'm not sure how it would improve the code. Open to suggestions though...

kwladyka13:11:51

I don’t know what you are doing, but if you want to generate a code based on same data macro sounds good.

kwladyka14:11:37

but maybe you need (apply prn collection) or something like that

kwladyka14:11:50

hard to say without the context what is the best approach

genRaiy14:11:13

the goal is to produce code that will print the parameter list `'[x y]`

genRaiy14:11:14

the idea was to produce pairs of kw / value so that it's obvious which variable is being referenced

genRaiy14:11:07

anyway, thanks for the suggestion

Ed16:11:37

you might want to care about the actual form that gets passed in rather than converting it to a keyword, like this maybe?

(defmacro prn-debug [& forms]
    `(do ~@(map #(list `prn :debug (list `quote %) :> %) forms)))

  (let [x 1 y 2]
    (prn-debug x (+ x y)))
  ;; outputs:
  ;; x :> 1
  ;; (+ x y) :> 3
I guess you might also want to care about the return value, so you can wrap it around things?? maybe like this??
(defmacro prn-debug [& forms]
    `(first ~(mapv #(list `doto % (list `->> (list `prn :debug (list `quote %) :>))) forms)))
which should output the same thing, but return the value of the first one?? Or are you imaging wrapping it round a defn / fn or something so it prints the args and then does whatever?

genRaiy17:11:31

thanks @U0P0TMEFJ that is indeed a powerful option

genRaiy17:11:54

the bigger picture is to enable code changes during a conform / unform round trip for ns, def and defn ... and then eventually others though that's a good enough start 🙂

genRaiy17:11:33

the actual goal is to to pass any registered functions the conformed data for the forms they are interested in and have an API that can make life easier for the authors of the functions. But those functions must return data that can be subject to unform

genRaiy17:11:40

so we would have (and this is just a sketch) something like

genRaiy17:11:46

(def sample-xform-declaration
  {:defn.params.prn
   {:enabled? false
    :ui       {:label   "Print parameters"
               :control :checkbox}
    :joins    [{:ns-regex   #"repl.*"
                :form-types #{:defn :defn-}}]
    :actions  [xform-sample-f-prn]}})

genRaiy17:11:35

and the :action functions would be passed the forms that are defined in :joins

genRaiy18:11:22

and the :ui part would control whether or not the specific xform is enabled ... like I say, very early sketch

genRaiy18:11:21

the macro for prn-debug is definitely more general than my code so I'll give it some thought

Ed12:11:20

so it's some kind of advice / interceptor framework type thing? When I've written things like that in the past, I've regretted writing things like regex-filter and ended up putting a transducer there, so you can compose filters together and write (remove #(re-matches #"regex" %)) for not, and so on ... so I'd recommend starting with that instead of :ns-regex and :form-types .. cos you might want to match all the forms that have some metadata or match some name or something - you can always write

:form-filter (filter (comp #{`defn `defn-} first))
or something? Then you're just reading forms from a file and filtering them with whatever matches before passing it to the action, and you get to name the filters and can easily join them together ... you get a sorta query language for very little effort ... just a thought 😉

genRaiy12:11:55

yeah that's a great idea - I can see how that would be more flexible

genRaiy12:11:23

and yeah I'm thinking that adding metadata will make undo more straightforward

Ed13:11:49

are you planning on getting in the way of loading files, so you're adding your own code processing layer in code loading? or altering the var contents after it's loaded? cos you're going to have to keep track of what order these transformations get applied, especially if you want to undo them 😉

genRaiy14:11:44

bigger picture is to store the code in an immutable graph DB, so yeah

genRaiy14:11:14

it's early days ... if you're interested you can read more https://github.com/repl-acement/editors

genRaiy14:11:47

although it's a bit broken now, this provides an approximate demo of how the transforms should work

genRaiy14:11:04

main bug: the panels aren't all sync'd up but that's fairly easy to fix

genRaiy14:11:57

work to do ... undo the transform (the fun bit), support multi-arity functions (straightforward)

genRaiy14:11:53

it's based on spec/conform and spec/unform

genRaiy14:11:06

the code is at https://github.com/repl-acement/repl-acement (like I say, currently broken ... I really should stop committing to main)

Ed17:11:01

ah ... so you're expecting to be in the way of people actually evaluating code, as some new tooling?? So you can just store the original form and a sequence of transforms as metadata on the var and apply them from scratch every time, right? That seems like the easiest way to implement undo, by not implementing it 😜

1
Ed17:11:18

I might have some free time to help out, but not this weekend ... I'll keep an eye on your repo and ping you if you want any help?

Ed17:11:34

also ... you're going to have to deal with destructuring and the like

Ed17:11:29

(defmacro when-let-all [bindings & body]
  (let [local? (->> bindings (partition 2) (map first)
                    (tree-seq (fn [x] (and (not (string? x)) (seqable? x))) seq)
                    set)]
    `(let ~bindings
       (when (and ~@(->> bindings destructure (partition 2) (map first) (filter local?) distinct))
         ~@body))))
that might help?

genRaiy17:11:50

if you want to discuss more, please join the discord server

genRaiy17:11:50

@U0P0TMEFJ there is some room for discussion about whether the transformations are durable or not ... something like tracing maybe just for the session but might also be useful on a production app and these kind of transforms reduce the labour of adopting logging frameworks. They could also be the basis for more extensive refactoring which would be retained. How undo operates is an open question tbh ... could be done by scrubbing back time but other work is lost then and that's not nice so there has to be some sort of means to select history in a granular manner

genRaiy17:11:37

apropos destructuring ... I'm currently relying on the clojure.core.specs to help me out for the defn specs so I'll see how that macro works with that data

Ed18:11:09

Cool ... Currently in a car ... Will join the discussion when I have more internet ;)

😎 1
genRaiy12:11:38

goal is to produce code that will print the parameter list '[x y]

genRaiy12:11:17

I know that I could avoid it altogether using pprint or tap but that's something else 🙂

slipset14:11:36

@takis_ I'll be looking into the data.json thing, but so far ai haven't been able to repro. Could your elaborate a bit on your environment and such? Perhaps in #data-json or on the ticket?

jumar14:11:52

A question about reading custom objects (org.joda.time.DateTime) in the REPL. The problem: I want to easily print and then read the printed version of collections containing joda time objects. Example - while debugging or when logging stuff floating through the app I may get this on stdout:

{:created-at #object[org.joda.time.DateTime 0x58053a61 "2021-11-10T17:31:01.000Z"]}
(possibly including multiple such things in a way larger map) Now I want to read this stuff in the REPL - but I cannot because the reader doesn't know how to read / instantiate such things. So I added data_readers.clj
{object my.ns/joda-time-data-reader}
which references this:
(ns ... 
  (:require
   [clj-time.format :as tf]))

(defn joda-time-data-reader [[class-symbol _objid date-time-string :as value]]
  (if (= "org.joda.time.DateTime" (name class-symbol))
    (tf/parse date-time-string)
    (throw (ex-info "Unsupported #object instance" {:value value}))))
But I still cannot read these things in the repl - an attempt to do so gives me an error:
;; Syntax error compiling fn* at (src/my/ns.clj:1:8458).
  ;; Can't embed object in code, maybe print-dup not defined: 2021-11-10T17:31:01.000Z
So without really knowing what is print-dup used for I find this https://groups.google.com/g/clojure/c/scAjN7_Xig0 and do something similar for the DateTime object
(defmethod print-dup org.joda.time.DateTime
    [dt stream]
    (.write stream (format "#=(org.joda.time.DateTime. %s %s %s %s %s %s %s)"
                           (.year dt)
                           (.monthOfYear dt)
                           (.dayOfMonth dt)
                           (.hourOfDay dt)
                           (.minuteOfhour dt)
                           (.secondOfMinute dt)
                           (.millisOfSecond dt))))
This finally seems to work. but it even works if i just do this
(defmethod print-dup org.joda.time.DateTime
    [dt stream]
    nil)
My questions: • Is there a better way to achieve my goal? • Did I introduce some problems with my solution that I'm not aware of? • In which scenarios I need a valid print-dup implementation? Will the dummy version suffice?

Ed17:11:40

I think you're looking for tagged literals. https://clojure.org/reference/reader#tagged_literals Rather than defining print-dup to print with the rather undesirable #=(,,,) form, you could always define a #joda/DateTime or something that you print it and read it as?

jumar05:11:26

Is there a difference though? Here I'm defining the reader function for #object (built-in tagged literal). Will it be any different if I use my own?

jumar07:11:29

That’s great, thanks!

Joshua Suskalo16:11:09

What are these performance warning, case has int tests but tested expression is not primitive warnings?

Joshua Suskalo16:11:21

is this just that I'm using a case expression with ints?

Joshua Suskalo16:11:41

I noticed these in one of my libraries today when I turned on reflection warnings

p-himik16:11:01

It has a boxed int probably:

user=> (case 1 1 1)
1
user=> (case (Integer. 1) 1 1)
Performance warning, NO_SOURCE_PATH:1:1 - case has int tests, but tested expression is not primitive.
1

p-himik16:11:22

And boxing can happen when there are no type annotations:

user=> (defn x [a] a)
#'user/x
user=> (case (x 1) 1 1)
Performance warning, NO_SOURCE_PATH:1:1 - case has int tests, but tested expression is not primitive.
1
user=> (defn x ^long [a] a)
#'user/x
user=> (case (x 1) 1 1)
1

Joshua Suskalo16:11:14

ah, okay. thanks

Joshua Suskalo16:11:57

What's the performance difference between a type hint for a primitive, and calling the associated primitive cast function?

Alex Miller (Clojure team)16:11:26

Depends where you put it and which hint :) Do you have more context?

Alex Miller (Clojure team)16:11:33

In particular ^long and ^double are special in function param/return hints

Joshua Suskalo16:11:04

Right, this is as the first argument to a case expression in order to suppress the performance warning

Alex Miller (Clojure team)16:11:03

Well ,should really be long first of all

Alex Miller (Clojure team)16:11:22

Clojure literal integers are longs

Joshua Suskalo16:11:36

Yeah, I've been casting to long

Alex Miller (Clojure team)16:11:00

If the expression is a literal number, they cant carry meta

Joshua Suskalo16:11:22

It's the result of calling first on a collection getting thrown around the stack by an Error.

Alex Miller (Clojure team)16:11:48

So type hinting it would be wrong then, it's not a primitive

Alex Miller (Clojure team)16:11:00

So I would use long cast

Joshua Suskalo16:11:04

alright, thanks

Alex Miller (Clojure team)16:11:42

The return type of that is known as prim long

Joshua Suskalo16:11:05

e.g. ^int x vs (int x)

Colin P. Hill16:11:49

Hard to give an exact number, since that's dependent on the machine. Conceptually, the conversion happens at runtime and the type hint is an indication to the compiler about how to produce more efficient code. All else being equal, pushing work to the compiler is generally going to be faster overall.

Colin P. Hill16:11:06

haven't looked at the code for int but I strongly suspect that, if x is already a primitive int, it just returns it with a type hint – making the latter just a more roundabout way to do the former

Joshua Suskalo16:11:58

More or less I'm just looking for if ^int will result in a cast in the resulting bytecode and if the function call overhead from int will be optimized away, which I guess criterium is the way to test that

Colin P. Hill16:11:07

Type hints are not casts

Joshua Suskalo16:11:37

They result in casts to primitives if it's the correct boxed type

Joshua Suskalo16:11:47

which is guaranteed to be the case here

Joshua Suskalo16:11:00

or at least that was my understanding

Colin P. Hill16:11:52

You're right, my mistake, it does can generate a cast.

user=> (defn ^long f [] "this is totes a number, I promise")     
#'user/f
user=> (f)
Execution error (ClassCastException) at user/f (REPL:1).
class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')

Colin P. Hill16:11:31

Oh really? interesting

Alex Miller (Clojure team)16:11:38

Only for long or double type hint

Alex Miller (Clojure team)16:11:15

And it's not a cast, it's generating a method in bytecode that really returns a long

Colin P. Hill16:11:57

But the exception here would suggest that, while the method's signature really returns a long, the body attempts to cast the string, right?

Alex Miller (Clojure team)16:11:30

I believe what you see above is not a cast but a typecheck ensuring that type (don't remember the exact bytecode name)

Alex Miller (Clojure team)16:11:23

I'd have to look at the bytecode to be sure

Alex Miller (Clojure team)16:11:51

The Clojure compiler may insert a cast here, don't remember

Colin P. Hill16:11:38

seems to walk like a cast and quack like a cast, at least

user=> (defn ^long f [] (int 100))
#'user/f
user=> (f)
100
user=> (defn ^long f [] (Long. 100))
#'user/f
user=> (f)
100
user=> (defn ^long f [] (Integer. 100))
#'user/f
user=> (f)
100

Alex Miller (Clojure team)17:11:33

You are also in the space of auto promotion and auto boxing, you just can't tell from this

Alex Miller (Clojure team)16:11:27

Type hints are mostly useful for allowing the right interp method to be selected

Alex Miller (Clojure team)16:11:51

You really want an explicit conversion here

respatialized19:11:46

More of a regex question than a #clojure question, I guess (happy to be redirected to the proper location for such questions), but it's sort of Clojure-specific in as much as my ultimate target is a regex that's compatible with instaparse. Anyway, here's my question. I'm trying to use lazy matchers in a regex to achieve matches against a string up to, but not including a specific pattern - if it's present. Otherwise I want the whole thing. I've managed to kind of do this using multiple non-capturing groups:

(let [re #"(.*?)(?:(?:🔚)?)"]
 (println "regex" (.toString re))
 (println "matches?" (re-matches re "text with ending🔚"))
 (println "matches?" (re-matches re "text without ending")))

; => regex (.*?)(?:(?:🔚)?)
; => matches? [text with ending🔚 text with ending]
; => matches? [text without ending text without ending]
the second match in both is what I'm after. Is there a way to specify the regex in a way that only returns those matches? I'm a relative novice w/ regex so I definitely don't have a good feel for how capturing/non-capturing groups work.

Alex Miller (Clojure team)19:11:23

re-matches always returns the full string match in the 0 position and captured groups after that in the order they appear

1
Alex Miller (Clojure team)19:11:48

and nil if it doesn't match

Alex Miller (Clojure team)19:11:49

most commonly, you destructure the return to grab what you want

Alex Miller (Clojure team)19:11:05

(let [re #"(.*?)(?:(?::end:)?)"
      [_ text] (re-matches re s)]
  ...do stuff with text, often includes a nil check for a match
  )

respatialized21:11:57

the #"(.*?)(?:(?:🔚)?)" regex seems to work with re-matches but not with the way instaparse https://github.com/Engelberg/instaparse/blob/4d1903b059e77dc0049dfbc75af9dce995756148/src/instaparse/gll.cljc#L745:

(let [m (re-matcher #"(.*?)(?:(?:🔚)?)" "text with ending🔚")]
  (do
  (.matches m)
  (.group m)
  ))
; => "text with ending🔚"
seems like I wasn't prototyping my regex using the right method, and I may need to narrow down the behavior of the groups so it works properly with the .group method.

respatialized23:11:34

I managed to experiment towards a solution for this problem that appears to work for both instaparse and for re-seq in a relatively unambiguous way:

(re-seq #"^.*?(?=🔚|$)" "text with ending🔚")
; => ("text with ending")
(re-seq #"^.*?(?=🔚|$)" "text ")
; => ("text ")