Fork me on GitHub
#clojure
<
2022-04-22
>
practicalli-johnny14:04:36

Is there a way to use *ns* with a require function? Why: - to avoid typing in the current namespace name when reloading the namespace via require (I admit this is probably the wrong kind of lazy) I can evaluate this require function and see what other namespaces that namespace also loads

(require '[practicalli.playground] :reload :verbose)
But I assume either *ns* is not producing the right data type for require or the dynamic binding is not resolved in a workable order. So this fails:
(require *ns* :reload :verbose)
I've tried casting the result of *ns* to symbol or var and wrapping it in a vector and quote, but then its getting simpler to just type the namespace name. Just curious if I have missed something obvious or am off on a journey to a different planet :)

dpsutton15:04:29

user=> (require (ns-name *ns*) :reload :verbose)
(clojure.core/load "/user")
nil

👌 1
practicalli-johnny22:04:02

Ah, ns-name, that makes sense now it's mentioned. I didn't spot that one. Thank you.

dpsutton22:04:44

i’ll periodically run (apropos "ns-") to remind myself of those

clojure.core/ns-aliases
 clojure.core/ns-imports
 clojure.core/ns-interns
 clojure.core/ns-map
 clojure.core/ns-name
 clojure.core/ns-publics
 clojure.core/ns-refers
 clojure.core/ns-resolve
 clojure.core/ns-unalias
 clojure.core/ns-unmap

👍 1
practicalli-johnny22:04:01

I should develop a habit of running apropos more often... There is even a Cider command for it, so no excuses :)

dpsutton22:04:28

the queryable run time is the hidden gem of Clojure in my opinion

Daniel Jomphe17:04:09

Don't you too have an itch like me to create a macro for error-prone cases like the following, where I must repeat the function name inside the body, and do so manually?

(defn do-something []
  (log/with-try {:op "do-sometypohing"}   ; typo!
    :result))

(defn do-something-else-refactored []     ; renamed
  (log/with-try {:op "do-something-else"} ; forgot!
    :result))
Wouldn't a e.g. defnamed macro introducing a fname anaphoric variable be magical?
(defnamed do-something []
  (log/with-try {:op (name fname)} ; ="do-something"
    :result))
So the question might be... in your experience, when is this a bad idea?

1
Daniel Jomphe17:04:18

I might start doing things like this in our codebase:

