Fork me on GitHub
#clojure
<
2023-09-07
>
Sam Ritchie00:09:21

hey all, question about a macro I’m writing. I’m walking the body of the forms passed to the macro and trying to detect the symbol foo.bar/func . I’ve noticed that aliases aren’t expanded inside the defmacro, so if the user has aliased foo.bar to bar, say, then I’ll see bar/func. Does anyone have good prior art for checking if a symbol is either :refered directly or has a namespace that’s aliased? Ideally this would work in cljs AND clj…

phronmophobic00:09:45

Often, if your macro cares about what a symbol resolves to, then you might want to redesign your macro, but there are some good reasons for it. The short answer is that you can use resolve for clojure and https://cljs.github.io/api/cljs.core/resolve for cljs. The shorter answer is it depends. The longer answer depends on a bunch of other stuff. Things get tricker if you eventually end up in other environments like self hosted cljs, sci, graalvm native, etc.

emccue00:09:44

I have some horrible examples somewhere - bug me if i don't get back to this

Sam Ritchie00:09:52

I’m with you that this pattern is a warning sign, but I’ve convinced myself that the domain requirements here force me into this…

Sam Ritchie00:09:30

though I’m still searching for a way around it. basically I want to alter-var-root! but only for calls to foo.bar/func inside the body of the macro. and dynamic binding won’t work here because I want the bindings to only apply “one level deep”, and I can’t figure out a way to say • bind a dynamic variable • if there are ANY FUNCTION CALLS inside this macro, remove the binding for those calls

emccue05:09:29

can you describe more about what the macro would accomplish?

oyakushev06:09:05

@U017QJZ9M7W I can imagine something like this:

(ns foo.bar)

(def ^:dynamic *func-overrides* [])

(defn default-func [args]
  <default-func-implementation-here>)

(defn func [args]
  (if-let [override (peek *func-overrides*)]
    (binding [*func-overrides* (pop *func-overrides*)]
      (override args))
    (default-func args)))

