This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-04-22
Channels
- # announcements (8)
- # babashka (4)
- # beginners (164)
- # calva (17)
- # cider (30)
- # cljdoc (4)
- # cljs-dev (6)
- # clojure (103)
- # clojure-europe (63)
- # clojure-nl (1)
- # clojure-norway (1)
- # clojure-portugal (1)
- # clojure-uk (3)
- # clojured (10)
- # clojuredesign-podcast (2)
- # clojurescript (16)
- # conjure (2)
- # core-async (9)
- # cursive (26)
- # datalevin (4)
- # datomic (156)
- # gratitude (1)
- # holy-lambda (8)
- # honeysql (9)
- # hoplon (6)
- # off-topic (55)
- # polylith (14)
- # portal (21)
- # reagent (5)
- # reitit (16)
- # releases (3)
- # shadow-cljs (87)
- # spacemacs (3)
- # tools-deps (25)
- # xtdb (9)
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 :)Ah, ns-name, that makes sense now it's mentioned. I didn't spot that one. Thank you.
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
I should develop a habit of running apropos more often... There is even a Cider command for it, so no excuses :)
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?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.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.
In .NET, they added the nameof
operator for stuff like that. For example:
void Foo() {
Console.WriteLine($"In {nameof(Foo)}");
}
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.
Wow, .NET is often impressive by its practicality. I'd have used that so often if it was available when I was a user!
> 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.
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. 🙂
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
The wrapping code is here: https://github.com/lambdaschmiede/maja/blob/b1a7c2bf3e8c45373b707d32acbb46059bdb14e3/src/maja/core.clj#L81
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?
Yes, that works. The alter-var-root
replacement happens at runtime, and I guess Cursive uses static analysis for that
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:
(traces spanning multiple threads do not work out of the box, though)
The meta version is interesting, but it means you can't use the function until you've processed the meta on it.
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
.
By the meta version, are you referring specifically to either morphe or the alter-var-root one?
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
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.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.
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.
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
I agree, btw. When working in the REPL it happens that functions „fall out of the tracing“,
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.
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?
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".
Yes, quite exactly. I'm getting excited about it.
I just hate that it calls itself "Aspects", I hate everything that reminds me of AspectJ 😛
Haha! I remember spending a good amount of time learning AOP and then deciding I was actually not going to use it. 🙂
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.
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.
I think I now know what I'll be trying out next week. 🙂
Also, its repo has seen no commits in the last 3 years, which is a smell in the clojure world. Good or bad? 😄
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)))
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.
What is that #=
reader macro?
Didn't know about this one!
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?
Might not work in Clojurescript. Also, it seems the Clojure maintainers kind of regret having added it in the first place.
Good to know. I've already ruled out the #=
solution. 🙂
Thanks a lot for everyone who chimed in (still open for more advice). This has been useful. Have a good week-end!
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:
Wait a sec, is the name of the wrapping function not available in the &form
meta? I know the line number is
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
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.
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.
https://github.com/metosin/malli/blob/0.6.0-SNAPSHOT/src/malli/instrument.clj#L12-L31 for instrumentation.
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
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?
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.
Nothing yet exposed to the wrapper body, but some docstring-handling progress
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
(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 maybeDoes 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"}}}
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
they also point out that they support multigraphs, allowing multiple edges between the same two nodes
Not following, how using adjacency list would block me from switching between different representations @U11BV7MTK
well it won’t necessarily. just pointing out that the abstraction over the concrete representation could help with some of the bookeeping
but i usually use the concrete adjacency list myself. but just mentioning that there are some benefits
are there docs somewhere on how you can pass in metadata to the same map as pre/post conditions for defn
?
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.
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?
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)
The full command that I used:
clj -Sdeps '{:deps {io.xapix/paos {:mvn/version "0.2.5"}} :mvn/repos {"enonic" {:url " "} "sonatype" {:url " "}}}}'
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.
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`?
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)
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.
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
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.
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
> 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.
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.
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
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.
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.
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.
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
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
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.
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
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
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.