(defn do-something []
  (otel/trace-span {:name "do-something"
    (log/with-try {:op "do-something"}
      :result)))
If I do that, the fn name repetition becomes laughable. Or, obviously, we could decide to instead augment log/with-try with options for automatically doing the trace-span functionality, reusing its :op name.

p-himik17:04:58

Sounds awfully similar to aspect-oriented programming where your aspects need to be aware of the context. I think https://github.com/galdre/morphe would be a good fit here, although I myself have never used it.

isak17:04:57

In .NET, they added the nameof operator for stuff like that. For example:

void Foo() {
	Console.WriteLine($"In {nameof(Foo)}");
}

Daniel Jomphe17:04:43

Thanks for the morphe link, I'll read it thoroughly. Nice to see it being quite lightweight. I see we'd need to remove CLJS from its main deps but it's a good starting point. I've also seen a lib that helps create defn-like macros easily. Don't remember its name out of my head though.

Daniel Jomphe17:04:26

Wow, .NET is often impressive by its practicality. I'd have used that so often if it was available when I was a user!

p-himik17:04:20

> I see we'd need to remove CLJS from its main deps but it's a good starting point. Fortunately, it's easy to do with :exclusions in deps.edn - you don't actually need to change morphe itself.

Daniel Jomphe17:04:00

Thanks for the reminder about exclusions. Morphe's docs is really useful to https://github.com/galdre/morphe/blob/master/docs/clojure-idioms.md. Typically I don't like how AOP isolates too much cross-cutting concerns (i.e. they're not always that cross-cutting). Anywhere in a fn body, I might want to be making calls where I might want to refer to the fn name. And/or log and trace conditionally midway. But anyone could answer: use AOP for truly cross-cutting concerns, and make it call simple utils that can also be called midway through a function body, and maybe also have the anaphoric variable for the function name, and you should be covered for all cases. 🙂

1
javahippie18:04:15

We do something similar like this for OTel, but we add {:traced true} metadata to the function definition and wrap the function with a tracing function on initialization. Doing it had not bit us, yet

Daniel Jomphe18:04:34

Wow thanks Tim, I wondered how easy/hard it could be to go this route. What about your IDE, does it still see the docstring and arities such that autocomplete and doc lookup follow through?

javahippie18:04:11

Yes, that works. The alter-var-root replacement happens at runtime, and I guess Cursive uses static analysis for that

javahippie18:04:56

Entry point to the service is HTTP, so we use a ring middleware to initiate the trace, and then all annotated functions appear in the trace nested:

javahippie18:04:34

(traces spanning multiple threads do not work out of the box, though)

didibus18:04:50

Can you?

(let [fn-name 'foo]
`(defn ~fn-name
   []
   (println "My name is: " ~fn-name)))

didibus18:04:56

Hum, I guess not, you'd then have to eval it again.

🙂 1
didibus18:04:15

(eval
 (let [fn-name 'foo]
  `(defn ~fn-name
       []
       (println "My name is: " '~fn-name))))

didibus18:04:31

But at that point it's too hairy, so ya, I guess you could make yourself a macro

didibus18:04:45

The only downside of a custom defn macro with that feature is tooling

didibus18:04:31

The meta version is interesting, but it means you can't use the function until you've processed the meta on it.

Daniel Jomphe18:04:43

Haha! I must say morphe is very impressive (not sure yet if it will evolve automatically to support clojure 1.11's new kw args) and also @U0N9SJHCH's solution based on dynamic alter-var-root.

Daniel Jomphe18:04:03

By the meta version, are you referring specifically to either morphe or the alter-var-root one?

didibus18:04:12

Ya, they both basically redefine the function after it already has been defined, so define it once with meta, than redefine it again based on the meta

didibus18:04:41

So if you go:

(defn foo {:aspect :foo} [] ...)

(foo)

(apply-aspects)
The call to foo prior to the aspects being applied will call the function without the aspects on them.

Daniel Jomphe18:04:38

Ok so you're saying it's a disadvantage that until they're redefined with their augmentations, if we call them we'll have their unaugmented behavior? That would certainly be true of the alter-var-root based solution. One must not forget to reapply the redefiner. Oh, and for macro-based solutions, I suppose you'd say the same thing: if you alter the macro, remember to re-eval all functions using it if you want them to use the new macro's output.

didibus18:04:39

Now if you don't do that, and your namespace load is only defns or delayed defs, it should be fine. But still, at the REPL it might get confusing, you'd need to always remember to reload the namespace, not just individual defs.

didibus18:04:41

Hum... no I think the macro approach doesn't have this problem. Since they all edit the Var, captured reference to the Var will work fine. It is a timing thing. At the time of evaluation the defn form, the augmentations are not yet applied. So between that time and the time when you augment, if you execute anything that uses the function, you don't get the augmentations

javahippie18:04:19

I agree, btw. When working in the REPL it happens that functions „fall out of the tracing“,

Daniel Jomphe18:04:57

Confirming morphe's macro-and-metadata-based implementation, there's no separate augment-them-now utility. IOW when the m/defn macro is evaled, it reads the form's metadata and immediately augments the defined function. The approach seems very REPL-sane, then.

didibus18:04:17

Oh, I see, so morphe is a custom defn that let's you like extend it by adding more body transformers in a kind of middleware pattern?

didibus18:04:00

Ya, that's not bad actually. I thought about doing something like that before. Like have a custom defn that lets you define customer defns within its contract so they can "compose".

Daniel Jomphe18:04:10

Yes, quite exactly. I'm getting excited about it.

didibus18:04:28

I just hate that it calls itself "Aspects", I hate everything that reminds me of AspectJ 😛

Daniel Jomphe18:04:06

Haha! I remember spending a good amount of time learning AOP and then deciding I was actually not going to use it. 🙂

Daniel Jomphe18:04:23

Reading these docs highlighted here, I found out the author spent a lot of time exploring the problem space. He spoke of most kinds of solutions I have thought until now, except the one based on alter-var-root. And I think he's the author of the other lib I was referring to earlier.

Daniel Jomphe18:04:47

Its code is small, seems well maintained and readable. The approach of forking clojure.core/defn seems reasonable and maintainable, especially following its changes in 1.11.

Daniel Jomphe18:04:22

I think I now know what I'll be trying out next week. 🙂

didibus18:04:50

Ya, if its small, you could maintain it yourself

Daniel Jomphe18:04:04

Also, its repo has seen no commits in the last 3 years, which is a smell in the clojure world. Good or bad? 😄

didibus18:04:37

Also, I might bring upon me the fury of the Clojure gods... but:

(def fn-name 'foo)
(defn #=(eval fn-name)
   []
   (println "My name is: " '#=(eval fn-name)))

didibus18:04:06

Well, normally it's rare that a Clojure lib breaks. But in the case of something that takes over defn, you need to keep it up to date with the features of defn if you want those as well, but I don't think it would break, just maybe wouldn't support the newer defn features.

Daniel Jomphe18:04:12

What is that #= reader macro?

didibus18:04:41

Ya reader macro, it expands at read-time, which is before macro-time

Daniel Jomphe18:04:52

Didn't know about this one!

Daniel Jomphe19:04:36

Hi @UGGG3G07K in case you'd be the author of https://github.com/galdre/morphe, this would be a request for your comments. May I ask if you're still happy with morphe?

didibus19:04:52

Might not work in Clojurescript. Also, it seems the Clojure maintainers kind of regret having added it in the first place.

Daniel Jomphe19:04:43

Good to know. I've already ruled out the #= solution. 🙂

didibus19:04:07

Ya, I would not advise doing it haha. I think morphe seems like a nice approach.

Daniel Jomphe19:04:39

Thanks a lot for everyone who chimed in (still open for more advice). This has been useful. Have a good week-end!

Benjamin C20:04:35

so this whole thread has resurfaced a idea-fragment I've had bouncing around my head for awhile: What if this problem could be generalized into an editor tool? What if it could be done something like a snippet, but one that can be ran as a test? Think live-snippet, but one that could be updated with a later change, regardless of what caused it. This might not really be feasible, and it might have a lot of annoying problems that it causes, (like false positives identifying a "snippet-generated" peace of code, perhaps), but I think if it's doable it might be nice to not need to introduce macros for this sort of thing. Or maybe it's a really bad idea altogether. 🙃 🤷:skin-tone-2:

Max13:04:08

Wait a sec, is the name of the wrapping function not available in the &form meta? I know the line number is

Max13:04:16

Then all you'd have to do is use a macro instead of a fn. That's how all the logging frameworks do it Oh and you'd probably want to let the macro set :op rather than doing it yourself

Daniel Jomphe17:04:35

Thanks Max (coming back late to this), yes it's easy to get the name in a macro and use it in the generated code. My question was about exposing a let-bound variable to be used by users of the macro (an anaphoric variable). It's a bit unhygienic in macro-writing to do this, but could be quite fine.

Daniel Jomphe17:04:12

Coming back from the hammock, I remembered that Clojure.spec (and spec2 alpha) https://github.com/clojure/spec-alpha2/blob/2a0a7c49c86e31b528be857ed004a4931a0c2551/src/main/clojure/clojure/spec_alpha2/test.clj#L161-L175`alter-var-root`https://github.com/clojure/spec-alpha2/blob/2a0a7c49c86e31b528be857ed004a4931a0c2551/src/main/clojure/clojure/spec_alpha2/test.clj#L161-L175, not the AOP nor the macro path. And clojure.spec is deemed REPL-friendly! So alter-var-root is not fundamentally anti-REPL at all. 🙂 There must be quite a few tradeoffs taken into account there. And we can always write fns/macros on top of that to sweeten up common, repetitive usage patterns.

didibus18:04:49

That's just AOP no?

didibus18:04:37

Like in your case, you need to have the user tag the functions somehow, using meta or some other way, and then you want a function you can call that finds all tagged functions and alter-var-roots them to be re-written

didibus18:04:49

Its even more complex in your case, because you will need like a placeholder for the name the first time the function is compiled no?

Daniel Jomphe18:04:53

Yes, all suggestions are a form of either direct AOP or effective AOP. For the first compilation, it's true that either a placeholder is required, or a def-like macro.

Daniel Jomphe18:04:31

Nothing yet exposed to the wrapper body, but some docstring-handling progress

didibus19:04:01

I'm not even too sure how you'd go the alter-var-root way. I think you would need a dynamic var, and then alter-var-root it in a binding that binds it to the name

didibus19:04:52

(def ^:dynamic fn-name)

(defn foo [] (println "inside: " fn-name)

(defn wrap-fn [fn-sym]
  (alter-var-root! fn-sym (fn[f] (binding [fn-name (get-fn-name f)] f))

(wrap-fn `foo)
Something like that maybe

Ivan Fedorov19:04:24

Does Clojure community have a favorite graph notation? Looking for something that allows arbitrary nodes / edges attributes Looked into https://github.com/Engelberg/ubergraph, seems a bit ad-hoc, but yes, allows nodes and edges with attributes. I’m thinking about something more orderly e.g.

{:graph/nodes #{{:node/id :node1, :node/title "Node 1"}}
 :graph/edges #{{:edge/id :n1n2, :edge/src :node1, :edge/dst :node2, :edge/color "red"}}}

dpsutton20:04:21

seems this representation is just an adjacency list. The benefit of using something like ubergraph where things are behind a matrix is that you can switch between different representations. One example is that they have a directed graph constructor and presumably a bidirectional version. That would maintain the invariants that you would have to put in your representation there

dpsutton20:04:02

they also point out that they support multigraphs, allowing multiple edges between the same two nodes

Ivan Fedorov20:04:36

Not following, how using adjacency list would block me from switching between different representations @U11BV7MTK

dpsutton20:04:13

well it won’t necessarily. just pointing out that the abstraction over the concrete representation could help with some of the bookeeping

dpsutton20:04:24

(and transparently changing between a sparse matrix or adjacency list, etc)

dpsutton20:04:07

but i usually use the concrete adjacency list myself. but just mentioning that there are some benefits

❤️ 1
lilactown19:04:49

are there docs somewhere on how you can pass in metadata to the same map as pre/post conditions for defn?

🥲 1
lilactown19:04:15

I'm curious if the metadata is simply merged with the metadata on the name symbol

lilactown19:04:40

or if it's metadata on the function itself

p-himik21:04:21

Don't know of the docs, but there's REPL and the implementation. :) Pre/post don't end up as metadata anywhere - and it makes sense because a function can have multiple arities, each with their own conditions. But you can set pre/post conditions using metadata on the arguments vector. No clue why - maybe it's useful for macros that write functions.

stephenmhopper21:04:51

Can someone that has knowledge of the inner workings of clojure.data.xml https://github.com/xapix-io/paos/issues/24 I’ve created and comment about whether or not I’m on the right track?

p-himik22:04:26

Huh, I can't reproduce because I can't get the paos dependency at all. Or rather, one of its dependencies:

Error building classpath. Could not transfer artifact org.reficio:soap-legacy:jar:1.0.0-20181009.115351-4 from/to clojars (): status code: 416, reason phrase: Range Not Satisfiable (416)

p-himik22:04:41

The full command that I used:

clj -Sdeps '{:deps {io.xapix/paos {:mvn/version "0.2.5"}} :mvn/repos {"enonic" {:url ""} "sonatype" {:url ""}}}}'

p-himik22:04:05

And when I excluded org.reficio/soap-legacy, I wasn't able to reproduce your issue. So either it's something local or something in the dependencies or the JAR of that library.

p-himik22:04:27

That library depends on https://mvnrepository.com/artifact/javax.xml/jsr173/1.0 which is now included in the JRE itself. Maybe its functionality overrides what exists in JRE/JDK, and it has different implementation. Can you try excluding it in your project.clj/`deps.edn`?

stephenmhopper00:04:33

Thanks for checking, but I still encounter the issue even when I exclude that jsr173 JAR. Also, if I exclude org.reficio/soap-legacy I still see the same issue (except of course all of the soap calls fail because of a classloader issue)

p-himik15:04:50

Sounds bizarre. Maybe its due to some specific combination of not only the dependencies but also some other factors. Specifically, I was trying with JDK 14 on Ubuntu. I'd try excluding all dependencies of paos one by one - eventually, you should hit a point where the behavior is not reproducible.

stephenmhopper18:04:53

Thanks for trying again. I managed to figure it out. Basically, clojure.data.xml calls (TransformerFactory/newInstance) when creating indented XML. Behind the scenes, the factory checks a few different places when trying to decide which impl it should return. By default, you get .apache.xalan.internal.xsltc.trax.TransformerFactoryImpl which prints the CDATA entities as-is. paos pulls in saxon which has net.sf.saxon.TransformerFactoryImpl and expects that instance to be used instead.

stephenmhopper18:04:18

For the sake of my application, I can likely just do this: (System/setProperty "javax.xml.transform.TransformerFactory" "net.sf.saxon.TransformerFactoryImpl") and call it a day. But it does mean that if there are any other places in my app’s JVM where a specific TransformerFactory impl is required, I have limited options on ensuring the proper impl is used. Thanks, Java

p-himik18:04:01

> Thanks, Java That's not really a Java problem thought, I think. More of a problem of the whole "check what's on classpath and use that" approach instead of asking a user to configure things manually. Another instance of "simple vs easy" IMO.

Nundrum22:04:12

I don't know if EOD Friday is the best or worst time to ask. Let's find out 😉 What's a good approach to doing a single-page web app? Which libraries should I consider? It's intended to be a sort of interactive graph viewer. I've already got a prototype REST server with endpoints for it to pull from.

p-himik22:04:42

Lots of libraries and approaches, hard to say which is "best" without knowing what you desire. And in the end, to each their own. For example, right now I'm using this setup for a couple of apps: • Yada for a web server (although I'll switch from it at some point as it has problems and isn't actively maintained anymore) • Integrant to tie all the backend components in a cohesive system (might switch to Clip at some point or something else, we'll see) • Sente for WebSocket communication • Reagent for UI • Re-frame for UI state management

👍 1
Nundrum22:04:37

I need to interact with a graph, kind of like what D3 can do with it's network graph. Enter some text. Make some pop ups. I don't think it will be terribly complex. Which I feel like complexity with web apps is unavoidable.

p-himik22:04:43

What you describe is doable on any stack. You can use D3 just fine, you can use HTML <input> fields or React with e.g. MUI or Bootstrap or re-com or... You get the idea.

djblue00:04:33

I think vega might be a good fit for graph visualizations, https://vega.github.io/vega/examples/force-directed-layout/ 👌 However, not sure how customizable the interactions are.

👍 1
kwladyka16:04:25

try #clojurescript for ClojureScript things

xceno19:04:36

I built a graph viewer and editor with fulcro and AntV G6: https://g6.antv.vision/en Once you've chosen a graph visualization tool, it's just a matter of plumbing it into whichever frontend lib you prefer

Nundrum19:04:00

Thanks! I'll check it out.

Nundrum16:05:58

Why this vs say, D3 or p5.js?

xceno21:05:23

No particular reason that would still be relevant. At the time I was searching for a library the G6 examples stood out. But today I'd probably do it with D3. On the non-graph side: Depending on how complex your app will be you could probably do it entirely in D3/p5,/whatever and plain cljs. I chose fulcro because I'm building a pretty complex app and it's a great fit

Nundrum02:05:13

I did a prototype in plain JS and D3. That was good enough for the graph, but not for the rest of the interaction. I've been looking at Fulcro, but I think it is very heavyweight for what I'm trying to do.

xceno08:05:44

Yeah, fair enough. Then I'd say go for a similar stack to the one @U2FRKM4TW has, so reagent and maybe re-frame if you need it. Use it together with D3 and flesh out your existing server aYeah, fair enough. Then I'd say go for

stephenmhopper18:04:53

Thanks for trying again. I managed to figure it out. Basically, clojure.data.xml calls (TransformerFactory/newInstance) when creating indented XML. Behind the scenes, the factory checks a few different places when trying to decide which impl it should return. By default, you get .apache.xalan.internal.xsltc.trax.TransformerFactoryImpl which prints the CDATA entities as-is. paos pulls in saxon which has net.sf.saxon.TransformerFactoryImpl and expects that instance to be used instead.