Fork me on GitHub
#clojure
<
2022-09-30
>
Martynas Maciulevičius06:09:32

I got a repro of my failing local dependencies. Java 11+Clojure1.11.1: https://github.com/Invertisment/clojure-ex-java11temurin11.0.16.1 On my setup lein uberjar fails. But it works fine if I use Clojure 1.11.0

hiredman07:09:54

Do you have a ~/.lein/profiles.clj or whatever the file is called?

Martynas Maciulevičius07:09:50

{:user {:dependencies [
                       [pjstadig/humane-test-output "0.11.0"]

                       #_[org.clojure/tools.namespace "0.2.3"]
                       #_[spyscope "0.1.3"]
                       #_[criterium "0.4.1"]]

        ;; 
        :injections [

                     (require 'pjstadig.humane-test-output)
                     (pjstadig.humane-test-output/activate!)

                     ;(require '(clojure.tools.namespace repl find))
                     ;; try/catch to workaround an issue where `lein repl` outside a project dir
                     ;; will not load reader literal definitions correctly:
                     ;(try (require 'spyscope.core)
                     ;  (catch RuntimeException e))
                     ]
        :plugins [
                  [cider/cider-nrepl "0.28.5"]
                  [lein-ancient "1.0.0-RC3"]

                  #_[lein-pprint "1.1.1"]
                  #_[lein-beanstalk "0.2.6"]
                  #_[lein-clojars "0.9.1"]
                  #_[lein-create-template "0.1.1"]
                  #_[lein-marginalia "0.7.1"]
                  #_[lein-exec "0.3.1"]
                  #_[lein-midje "3.1.1"]
                  #_[lein-kibit "0.0.8"]]}}

Martynas Maciulevičius07:09:47

It doesn't fail on this Java:

java --version
openjdk 18.0.2 2022-07-19
OpenJDK Runtime Environment Temurin-18.0.2+9 (build 18.0.2+9)
OpenJDK 64-Bit Server VM Temurin-18.0.2+9 (build 18.0.2+9, mixed mode, sharing

hiredman07:09:31

Rename that to profiles.clj.bkup and try lein uberjar again

Martynas Maciulevičius07:09:08

Omg it doesn't fail anymore 😕 i.e. I didn't change the ~/.lein/profiles.clj and it doesn't fail

Martynas Maciulevičius07:09:38

I was having issues but when I changed to Java 18 then they went away. But now when I change back to Java 11 it works again :thinking_face:

Martynas Maciulevičius07:09:51

I'll try to delete profiles file if the issue will come back :thinking_face: 🤷

craftybones14:09:23

But how do you cache a sequence that isn't realised yet? Or can't be realised entirely?

craftybones14:09:34

(range) for instance?

craftybones14:09:03

You mean once it is realised it can be cached?

borkdude14:09:58

Calling count on a lazy sequence realizes the sequence

craftybones14:09:07

Right, but the risk is it may not return.

craftybones14:09:29

I understand what you are saying though, that once it is realised, it should be cached

👍 1
craftybones14:09:44

What is this in aid of?

borkdude14:09:06

Not having to walk the entire lazy seq again if you want to know the number of elements again? Of course you could cache the count yourself

borkdude14:09:22

This isn't a feature request, just a random thought

craftybones14:09:00

Ah ok. Yeah, it definitely is cacheable

Felipe Reigosa14:09:00

Hey guys, how do I pass different jvm-opts depending on the platform? There was a bug with MockMechanics (https://github.com/felipereigosa/mock-mechanics) not working on Mac, someone else solved it in part by adding an extra jvm option -XstartOnFirstThread but it is not recognized here on my linux. How do I handle that situation? Is it with different profiles?

❤️ 1
dpsutton14:09:18

i would tuck that into an :osx alias. jvm opts are concatenated so it will add in when the alias is added

Felipe Reigosa15:09:15

Thanks, could you elaborate though? How would that work exactly? I don't understand aliases much, I'm trying using assoc from this example but it's not working. https://gitea.esy.fun/yogsototh/leiningen/commit/6f04e0d43db8d8c4a4593ce1553704f8241c9cb4 I'm trying this: :aliases {"osx" ["assoc" ":jvm-opts" "-XstartOnFirstThread"]}

Felipe Reigosa15:09:47

@U11BV7MTK Nevermind, I figured it out. Thanks again. I'm using this: :aliases {"osx" ["update-in" ":jvm-opts" "concat" "[\"-XstartOnFirstThread\"]"]} And calling it with lein osx -- run Is that the right way to do it?

rolt15:09:55

or something like {:profiles {:osx {:jvm-opts ["-XstartOnFirstThread"]}}} lein with-profiles +osx run (you'd need to double check that it actually concat the jvm opts and dot not replace them, but from what I remember that's what it does)

rolt15:09:35

you could also add it to your user profile instead of the project.clj

👍 1
Mikko Harju17:09:54

EDIT: Nevermind, I messed up myself.

Dallas Surewood21:09:50

Hey everyone. I'm trying to build a cool debugging macro on top of another macro, and I found a problem that might not have a solution. Macros that have a non-quoted function call in their definition seem to always run, even if they are never evaluated. I am using a library that does this, and I'm trying to extend one of its macros with a macro of my own, but it won't work. Here's a simple example of what I mean. The problem macro represents a library macro that I can't change. It will crash if passed a non number. You can see the way I'm using it, however, is that I want it to be completely ignored unless the ~val that will be passed to it is a number. I would expect the output to look like this. (func 4) => prints "4" (func "a") => returns "a" Currently (func "a" crashes with class java.lang.String cannot be cast to class java.lang.Number Is there a way around this without changing problem?

seancorfield21:09:31

The problem is that problem requires a number at expansion time.

seancorfield21:09:07

And your func macro still causes problem to be expanded, even if it doesn't run.

seancorfield21:09:02

Can you provide a bit more detail about the actual macro you can't change?

seancorfield21:09:52

(thanks for updating the original post!)

Dallas Surewood21:09:13

It's from scope-capture. It's the same scenario, where sc.api/letsc is expecting a number passed.

Dallas Surewood21:09:56

So I was trying to pass it a number not determined at expansion time, like ~val, and it wasnt having it.

seancorfield21:09:12

Part of the issue here is that macros do not have access to runtime values -- because they expand prior to runtime. So if you have a macro whose expansion requires a literal number, you have to pass a literal number.

seancorfield21:09:25

As letsc says, ep-id must be a literal value. It can't be a symbol bound to something because ep-id is used at expansion time here, not runtime.

Dallas Surewood22:09:13

Is there a way to wrap it in a macro or some hacky trick where it won't expand until it's actually needed?

Dallas Surewood22:09:21

And can still be run?

seancorfield22:09:22

Your (func "a") call expands to (if (string? "a") "a" (problem "a")) -- so your argument will be evaluated three times by the way -- and then that expands -- and that's when (inc "a") is called (at expansion time).

seancorfield22:09:56

(defmacro func [val]
  (let [v val]
    (if (string? v) `~v `(problem ~v))))

seancorfield22:09:16

That might get you closer to what you want -- bit will still only work with literals

seancorfield22:09:18

user=> (defmacro problem [x] ;; Macro I cant change
  (let [p# (inc x)]
    `(println ~p#))) ;; Will crash if passed a non number
#'user/problem
user=> (defmacro func [val]
  (let [v val]
    (if (string? v) `~v `(problem ~v))))
#'user/func
user=> (func 4)
5
nil
user=> (func "a")
"a"
user=>

seancorfield22:09:11

But here's the expansion-before-runtime issue:

user=> (let [x 42] (func x))
Unexpected error (ClassCastException) macroexpanding user/problem at (REPL:1:13).
class clojure.lang.Symbol cannot be cast to class java.lang.Number (clojure.lang.Symbol is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')
user=>

seancorfield22:09:01

But you can isolate the only cases letsc accepts and do this:

user=> (defmacro func [val]
  (let [v val]
    (if (number? v) `(problem ~v) `~v)))
#'user/func
user=> (func 4)
5
nil
user=> (func "a")
"a"
user=> (let [x 42] (func x))
42
user=>
Note that you'd need to allow a literal vector with one element containing a number as well.

seancorfield22:09:21

(my let isn't really needed since the ~val expansion only happens once -- either in the truthy branch or the falsey branch)

seancorfield22:09:43

user=> (defmacro func [val]
    (if (number? val) `(problem ~val) `~val))
#'user/func
user=> (func 4)
5
nil
user=> (func "a")
"a"
user=> (let [x 42] (func x))
42
user=> (func (println "Evaluated!"))
Evaluated!
nil
user=>
Hope that helps @U042LKM3WCW?

Dallas Surewood22:09:15

That's helpful, I'm testing some things out and I'll see if I have more questions

Dallas Surewood22:09:33

I'm basically trying to make this work:

(defmacro -superspy [key form state debug_no]
  `(if (contains? ~state ~key)
     (if (nil? ~debug_no)
       (sc.api/spy ~form)
       (sc.api/letsc ~debug_no ~form))
     ~form))

Dallas Surewood22:09:49

everything is good except letsc can't be passed ~debug_no like that, so I'll try to apply what you showed me

seancorfield22:09:06

Macros can be a pain to work with because they don't compose easily like functions.

Dallas Surewood22:09:54

Yes I'm learning that. If I get this working I have a pretty cool workflow for debugging with context automatically recreated, but I'm not sure I can get past this part

seancorfield22:09:46

I use tap> for debugging since it's a) built-in b) can be left in production code and does "nothing" and c) it works great with data visualizers like Portal (or Reveal or REBL).

Dallas Surewood22:09:03

I can look into that. My dream setup was this: Scope-capture will log context each time a function is called. Flow-storm makes visualizing a function call extremely easy, as you can step forwards and backwards. The macro I was making lets you instrument a function with scope-capture and assign a key to it. If that keys is present in some store, it will log stuff in that function. If the key is not present, it will just run the function as normal. You would call it like this (superspy _:request_ (-login request)) This makes it so you can enable and disable "breakpoints" with scope-capture without modifying code. Instead you're modifying a hashmap. I got this part working. But then I wanted to expand my macro. I wanted to make it optionally run the function with an old context that was logged. I was gonna make it so all you had to do was write in the ID of the log to the function call, and then evaluate that form. Like this (superspy _:request_ (-login request) 47) where id is 47. This setup seemed cool to me, cause you can just leave these superspy calls in place, you don't have to mess with parenthesis to make it run in a certain scope, and you could activate something like flowstorm and step through that old context. Sadly that macro didn't work out.

Lone Ranger23:09:18

I second tap> on this one, but also I understand the challenge of trying to get this to work!

Dallas Surewood09:10:48

I ended up fixing this with a modified version of the sc.api/letsc macro. This is an alternative debugging workflow that I don't know if I'll stick with, but it might lead to some other ideas. (superspy (login request-obj)) superspy works a lot like the regular scope-capture/spy. It will log the locally bound variables to an atom (and print an id to the console for each iteration). The only difference is some conveniences for quick debugging. One, superspy calls can be given a key. (superspy :login-route (login request-obj)). Superspy will check a hashmap atom, and if its key is not mapped to a truthy value, then it will just run the form normally instead of instrumenting and logging the scope. This makes it so you can have superspy throughout your code, but turn it on and off optionally through the repl. I do this with something like (user/debug :login-route) Two, and this may not seem ergonomic to people but I find it very useful: it can be passed the id of a previously logged scope. Like this: (superspy (login request-obj) 3). This completely changed the behavior of the macro. If you evaluate the entire expression, it will evaluate the login form with the context of the previously logged scope. Why have this? I find it way easier to drop a number in at the end of a form than to highlight the whole thing, wrap it in parenthesis and call a tracer/logger on it everytime I need to debug something, and I certainly don't want to manually rebind scopes. This makes it so all I have to do is watch the server logs for the scope-capture ID, drop it into the form, and evaluate it. When combining this with Flowstorm, you can log a function once and rerun it as many times as you need with that context, stepping through your entire codebase with flowstorm and changing code along the way if needed. The dream would be to have a way to log all the I/O points of an app, and easily instrument a function with all of the context it had when it was originally run, and step through the codebase with flowstorm

Lone Ranger12:10:57

I’m just curious do you actually mean scope here as in lexical scope/continuation or do you mean calling with the same args as before? Good job on working this out!

Dallas Surewood17:10:26

I actually mean scope, as that's what scope-capture does

Lone Ranger18:10:27

Wow that’s really cool!