clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-05-04T00:50:39.498600Z

A library that I'm publishing on clojars requires some AOT compilation due to :gen-class. I'd ideally like to allow users to have this library as a git dependency too. My question is this: does the Clojure compiler produce bytecode that's dependent on the JVM version it's running on? e.g. if I compile on openjdk11, will I be able to rely on the classfiles generated being able to run on openjdk8?

2021-05-04T00:56:53.498800Z

In https://clojure.org/guides/repl/enhancing_your_repl_workflow#writing-repl-friendly-programs why is print-number-and-wait re definable here:

(future
  (run!
    (fn [i] (print-number-and-wait i))
    (range)))
But not here:
(future
  (run!
    print-number-and-wait
    (range)))

seancorfield 2021-05-04T00:58:47.000100Z

@suskeyhose More to the point, if you compile code against a specific version of Clojure, people using other versions might not be able to use your library.

seancorfield 2021-05-04T01:00:57.001300Z

Because a (fn [..] ..) compiles in a way that will still deref the name to get the current function value, but when passing a function by name the compiler derefs it to get the underlying function value.

👍 1
2021-05-04T01:01:10.001600Z

That's a good point, but an unfortunate requirement. In this case, the actual generated code is exceedingly simple, just a call to = and a couple of keyword lookups on the state of the generated class, which extends java.lang.Error

seancorfield 2021-05-04T01:01:14.001700Z

(it’s a bit more complex than that but that’s the gist of it)

👍 1
2021-05-04T01:01:40.002500Z

Oh and the class implements an interface for a protocol

2021-05-04T01:02:02.002800Z

So I'd imagine it's unlikely to see version conflicts.

2021-05-04T01:03:14.003Z

Not a good idea

2021-05-04T01:04:21.003100Z

Aot compilation works best at the end of the dependency chain (you might aot your final program)

2021-05-04T01:05:05.003200Z

If you start inserting aot in the middle (aoting chunks of libraries) things can go very wrong

2021-05-04T01:07:12.004400Z

Yeah, I've been over this before. The unfortunate reality is that with the current facilities Clojure provides I cannot extend Throwable in a way that I can catch the resulting class without :gen-class

2021-05-04T01:07:14.004500Z

The hazards you have to navigate are largely related to aot compilation being transitive, and order dependent

2021-05-04T01:07:54.005300Z

So aot compiling code will aot compile everything it depends on

2021-05-04T01:08:02.005600Z

There is exactly one namespace that needs aot compilation, and it only needs to create a named subclass of Throwable.

alexmiller 2021-05-04T01:08:03.005700Z

@suskeyhose stay tuned for some new features in the hopefully imminent future

2021-05-04T01:08:14.006Z

Thanks alex! I'm looking forward to it!

2021-05-04T01:08:21.006200Z

Which will, if you package it in a jar, result in something like an uberjar

2021-05-04T01:08:40.006600Z

Which depending on can lead to all kinds of dependency conflicts

2021-05-04T01:08:40.006700Z

Sure, except in this case there are no transitive dependencies of this namespace.

seancorfield 2021-05-04T01:09:05.007600Z

Well, clojure.core is a dependency I bet?

seancorfield 2021-05-04T01:09:35.008400Z

So compiling your single namespace will cause a bunch of compiled .class files to appear I suspect.

2021-05-04T01:09:48.008800Z

depstar allows me to elide all of the clojure.core class files

2021-05-04T01:09:56.009100Z

and that has worked fine when depending on it from clojars.

2021-05-04T01:10:13.009500Z

Eliding class files can be done, but it will also break things sometimes

alexmiller 2021-05-04T01:10:26.010Z

clojure.core is already compiled so will not produce any class files there

seancorfield 2021-05-04T01:10:28.010200Z

Fair enough. I don’t like to encourage AOT for libraries (and I think you get a warning, yes?) but some folks insist…

2021-05-04T01:10:53.010400Z

Yeah, I would love to avoid aot if I could

2021-05-04T01:10:54.010500Z