(defmacro call-func-with-one-level-override [override-fn args]
  `(binding [*func-overrides* (conj *func-overrides* ~override-fn)]
     (func ~args)))

Sam Ritchie13:09:19

That is really interesting

Sam Ritchie13:09:57

I’ll write more soon about the actual problem here, it’s in the implementation of a probabilistic programming language, definitely a cool domain

Sam Ritchie13:09:35

okay, so here is the problem as currently laid out. the ideas are: • I am writing programs that internally can make random choices • I have a trace! primitive that makes a random choice, but also stores it in a hidden piece of dynamically bound state • (simulate f args) is like (apply f args), but sets up the hidden state and the machinery to track the random choices, and returns {:trace <map of random choices> :retval <(apply f args)>}

Sam Ritchie13:09:58

Here is trace!, which by default also acts like apply:

(defn no-op [_k f args]
  (apply f args))

(def ^:dynamic *trace* no-op)

(defn trace! [addr f & args]
  (*trace* addr f args))

Sam Ritchie13:09:23

and then simulate dynamically binds the *trace* function to capture its results in a map:

(defn simulate [f args]
  (let [trace (atom {})]
    (binding [*trace* (fn [k f args]
                        (let [v (:retval (simulate f args))]
                          (swap! trace assoc k v)
                          v))]
      (let [retval (apply f args)]
        {:trace @trace
         :retval retval}))))

Sam Ritchie13:09:36

Then we can add this into the pot to turn off tracing:

(defn untraced [f]
  (binding [*trace* no-op]
    (f)))

Sam Ritchie13:09:09

As an example:

(letfn [(f []
          (if (trace! :k rand-int 10)
            1
            2))]
  (simulate f []))
;;=> {:trace {:k 0}, :retval 1}

Sam Ritchie13:09:09

So here is the trouble. The current spec wants to say that if I simply CALL a function inside of f, then tracing should be turned off; a function call should implicitly insert an untraced wrapper:

(letfn [(f []
          (ignore)
          (if (trace! :k rand-int 10)
            1
            2))]
  (simulate f []))
If the user wants tracing, they should have to write:
(letfn [(f []
          (trace! :v ignore)
          (if (trace! :k rand-int 10)
            1
            2))]
  (simulate f []))
(in the original language there is a splice! version which doesn’t introduce another level of nesting)

Sam Ritchie13:09:30

what I can’t figure out is how I can possibly have the call to ignore not trigger any bindings. the way the original code did it, which led to my macro, was that trace! was a no-op, and the instead of dynamically binding, the language used a version of fn called gen that structurally rewrote any references to trace! into calls to *trace*. that’s why I cared what a symbol resolved to within the macro

Al Baker00:09:21

anyone know off hand how to get a ring/jetty response to not do http chunking and just inline the payload?

hiredman00:09:50

I am not sure of the internal mechanics of the ring jetty adapter, but basically if you hand it something as the body that it can't know the length of a head of time (some kind of stream) it has to chunk

hiredman01:09:47

The http protocol essentially requires you to either set a content-length header or use a transfer encoding like chunked. I vaguely recall looking at this, but the details escape me, but somewhere between the ring jetty adapter and jetty, some kinds of bodies it can automatically figure out the length of, and if it can't, you get chunking

jasonjckn04:09:57

The most recent version ring-jetty9-adapter (jetty 12.x) should buffer up to 2GB by default, there's more info about it here https://github.com/sunng87/ring-jetty9-adapter/pull/106#issuecomment-1705491599 @U0NCTKEV8 summed it up.

jasonjckn04:09:07

correction: rather its 2GB by default \w vanilla jetty 12.x, \w with ring-jetty9-adapter the buffer size limit is 16kb by default https://github.com/sunng87/ring-jetty9-adapter/commit/31173a0a4d2459e570d1f25a463f24bd29930af7#diff-e7a14ad839fd5d6b64052aab9c40a7c023ead62def1afecac23199b1c65d7965R92 there's no published jar yet, so just use HEAD / git dependency

tatut08:09:59

anyone else have failing GitHub workflow runs that use DeLaGuardo/setup-clojure action? Mine started failing today (HTTP 404), I’m wondering if this is some github problem

tatut08:09:39

ok, seems version 11.0 works, latest one doesn’t

Alex Miller (Clojure team)09:09:55

I know he was shifting to use the new download location for the CLI, would probably be helpful for you to drop an issue on the project if something not working

tatut09:09:43

I assumed that it would be known as the github checks also fail for that commit

tatut09:09:12

but yes, I’ll make an issue just to be sure it is recorded

mx200010:09:05

Where is a good place to host a development blog ?

tatut10:09:06

github pages? if you just need static markdown rendered as pages

👍 4
igrishaev10:09:35

Jekyll, github pages with a custom domain

Mario G11:09:45

Cryogen if you fancy a Clojure way 😊 https://github.com/cryogen-project/cryogen

👍 4
2
practicalli-johnny13:09:36

http://Practical.li/blog uses cryogen to generate the blog website and GitHub pages to host. PRs are published to a staging site (using a small cryogen customisation) for peer review and commits & PR merges are published live, all via GitHub workflows https://github.com/practicalli/blog

kwladyka15:09:45

github pages + Cryogen

seancorfield15:09:51

Another +1 for Cryogen + GitHub pages. That's what powers http://clojure-doc.org (and http://corfield.org).

Thomas Cherry13:09:47

New here, so sorry if I'm in the wrong channel: Has anyone here used VisualVM to profile clojure? Have you had any success doing this?

p-himik13:09:09

Yes, plenty of people. Do you have any specific questions?

Jason Bullers13:09:06

A post I read a while ago that shows successful usage: https://yogthos.net/posts/2012-08-21-Reflecting-on-performance.html

4
Thomas Cherry13:09:15

1. Do I need to launch my clojure app with any flags to allow VisualVM to connect? 2. Can I find my package names as they are defined in clojure or are they prepending with something.

p-himik13:09:05

1. I don't remember myself but a Clojure app is just a Java app with extra things on top. Whatever you have to do to make VisualVM work with a Java app, you also have to do with your Clojure app. The ways of doing that might differ though and depend on whether you're using Lein or tools.deps 2. Clojure constructs end up being Java classes. Nothing will be prepended but things will get munged. E.g. clojure.core/+ is clojure.core$_PLUS_ on the JVM, and that's how VisualVM will show it

Thomas Cherry13:09:58

so I should be able to find my class if defined as com.mystuff as just that in visualvm

Thomas Cherry13:09:35

also, I have two apps listed as clojure. I'm guessing one is the repl and one is my actual app, is that what other people have seen?

p-himik13:09:39

Note also there are Clojure tools for profiling, such as https://clojure-goes-fast.com/. In particular, https://github.com/clojure-goes-fast/clj-async-profiler. That's the one I use myself most often.

Thomas Cherry13:09:03

I'll take a look at these tools

p-himik13:09:36

> I have two apps listed as clojure Again - depends on your tools, how you use them, and maybe even your code (who knows, maybe you or some libraries are shelling out to clojure)

2
oyakushev13:09:20

If you prefer VisualVM, you can check this article: https://clojure-goes-fast.com/blog/profiling-tool-jvisualvm/

oyakushev13:09:55

Two Clojure apps being shown is most likely because you use Leiningen (it spawns one process for itself and one for your app). The blogpost I linked mentions it

Thomas Cherry13:09:03

that's the second recomendation for clj-async-profiler, so I'm adding it to my ticket for things to look into

Thomas Cherry13:09:16

yea, I think leinigen is one of them, so I'm going to guess it's the one with the lower pid

oyakushev13:09:10

Type jcmd in the terminal and it will show you the command lines of all active Java processes. That will help you distinguish which process is which

❤️ 1
oyakushev13:09:39

> I'm going to guess it's the one with the lower pid (edited) That's a good heuristic but might not work sometimes, OS can reuse pids

2
jpmonettas14:09:17

since most clojure projects look the same at first glance on visualVM I add to each project a jvm option like "-Dproject-name=MyProjectName" so then is easy to just figure out what project you are looking at from the first visualvm tab You can do it on your lein project.clj by adding :jvm-opts ["-Dproject-name=MyProjectName"]

jpmonettas14:09:14

I use VisualVM quite a lot with Clojure but just for Heap monitoring and quick threads and heap dumps, not much for profiling, I find clj-async-profiler much better at it

p-himik14:09:31

@U0739PUFQ If it's only for VisualVM, you can use -Dvisualvm.display.name=FooBar instead - it will display the passed name right in the list of applications.

🤯 2
👏 2