clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Narendra 2021-02-08T12:14:57.315200Z

I am trying to understand the following type

user> (type in-ns)
clojure.lang.RT$1
user> 
I can see that RT.java defines in-ns (`clojure.lang.RT/IN_NS_VAR`) as a Var (with a value equal to Boolean/FALSE?) but where does this RT$1 class come from?

bronsa 2021-02-08T12:23:55.315800Z

that's the naming scheme javac uses for anonymous classes

bronsa 2021-02-08T12:25:16.316300Z

which is assigned to IN_NAMESPACE here https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L319

bronsa 2021-02-08T12:26:49.317100Z

so RT$1 is the name assigned to the first anonymous class inside the RT class

👍 1
Tomas Brejla 2021-02-08T12:31:42.319600Z

Hello. Does anyone here know about some workaround for this issue with clojure compiler https://clojure.atlassian.net/browse/CLJ-1852? It seems to be a cause of this issue in Fulcro I reported a few days ago: https://github.com/wilkerlucio/pathom/issues/187. Perhaps @alexmiller might have some workaround idea?

Narendra 2021-02-08T12:34:41.319900Z

Thanks for the link. I couldn't find this relationship between IN_NAMESPACE and IN_NS_VAR. Now, it makes sense. 🙂

alexmiller 2021-02-08T13:50:38.321700Z

The long name is the combination of nesting and existing names, so using shorter intermediate names and/or nesting less are the two main workarounds

Tomas Brejla 2021-02-08T13:56:18.322600Z

That's what I thought as well. But if I'm looking correctly to that specific problem in compiling of pathom, it doesn't seem to use that-much of a nesting, does it? https://github.com/wilkerlucio/pathom/blob/v2.3.1/src/com/wsscode/pathom/core.cljc#L564

Tomas Brejla 2021-02-08T13:58:27.322900Z

Maybe if one would macro-expand all that, it's actually nested a lot. But at first sight I don't see any obvious "low-hanging fruit" of easily reducing the nesting in that code.

mpenet 2021-02-08T16:10:02.323300Z

isn't the code in question a bit problematic: nested go blocks

mpenet 2021-02-08T16:10:25.323500Z

I suspect that could be one of the reasons why it explodes

2021-02-08T16:29:31.323700Z

@brdloush hard agree with mpenet - the big culprits for massive method size and deep unexpected nesting after macroexpand are go, core.match, and for / doseq - if you nest those things enough you will run into issues

2021-02-08T16:29:50.323900Z

usually it's a question of decomposing into functions to remove that nesting of "verbose" macros

2021-02-08T16:31:29.324100Z

the "low hanging fruit" in your example is to replace the nested go block with a call to a function that makes a go block

2021-02-08T16:33:40.324400Z

re-reading - it's probably actually to move the function definition out of the deep nesting where the current literal is (and factor any captured locals into a hash map arg)

mpenet 2021-02-08T16:41:30.324800Z

I didn't dig deeper but for sure some refactoring might help

erwinrooijakkers 2021-02-08T17:03:01.326500Z

I see this thread, https://clojureverse.org/t/what-about-goto-in-clojure/3943/19, is it possible to create a goto macro in Clojure? Make a label somewhere and use (`goto label)` to continue execution at the label?

2021-02-08T17:10:53.327600Z

in theory, but it would all have to be in one method body for a literal vm goto

2021-02-08T17:11:26.328Z

otherwise you end up needing an interpreter, which clojure intentionally is not

2021-02-08T17:16:23.328700Z

@erwinrooijakkers an option that I don't see in that thread: letfn plus trampoline allows a structured rewrite of any code block that uses goto

👍 1
2021-02-08T17:17:15.329700Z

replace each segment of code between labels with a named fn inside letfn, and replace every fall-through to the next label with returning the function replacing that label

2021-02-08T17:17:32.330100Z

similarly, replace each goto of some label with returning the function replacing that label