Because the next time whatever elided clojure code is compiled it may not have the same class name

2021-05-04T01:11:27.011100Z

So your aot compiled code may refer to a class that doesn't exist any more

2021-05-04T01:11:40.011500Z

Yes, it will not produce any clojure.core class files, but I had to elide some clojure.core.specs.alpha class files

2021-05-04T01:12:21.012600Z

Yes, I would love to avoid aot completely, and if you have a way I can extend Throwable and catch the resulting class without aot, I will gladly switch.

alexmiller 2021-05-04T01:12:34.012900Z

ideally, relying on ex-info is better

2021-05-04T01:13:16.014100Z

Unfortunately that's not an option in this case because this is for control-flow constructs and I can't rely on third party code to re-throw the ex-info

2021-05-04T01:13:38.014900Z

For reference, this is for https://github.com/IGJoshua/farolero

seancorfield 2021-05-04T01:13:52.015200Z

@alexmiller If you compile a very simple namespace, even though you don’t get classes from clojure.core, you do get classes from core.specs.alpha in your classes folder.

2021-05-04T01:14:05.015500Z

Yup, I had to elide those

alexmiller 2021-05-04T01:14:40.015600Z

that is true

seancorfield 2021-05-04T01:15:28.016800Z

Since @alexmiller is around: what about the OP’s Q which we haven’t answered yet… “if I compile on openjdk11, will I be able to rely on the classfiles generated being able to run on openjdk8?”

alexmiller 2021-05-04T01:15:51.017300Z

should be, they are java 8 bytecode

alexmiller 2021-05-04T01:16:11.018100Z

whether or not you make calls to jdk apis added after java 8 requires more discipline

seancorfield 2021-05-04T01:16:13.018200Z

Cool. I had a feeling I’d seen that as an answer to another question here recently.

2021-05-04T01:16:14.018300Z

Awesome, that helps out a lot.

2021-05-04T01:16:44.019200Z

Yeah, I'm not calling any JDK api, all I'm doing is extending java.lang.Error with a named class that has a protcol implementation that checks if the argument is equal to an item in the state.

alexmiller 2021-05-04T01:17:28.020100Z

there is a lot of fud around aot. it's not conceptually any different than all java libs that "aot compile". there are some issues for sure - we may take on making those issues less of a problem.

2021-05-04T01:17:37.020300Z

Although now that I think about it, I could potentially reduce how much is AOTed by using extend-protocol in the other namespace.

2021-05-04T01:17:56.020700Z

rather than directly extending the interface

seancorfield 2021-05-04T01:18:35.021500Z

“fixing” the transitive compilation stuff would probably be the #1 thing folks seem to want (although there are tooling workarounds) — but I understand that there are complexities behind that.

seancorfield 2021-05-04T01:20:24.022800Z

Making it easy to compile some thing(s) at the point of use, i.e., via a command that runs code, would also probably satisfy what most folks actually need 🙂

2021-05-04T01:21:08.023200Z

Yeah, that or a way to extend classes while generating a named class is all I need

2021-05-04T01:21:17.023400Z

in order to completely remove aot from the equation

alexmiller 2021-05-04T01:23:23.024Z

all of those things are up for consideration

dpsutton 2021-05-04T01:23:57.025200Z

On mobile, but would it be possible to put your error class as a stand alone maven project and just depend on that from your clojure project?

dpsutton 2021-05-04T01:24:15.026200Z

Then no aot and can use git deps and anything else you like

2021-05-04T01:24:38.026600Z

Hmm. Except for the ns form, I think I could actually entirely remove the dependency on clojure.core

2021-05-04T01:25:25.026900Z

That's actually a good question @dpsutton

2021-05-04T01:25:50.027800Z

I'll consider that. It depends on what gets generated once I pare down what's actually in the aot-ed part.

2021-05-04T01:26:18.028300Z

I think I can get everything that's there down to special forms.

dpsutton 2021-05-04T01:31:28.029100Z

I think two jobs ago we did something like this and just compiled it once and stuck it on the class path.