2021-02-08T17:17:54.330600Z

it has the same semantics (but is a little slower thanks to function call overhead)

borkdude 2021-02-08T17:23:58.331200Z

There's also an issue in JIRA about loop + recur-to I believe

2021-02-08T17:24:02.331500Z

;; x = 1
;; a: inc x
;; b: if (x > 12) goto :c
;; goto :a
;; c: return x*x
(let [x (volatile! 1)]
  (letfn [(a []
            (vswap! x inc)
            b)
          (b []
            (if (> @x 12)
              c
              a))
          (c []
            (* @x @x))]
    (trampoline a)))

2021-02-08T17:24:24.332Z

it's a tedious way to write code, but if going through Knuth, like the example, the translation is very direct

2021-02-08T17:24:51.332300Z

(returns 169)

2021-02-08T17:26:05.332800Z

(nb. the gotcha with trampoline is you have to wrap a function in a container in order to return it)

2021-02-08T17:30:55.334700Z

a version of trampoline that works on (function, value) tuples instead of just function might be a fun exercise

2021-02-08T17:48:25.337500Z

you could use a continuation monad to get an extremely literal translation of goto containing code https://gist.github.com/hiredman/235655ccde8689499576db882095959f is of what using a continuation monad can look like, it creates labels and uses goto to jump to them.

erwinrooijakkers 2021-02-09T13:40:26.384300Z

I’m going to study this 🙂 never fully understood continuations

2021-02-08T17:49:29.338200Z

with continuations a label is "capture the continuation here" and goto is "replacing the continuation here with this captured one"

2021-02-08T17:53:08.339100Z

@borkdude interesting that your link doesn't mention trampoline - I always thought that letfn (which it does mention) is pretty much pointless outside trampoline

2021-02-08T17:53:38.339600Z

(and trampoline addresses the non-tail-recurring complaint in that post)

Tomas Brejla 2021-02-08T17:59:19.339800Z

Thanks a lot for your ideas, guys. I'll pass this information to the mentioned pathom issue. And if I find some spare time, I'll try to look into the issue myself just to learn what can be done with it.

2021-02-08T17:59:46.340Z

https://gist.github.com/779827c2f1055c5ab080a32bff31656b is the little continuation monad library that example code uses, it is kind of customized around the needs of that particular library, no docs on it of course

Tomas Brejla 2021-02-08T18:00:28.340200Z

it'd be nice if the compiler itself didn't have that issue though 😉

2021-02-08T18:01:18.340400Z

it's intrinsic to the compilation model - fn inside fn is a nested class

2021-02-08T18:01:58.340600Z

since go rewrites things into fns, your nesting can get very deep

2021-02-08T18:03:13.340800Z

fixing this compilation issue (without losing performance) would be one of those "clojure 2.0" things, and I suspect clojure 2 would never happen

alexmiller 2021-02-08T18:04:09.341Z

I don't think it needs to be of that level of significance. it's on the list to think about for 1.11 at least

2021-02-08T18:04:37.341200Z

oh wow - I'm glad if that's the case

p-himik 2021-02-08T18:04:47.341400Z

letfn is useful even with a single recursive function - to avoid having to type its name twice.

alexmiller 2021-02-08T18:05:02.341600Z

I'm not saying it's likely :)

2021-02-08T18:08:13.341900Z

in a way that (fn foo [x] ...) isn't?

2021-02-08T18:08:46.342100Z

(let [foo (fn foo [x] ...)] ...)

2021-02-08T18:08:55.342300Z

is the double name thing

Tomas Brejla 2021-02-08T18:09:15.342500Z

Awesome! I don't want to push my luck, but speaking of list of things to think about.. I wonder whether this is still a possibility? 😇🤞 > Rich and I have been kicking around some interesting ideas on a class compilation cache that would be integrated with core and that research is in scope for Clojure 1.10 but no guarantees on anything. Potentially it would combine the benefits of publishing source-only but the performance of AOT. It is inherently silly to recompile the same lib source file over and over when it’s not changing. https://clojureverse.org/t/deploying-aot-compiled-libraries/2545/10

2021-02-08T18:09:37.342900Z

I disagree with the letfn hate, letfn is great, I prefer it to a regular let binding of functions

alexmiller 2021-02-08T18:13:26.343100Z

you can get 90% of the benefits of that using the approach in https://clojure.org/guides/dev_startup_time right now

👍 1
alexmiller 2021-02-08T18:13:40.343300Z

(and probably fewer of the downsides)

2021-02-08T18:16:08.343500Z

a gist for all seasons https://gist.github.com/hiredman/1179073

p-himik 2021-02-08T18:16:54.343700Z

I do too, but only when there are no non-fn bindings involved, to avoid an extra level of nesting.

2021-02-08T18:33:01.344400Z

sure, creating a cyclic graph is trivial when you mutate stuff

vemv 2021-02-08T18:51:23.348200Z

Are people following the https://clojure.org/guides/dev_startup_time dev model at scale? It worked well for me in a small-medium project (where it shaved some 10s startup time) but in a larger app it would immediately cause issues. Which would not seem surprising when "AOT in dev" is kind of a frowned-upon thing (and a root cause diagnostic that I've seen given in this very channel many times) So I'm a bit conflicted as to what to believe

2021-02-08T18:58:22.349Z

I think that guide undersells the tricky bits of making aot in dev work because it is written by someone with considerable clojure and clojure tooling expertise

👀 1
seancorfield 2021-02-08T19:04:40.352100Z

I have a compile.clj script that, when loaded, will compile several top-level namespaces in our codebase to follow that "AOT in dev" model, but I don't update the compiled classes very often. I haven't noticed any unusual behavior with it but I'm probably not pushing it hard -- it does improve initial load time in dev noticeably but, OTOH, I only restart my REPL maybe once a week so I don't suffer that initial load time very often anyway...

👍 1
vemv 2021-02-09T13:58:10.384500Z

Thanks everyone! I've given it another shot. Here's one specific blocker I have found out. It seems easy to reproduce: https://github.com/clj-commons/potemkin/issues/50#issuecomment-775954692 (I don't like / use Potemkin directly; but that dep can a bit hard to avoid in large/existing projects) *Edit*: might not be specific to Potemkin. Will update that issue after I'm done figuring out stuff

alexmiller 2021-02-09T14:24:41.385900Z

The main reason you won’t see a class is because the class already exists on your classpath

👍 1
alexmiller 2021-02-09T14:25:10.386900Z

Not sure if that’s what you’re seeing but something to rule out first

vemv 2021-02-09T14:27:00.387100Z

Yes, I figured something was requireing potemkin before my 'compile' step So I'm doing