dpsutton 2021-05-04T01:32:02.030200Z

Checked in and everything if I remember correctly

2021-05-04T01:33:37.031Z

Alright, well after getting it down to just special forms and an ns clause it's still a few too many class files. I think I might just make a java artifact that I can put out on clojars.

2021-05-04T01:33:46.031200Z

Thanks for the suggestion @dpsutton!

emccue 2021-05-04T03:00:13.031700Z

@suskeyhose one thing that i think might be worth investigating is just using https://bytebuddy.net/#/

emccue 2021-05-04T03:00:48.032200Z

making your class and loading it into the right classloader

2021-05-04T03:01:30.032900Z

I think that adds more overhead than just making an additional jar with the single class file in it, and it certainly increases the runtime overhead for users of my library.

2021-05-04T03:53:40.035400Z

Clojure has a censored version of asm

2021-05-04T03:53:45.035500Z

Vendored

2021-05-04T03:56:48.035600Z

If you are willing you can do all kinds of stuff with it https://git.sr.ht/~hiredman/reagents/tree/master/item/src/com/manigfeald/reagents/struct.clj

2021-05-04T03:57:45.035700Z

(oh, I forgot that is such a heinous arrow)

2021-05-04T09:31:47.039800Z

I’ve just adopted using component(https://github.com/stuartsierra/component) in my project, and I’d like to know how others think about the experience. I haven’t checked, but there seem to be quite a few alternatives like integrant and mount. It might be because I’m inexperienced, but restructuring a non-component project (which was made of many global singleton defs and defns) into a component project wasn’t easy..

imre 2021-05-04T09:42:29.040Z

I like that using these libs seem to direct code organisation towards a more decoupled way.

imre 2021-05-04T09:42:49.040200Z

I've worked with all 3 and currently prefer integrant then component, lastly mount

p-himik 2021-05-04T09:42:50.040400Z

> restructuring [...] wasn't reasy Seems like it's one of the reasons behind https://github.com/juxt/clip since it can work with regular functions. Personally, I use Integrant. Wasn't that hard given that I had to just add a bunch of multimethods only for the lifecycle parts that I was interested in.

imre 2021-05-04T09:44:24.040700Z

What I like in integrant is that system structure and component config are in the same source data structure

imre 2021-05-04T09:44:31.040900Z

At least in my experience

ordnungswidrig 2021-05-04T09:46:04.042700Z

I prefer stuartsierra/component for the simplicity of the model. In the ends it's a topological sort and a function to map over your graph. I used the generic mapping in the past to implement "system wide" functions, other than start and stop.

imre 2021-05-04T09:46:17.043200Z

Is anyone aware of a utility lib that has a hybrid let/`letfn` macro? I often want to use letfn but end up just going with a let as I also need let-like bindings in the same scope and don't like the idea of adding yet another level of indentation. I'm looking for something where you can mix let and letfn style bindings in the same binding vector

p-himik 2021-05-04T09:46:35.043300Z

To add to what imre said - it can be separated with e.g. Aero. That's exactly what juxt/yada uses, and sometimes it makes sense - in my case, because I'm using shadow-cljs and I want to pull some of the config from the shadow-cljs.edn file.

dharrigan 2021-05-04T09:48:59.043500Z

I use juxt clip, across a variety of applications. Works really well.

dharrigan 2021-05-04T09:49:55.043700Z

Similar concepts as others (there is a small comparison here: https://github.com/juxt/clip#comparison-with-other-libraries)

flowthing 2021-05-04T09:50:29.044Z

I've used every library mentioned so far apart from Component, and I also prefer Clip. The only downside is that it is not (yet) possible to start/stop a partial system.

flowthing 2021-05-04T09:52:44.044200Z

What I don't like about Mount and Integrant is that both conflate namespaces with components. I also found Clip pretty easy to grok, compared to e.g. Integrant. YMMV, of course.

borkdude 2021-05-04T10:03:58.045Z

I hardly use letfn, I just can't remember the syntax. Imo letfn is useful for mutually recursive fns, but in other cases, I would just use let

👆 5
Jim Newton 2021-05-04T10:48:29.046500Z

my code is usually littered with letfn , and I’ve also often wished to mix let with letfn but not often enough to write the macro.

Jim Newton 2021-05-04T10:51:00.048600Z

@imre what would be the clojure ideomatic way to distinguish the two types of bindings? For example, I very well might want to bind a variable to a vector. would it be the case that a list of 3 or more components whose 2nd element is a vector is necessarily an attempt to bind a function? and the macro could simply insert the (fn …) around it? or would you want something more exotic?

imre 2021-05-04T10:52:49.048800Z

the elements of a letfn binding vector are list s

imre 2021-05-04T10:53:11.049200Z

destructuring works off vectors and maps

imre 2021-05-04T10:54:14.049400Z

so if the next thing you see in the binding vector is a list, that would be your letfn thing

imre 2021-05-04T10:55:30.049700Z

It's a pretty simple and convenient syntax actually if you only need to let fns

imre 2021-05-04T10:55:46.049900Z

(letfn [(foo [] 1)] (foo))

borkdude 2021-05-04T10:56:21.050700Z

yeah, once I look it up it makes sense, but I just can't remember it

Darin Douglass 2021-05-04T10:57:01.051600Z

We’ve recently found redelay and have been really happy with how simply it deals with state. https://github.com/aroemers/redelay

imre 2021-05-04T10:58:23.051900Z

(let+ [bar           4
       (foo [x y z] [x y z])
       {:keys [baz]} {:baz 2}
       [_ _ qux]     [0 0 3 4]]
  (foo bar baz qux))

imre 2021-05-04T10:59:11.052100Z

I prefer it a lot to using let when it's possible as it's a lot more concise

NoahTheDuke 2021-05-04T11:12:11.053400Z

@tlonist.sang I just did the same but with integrant. It’s a lot of up front work but when you get into the “reloaded workflow”, it makes things so much smoother.

Ben Sless 2021-05-04T11:13:28.053600Z

Took me some time to "get" component. I think my problem was Component seemed too ad-hoc and using them was often left up to the implementation. Looking at https://github.com/juxt/aero#using-aero-with-components I landed at a usage mode I liked: Very generic components. Implement interfaces on them, including IFn. Take "constructor" functions as arguments. Constructor functions take the entire component as argument and destructure the keys they require out of it. Because of how component works and how the aero config got merged in, the component's this contains all of its dependencies and options. And finally, don't pass components as arguments to functions which aren't constructors. It creates code which looks a bit weird, but very easy and pleasant to work with.

NoahTheDuke 2021-05-04T12:29:16.055500Z

Is it possible to test macro expansion? I have a branch in my macro that throws an error when given incorrect code. I’d like to write a test to demonstrate that. Is that possible?

mpenet 2021-05-04T12:30:43.055900Z

yes with macroexpand, macroexpand-1 or clojure.walk/macroexpand-all

NoahTheDuke 2021-05-04T12:35:03.058300Z

I’ve only ever used that at the repl. They don’t return strings, they return the actual code?

ghadi 2021-05-04T12:35:33.058600Z

correct

ghadi 2021-05-04T12:36:01.059100Z

macroexpand takes a form and returns a form

NoahTheDuke 2021-05-04T12:38:33.059400Z

Cool, I’ll give that a try

2021-05-04T13:31:37.059500Z

Thanks for all the comments. When using component, I felt its way of coding makes the whole structure more nested than I originally thought. For instance to track down where the datasource is made -whether it be that of jdbc or from hikari congig - I needed to go to the definition of record database first, and then to its realiing function, and finally to where it actually gets injected. In the meantime it was quite verbose, with its structure code and business code mingled together.

2021-05-04T13:32:28.059700Z

But I believe the directive of component is a truly needed one. Will look more into it with other suggestions, thanks!

NoahTheDuke 2021-05-04T13:33:10.059900Z

yeah, it can make things feel much more interwoven/complex, but it also reveals how interconnected things already were through globals.

👍 1
wontheone1 2021-05-04T14:46:12.061100Z

Great thread! @flowthing I have a question specifically for you. > The only downside is that it is not (yet) possible to start/stop a partial system. Usually the way we can test apps using these libraries is not starting real HTTP web server and replace it with a handler functions and so on. If you can’t start/stop a partial system, how do you test them? Do you start whole dependencies and then mock all HTTP calls or such? Please share how you test a Clip app, very interested.

emccue 2021-05-04T15:24:17.061300Z

@imre Let me take a stab at it - untested

imre 2021-05-04T15:24:54.061500Z

😄

emccue 2021-05-04T15:31:10.061700Z

(defmacro let% [clauses]
  (loop [clauses clauses
         final-bindings []]
    (if (empty? clauses)
      `(let ~final-bindings)
      (if (list? (first clauses))
        (recur (rest clauses) 
               (conj final-bindings 
                     [(first (first clauses))
                      (cons 'fn (rest (first clauses)))]))
        (if (>= (count clauses) 2)
          (recur (rest (rest clauses))
                 (-> final-bindings
                     (conj (first clauses))
                     (conj (second clauses))))
          (throw (RuntimeException. "Not enough clauses in let")))))))
        

emccue 2021-05-04T15:31:13.061900Z

give this a shot

emccue 2021-05-04T15:31:55.062200Z

it won't let you do mutally recursive stuff like letfn but its somethign

imre 2021-05-04T15:37:50.062400Z

nice one 🙂

imre 2021-05-04T15:37:59.062600Z

I'll give this a go

Milan Munzar 2021-05-04T15:57:44.064600Z

Coming from ClojureScript I was suprised that defrecord behaves differently in both:

(defrecord Service [send-query])

(defn make-send-query []
  (fn [foo] foo))

(let [s (->Service (make-send-query))]
  (.send-query s "foo"))
; ^ Throws in Clojure (no matching method), but works in Cljs
How to do this in Clojure (using Protocols?), why doesn't it work the same?

alexmiller 2021-05-04T15:58:45.065200Z

you should not be using interop syntax to invoke

alexmiller 2021-05-04T15:59:27.065700Z

record fields should be accessed like maps

Milan Munzar 2021-05-04T15:59:50.066Z

I see thx :thumbsup:

alexmiller 2021-05-04T15:59:54.066200Z

in both clj and cljs

imre 2021-05-04T16:00:03.066400Z

(defmacro let% [clauses & body]
  (loop [clauses clauses
         final-bindings []]
    (if (empty? clauses)
      `(let ~final-bindings ~@body)
      (let [[fc & rcs] clauses]
        (if (list? fc)
          (recur rcs
                 (-> final-bindings
                     (conj (first fc))
                     (conj (cons `fn fc))))
          (if (>= (count clauses) 2)
            (recur (rest rcs)
                   (-> final-bindings
                       (conj fc)
                       (conj (second clauses))))
            (throw (RuntimeException. "Not enough clauses in let%"))))))))

imre 2021-05-04T16:00:17.066600Z

did a bit of fixing & refactor on it

imre 2021-05-04T16:00:38.067100Z

(let% [bar           1
       (foo [x y z] [x y z])
       {:keys [baz]} {:baz 2}
       [_ _ qux]     [0 0 3 4]]

      (foo bar baz qux))

;; => [1 2 3]

Milan Munzar 2021-05-04T16:00:43.067500Z

so this would be a proper use for Protocols I guess

alexmiller 2021-05-04T16:00:47.067700Z

((:send-query s) "foo") is I think what you're doing?

alexmiller 2021-05-04T16:00:59.068100Z

you can certainly do the equivalent with protocols too

alexmiller 2021-05-04T16:01:38.068800Z

(protocols are mostly just implemented as maps of types to functions)

Milan Munzar 2021-05-04T16:01:41.068900Z

yeah that works in both

Milan Munzar 2021-05-04T16:02:06.069300Z

thanks!

borkdude 2021-05-04T16:03:19.070100Z

The lower level syntax for accessing these fields is (.-send-query s "foo") which works in both, but it's more preferable to access those via either keywords or protocol fns (in the case of deftype)

borkdude 2021-05-04T16:03:41.070600Z

That (.send-query s "foo") works in CLJS might be just accidental since the . is quite overloaded in CLJS

imre 2021-05-04T16:06:30.070900Z

mutual recursion could be tough 🙂

Jim Newton 2021-05-04T16:14:36.072600Z

does anyone know how I can contact Sam Ritchie ?

borkdude 2021-05-04T16:15:38.073300Z

@jimka.issy #sicmutils

1
Helins 2021-05-04T16:16:56.074100Z

Through generative testing I found this behavior which I find surprising. In the context of maps and sets, [] and (list) are equivalent:

{[] :vec, (list) :list}  ;; Throw, duplicate keys

(hash-map [] :vec (list) :list)  ;; Super weird => {[] :list}

;; And similarly with sets

borkdude 2021-05-04T16:18:31.074500Z

In CLJS (at least, planck) {[] :vec, (list) :list} doesn't throw though, it's something only the JVM Clojure seems to do

p-himik 2021-05-04T16:20:05.075500Z

They have the same hash for some reason and are equal in CLJ.

Helins 2021-05-04T16:20:39.076400Z

But Lumo (CLJS) returns {'() :list} , which is inconsistent with Clojure JVM

alexmiller 2021-05-04T16:20:48.076700Z

sequential collections compare equal. equal things have the same hash.

2021-05-04T16:21:01.077Z

user=> (= [] (list))
true

p-himik 2021-05-04T16:21:29.077200Z

Ah, right, makes sense.

alexmiller 2021-05-04T16:21:55.078300Z

hash-map docstring explains why different than literal (... "as if by repeated uses of assoc")

dpsutton 2021-05-04T16:22:01.078600Z

in clj -A:cljs -M -m cljs.main -r {[] :vec, (list) :list} returns {[] :list}

borkdude 2021-05-04T16:22:33.079Z

$ bb -e '{[] :vec, (list) :list}'
{[] :list}
;)

Helins 2021-05-04T16:24:42.080700Z

Right, so this is not specific to empty colls then, it is indeed about hashing. It has never bothered me with = , but after years of Clojuring, I kind of freaked out seeing that behavior with keys :p

dpsutton 2021-05-04T16:26:38.081200Z

it's just a subtle difference in the behavior when the key is already present and a new value is assoced.

👆 2
flowthing 2021-05-04T16:29:29.081400Z

I rarely mock anything. The one application where I've had the opportunity to use Clip I just start the entire system and test against that. Might not be a viable option with larger applications, of course (although you might be able to split it into several systems, not sure).

👍 1
imre 2021-05-04T16:33:42.082500Z

@emccue I saved that for later 🙂 https://gist.github.com/imrekoszo/5e5c007659264729fad26481206c2691

raspasov 2021-05-04T19:31:36.084700Z

Yeah, I try to stick to [] (vectors as opposed to lists/seqs) nowadays for everything which is data but definitely not code in the “final” output; eliminates this kind of “worry”

raspasov 2021-05-04T19:35:06.086Z

Unless I specifically want to keep something as a, say, list, so I can keep adding to the front; That’s rare.

hipster coder 2021-05-04T22:58:25.088300Z

@alexmiller I watched your video on Clojure Spec from about 1 year ago. Just wanted to say thank you for all the hard work you have done leading the community. It has kept me interested in using Clojure. Spec is an amazing tool. And you picked the perfect images for your talk. Of the bird.

nate sire 2021-05-04T22:59:49.088500Z

thank you

1
2021-05-05T14:23:07.098500Z

link?

2021-05-05T14:24:02.098700Z

this one? https://www.youtube.com/watch?v=Xb0UhDeHzBM

nate sire 2021-05-04T23:17:54.088800Z

I was just seconding what hipster said

nate sire 2021-05-04T23:18:02.089Z

I saw the video too