(binding [*compile-files* true]
  (when-not (-> *compile-path* (File. "potemkin") .exists)
    (require 'potemkin :reload-all))
  ;; proceed with other compilation...
  )

vemv 2021-02-08T19:10:14.353100Z

thanks! do a lot of 3rd-party libs get compiled transitively?

seancorfield 2021-02-08T19:11:11.353300Z

Yes 😞

vemv 2021-02-08T19:13:51.353500Z

that's kind of good news as it means that many libs in the wild can survive AOT in dev? :) I think I had problems with two, although I'm not experienced in AOT to actually tell if they were wrong

alexmiller 2021-02-08T19:28:37.353800Z

it would be helpful to file questions on https://ask.clojure.org if you run into them

alexmiller 2021-02-08T19:30:03.354100Z

AOT compile is exactly the same compile as the not AOT compile, so generally all libs should "survive AOT" (because they are being compiled on load)

alexmiller 2021-02-08T19:30:45.354300Z

there can be issues with load ordering, particularly around protocols being reloaded after instances have been loaded

caumond 2021-02-08T19:31:11.354500Z

yes @seancorfield, about repl management, I can say I improved a lot my workflow thanks to your advices. I mostly start my repl for deps.edn update right now ! it has been a learning curve but the final experience is much more "sure", I feel like I am more mastering the state of my code, even during deep refactoring

👏 1
alexmiller 2021-02-08T19:32:07.354700Z

if there are issues that people are running into doing this kind of aot dev env, I would love to be hearing about them so we can assess if there are things to fix/improve

alexmiller 2021-02-08T19:35:11.354900Z

in my experience, using pre-compiled libs is problematic (because that nails down use of a specific clojure version and to some degree fixes compilation order in a way that may be different than use), but using aot in dev is not inherently frowned upon - it is exactly the same work being done to compile at load time, just saved to disk

seancorfield 2021-02-08T20:02:07.355500Z

@vemv FWIW, I just cleaned out my local classes folder and kicked off the compile script and that produces just over 13K .class files.

vlaaad 2021-02-08T20:05:25.357500Z

(:foo (sorted-set 1 2 3)) => ClassCastException (can't cast Long to Keyword)
is there a generic way to check if something has key :foo without exceptions? (contains? (sorted-set 1 2 3) :foo) also throws...

timsgardner 2021-02-08T20:06:08.357900Z

Hey all, old question I'm sure but I had trouble finding an answer: is there a way to intern a new dynamic var? Like (intern (ns-name *ns*) (with-meta 'blorp {:dynamic true})), except it works

grazfather 2021-02-08T20:08:54.358300Z

Cool lisp (not clj) article on lisp shells https://ambrevar.xyz/lisp-repl-shell/index.html

2021-02-08T20:11:13.358800Z

(some #{:foo} (sorted-set 1 2 3))
=> nil
(some #{1} (sorted-set 1 2 3))
=> 1

vlaaad 2021-02-08T20:11:48.359Z

mm no, by generic I mean something that works on ANY object without throwing exceptions

vlaaad 2021-02-08T20:12:16.359200Z

this requires my generic arg to be a collection

timsgardner 2021-02-08T20:13:25.359400Z

You could just filter for coll? first

timsgardner 2021-02-08T20:14:11.359600Z

I guess what's trickier is also avoiding a linear search

vlaaad 2021-02-08T20:14:25.359800Z

I also want this stuff to be fast, e.g. O(1) on maps, not O(N)

p-himik 2021-02-08T20:15:06.360Z

sorted-map cannot be O(1). :)

vlaaad 2021-02-08T20:15:42.360200Z

ehhh

p-himik 2021-02-08T20:15:49.360400Z

(defn safe-contains? [maybe-coll val]
  (try
    (contains? maybe-coll val)
    (catch Throwable _ false)))

vlaaad 2021-02-08T20:15:52.360600Z

I mean reasonobly fast

vlaaad 2021-02-08T20:16:20.360900Z

yeah, probably catching is fine...

2021-02-08T20:17:51.362300Z

hey there, i’m running into an opaque macroexpansion error on clojure 1.10.2 that i did not have on 1.10.1, and i was wondering if any folks had some ideas (details in thread):

2021-02-08T20:18:00.362400Z

here’s a repro repo https://github.com/robhanlon22/clojure-1.10.2-compiler-error-repro

2021-02-08T20:18:28.362700Z

i have a project that depends on specter and methodical and a newer version of riddley than is specified in either of those projects

2021-02-08T20:19:03.362900Z

i’ve tried adding an exclusion for riddley to both methodical and specter’s dependency vectors, but this error persists

2021-02-08T20:19:10.363100Z

the error is specifically this:

2021-02-08T20:19:25.363300Z

{:type clojure.lang.Compiler$CompilerException
   :message Unexpected error macroexpanding com.rpl.specter/path at (com/rpl/specter.cljc:980:9).
   :data #:clojure.error{:phase :macroexpansion, :line 980, :column 9, :source com/rpl/specter.cljc, :symbol com.rpl.specter/path}
   :at [clojure.lang.Compiler macroexpand1 Compiler.java 7023]}
  {:type java.lang.NoClassDefFoundError
   :message clojure/core$seq_QMARK___5406
   :at [riddley.walk$macroexpand$fn__214 invoke walk.clj 14]}
  {:type java.lang.ClassNotFoundException
   :message clojure.core$seq_QMARK___5406
   :at [java.net.URLClassLoader findClass URLClassLoader.java 382]}

vlaaad 2021-02-08T20:19:32.363500Z

thanks folks!

2021-02-08T20:20:55.363800Z

note that lowering the clojure version to 1.10.1 causes the error to go away

2021-02-08T20:21:06.364Z

thanks in advance!

p-himik 2021-02-08T20:23:47.364200Z

Just in case you don't get an answer here - that statement about "working with 1.10.1, not working with 1.10.2" might make it worthy of a question at https://ask.clojure.org/

2021-02-08T20:24:19.364500Z

thanks! i’ll post in there in a bit.

👍 1
alexmiller 2021-02-08T20:35:57.364800Z

there's not that much different in 1.10.2 so that is surprising to me

2021-02-08T20:38:14.365Z

it’s surprising to me too! i just checked, and it actually even manifests on 1.10.2-alpha1

alexmiller 2021-02-08T20:41:01.365200Z

it looks like methodical is a compiled uberjar and includes riddley, potemkin, etc inside it

alexmiller 2021-02-08T20:41:05.365400Z

that's always bad

alexmiller 2021-02-08T20:41:59.365600Z

it's basically a hidden dep conflict in that you end up with two versions of those transitive deps on the classpath, one pre-compiled with a different version of the compiler

2021-02-08T20:42:18.365800Z

ah! no wonder the exclusion had no effect

2021-02-08T20:43:54.366Z

nice catch 🙂

alexmiller 2021-02-08T20:44:28.366200Z

same bug is filed multiple times on the methodical repo

alexmiller 2021-02-08T20:44:35.366400Z

like https://github.com/camsaul/methodical/issues/40

alexmiller 2021-02-08T20:44:39.366700Z

I commented there

2021-02-08T20:45:33.366900Z

thanks so much mr. miller!

alexmiller 2021-02-08T20:46:09.367100Z

https://github.com/camsaul/methodical/issues/33

alexmiller 2021-02-08T20:46:15.367400Z

^^ closer to yours

2021-02-08T21:00:43.367600Z

If excluding potemkin and ridley has no effect, it sounds like some other dependency is including their source or more likely aoted classfiles

2021-02-08T21:00:53.367800Z

Which is often bad news

2021-02-08T21:08:47.368Z

(oops. missed already done I guess)

p-himik 2021-02-08T21:18:09.368200Z

Would it be reasonable to detect and issue a warning when you're trying to exclude something that was uberjar'd?

2021-02-08T21:18:55.368400Z

it is tricky

2021-02-08T21:19:11.368600Z

impossible in the general case, maybe sort of workable in someway?

2021-02-08T21:19:34.368800Z

clojure has kind of two languages of dependencies

2021-02-08T21:20:06.369Z

the language of clojure itself is expressed as clojure namespaces, references to classes by name, etc

2021-02-08T21:20:33.369200Z

but overlayed on top of that is the language of artifact dependencies (maven mostly)

2021-02-08T21:21:09.369400Z

the names in those two languages have no mechanical linkage

2021-02-08T21:21:45.369600Z

and exclusions are expressed in terms of maven names, and compiled clojure classfiles exist in terms of clojure names

p-himik 2021-02-08T21:26:03.369800Z

And it's not possible to robustly get the URL from which a particular ns was loaded, right?

2021-02-08T21:26:06.370Z

something that would give you similar kind of warning, and might be easier to implement, is a utility to look at your dependencies and warn you if any of them contain both classfiles and .clj files

2021-02-08T21:31:02.370200Z

you could maybe download the transitive dependency tree of your exclusions, build a list of namespaces that seem to be defined there, then look through your dependencies and issue a warning if they are defined there

p-himik 2021-02-08T21:33:06.370400Z

Mmm, right, I see. Thanks!

alexmiller 2021-02-08T21:50:23.370600Z

I believe this utility to examine your classpath exists

alexmiller 2021-02-08T21:50:38.370800Z

it certainly exists for java, and I seem to recall someone made a clojure friendly one too

alexmiller 2021-02-08T21:51:13.371Z

but unfortunately I don't remember a link or context for it

borkdude 2021-02-08T21:56:21.371200Z

I think I recently discussed a similar thing with @noisesmith. The way I solved a similar problem was just to do:

(io/resource "clojure/spec/alpha__init.class")
to look for an AOT-ed class of the lib I was expecting the source for and not a class. Then I suggested this could be hooked up to the output of
user=> (binding [clojure.core/*loading-verbosely* true] (require '[clojure.spec.alpha] :reload-all))
(clojure.core/load "/clojure/spec/alpha")
to search in the order of __init.class, .clj and .cljc and report all loaded "files" using the io/resource invocation and parse out the exact mvn versions or gitlib paths to give you an overview of what's loaded from where.

borkdude 2021-02-08T21:56:58.371400Z

(clojure.spec.alpha is just a random example here, that wasn't the lib in question I had a conflict with then)

2021-02-08T21:57:37.371600Z

that kind of analysis is handy (an oracle that tells you which namespaces are defined in which jars), I started writing it the other day (for what feels like the 10th time) because I had the idea that some of our projects at work have unused dependencies, and getting rid of them would cut down on build artifact size (big dumb tree shaking)

borkdude 2021-02-08T21:57:46.371800Z

But maybe clojure itself could emit this information during loading verbosely

2021-02-08T21:58:55.372Z

for our purposes, while it is true that issues like this usually crop up because of aot dependencies, the aot bit really doesn't matter

2021-02-08T21:59:37.372200Z

you want it to be an error/warning when a clojure namespace froms a jar file you don't want it to come from

borkdude 2021-02-08T21:59:52.372400Z

@hiredman there is also tools like this: https://github.com/SevereOverfl0w/vizns (based on static analysis, not on runtime data). This probably won't find the above problem though, since it relies on source for analysis, not AOT-ed stuff.

2021-02-08T22:01:33.372900Z

https://github.com/hiredman/clojure-dependency-grapher 12 years old

2021-02-08T22:03:33.373200Z

but yeah, vizns looks like what I was describing, not just namespace dependencies, but including maven artifact dependencies

2021-02-08T22:24:08.373400Z

so, on a different tack—it appears that methodical is including potemkin (and its dependencies) because of potemkin’s definterface+

2021-02-08T22:24:56.373600Z

it internally defines a java interface

2021-02-08T22:25:25.373800Z

is there a way to work around needing to AOT compile that so the interface is present?

2021-02-08T22:25:33.374Z

or would that require a restructuring

borkdude 2021-02-08T22:26:39.374200Z

That interface would be defined when you load those namespaces, so why is AOT needed?

2021-02-08T22:27:44.374400Z

ah, i see what’s happening

2021-02-08T22:28:13.374600Z

the interface namespace is never :required, it’s only :imported

2021-02-08T22:40:46.374900Z

it seems even somewhat likely that the lack of the :require caused them to aot, so they could import without requiring, the aot caused them issues where some code would use the aot'ed interfaces and some would use the newly created interfaces once the required code was loaded, which caused them to bring in potemkin to definterface+ which seems to have its only feature avoiding re-defining the interface

🎯 1
2021-02-08T23:11:34.375200Z

thank you for the help and great conversation!