Fork me on GitHub
#clojure
<
2020-10-24
>
fadrian01:10:33

I have a function declared as follows: (defn fn-name [^ClassName c] (.field c)) where ClassName is defined in a previous defrecord in the same package. When I try to run the function, I get a runtime error: ClassCastException pkg.ClassName cannot be cast to pkg.ClassName  pkg/fn-name (...). The ^ClassName appears to be used to prevent reflection warnings about .field, and so seems to need to be there. I don't understand why the function call is trying to convert the parameter when it's already of the declared type. Any idea how to either get rid of the error or how to prevent the reflection warnings without the declaration (because removing the declaration seems to remove the runtime error, but has the reflection warning)?

hiredman01:10:43

If it's a defrecord use keyword lookup

seancorfield01:10:42

@fadrian I can't repro that error:

$ clj
Clojure 1.10.1
user=> (set! *warn-on-reflection* true)
true
user=> (defrecord ClassName [field])
user.ClassName
user=> (defn fn-name [c] (.field c))
Reflection warning, NO_SOURCE_PATH:1:19 - reference to field field can't be resolved.
#'user/fn-name
user=> (defn fn-name [^ClassName c] (.field c))
#'user/fn-name
user=> (fn-name (->ClassName 42))
42
user=>

seancorfield01:10:47

I see the reflection warning (expected). Using the record's class name as a type hint removes the reflection warning (expected). Although as hiredman says, you could just use (:field c) which would be idiomatic.

fadrian02:10:18

Changing the .field to :field worked. Thanks.

valerauko12:10:47

I'm running into a problem with type hinting a constructor. Even with the type hints it complains ctor can't be resolved.

valerauko12:10:58

the constructor is like public A(B firstParam, C... secondParam)

valerauko12:10:03

my call is like (A. ^B b-instance ^C c-instance) but this doesn't work

valerauko12:10:31

i've tried ^[Lfully.qualified.C [c-instance] too but that didn't work either

schmee12:10:04

you might need something like (A. b-instance (into-array C c-instance))

valerauko12:10:58

thanks for the suggestion! i just tried that and it did not resolve the reflection warning. to give concrete sample: i'm trying to instantiate a netty class with this constructor: https://netty.io/4.1/xref/io/netty/handler/codec/http2/Http2ServerUpgradeCodec.html#107 like this:

(Http2ServerUpgradeCodec.
 ^Http2FrameCodec (.build (Http2FrameCodecBuilder/forServer))
 ^ChannelHandler handler)

valerauko12:10:02

it keeps telling me that call to io.netty.handler.codec.http2.Http2ServerUpgradeCodec ctor can't be resolved. and fails to actually work

valerauko12:10:03

sadly it doesn't go into detail why it can't resolve the constructor (listing the argument types)

valerauko12:10:29

the ctor in question is public too

schmee12:10:26

you need to pass (into-array ChannelHandler []) as a second argument

schmee12:10:38

Java interop doesn’t let you ignore varargs

valerauko12:10:22

i've tried both with (into-array ChannelHandler [handler]) and (into-array ChannelHandler handler) and neither worked

schmee12:10:54

so this doesn’t work?

(Http2ServerUpgradeCodec. (.build (Http2FrameCodecBuilder/forServer)) (into-array ChannelHandler [handler]))

valerauko12:10:51

yes it still tells me ctor can't be resolved

schmee13:10:20

then I don’t know

valerauko13:10:08

i tried making a static java method that does the same and it resolves the warning

dharrigan13:10:00

(Http2ServerUpgradeCodec. (.build (Http2FrameCodecBuilder/forServer)) ^"[Lio.netty.channel.ChannelHandler;" (into-array ChannelHandler [nil]))

dharrigan13:10:15

(naturally, replace the nil with your handler :-))

dharrigan13:10:52

(Http2ServerUpgradeCodec. (.build (Http2FrameCodecBuilder/forServer)) ^"[Lio.netty.channel.ChannelHandler;" (into-array ChannelHandler [nil]))
#object[io.netty.handler.codec.http2.Http2ServerUpgradeCodec
        "0x2c6ee031"
        "io.netty.handler.codec.http2.Http2ServerUpgradeCodec@2c6ee031"]

dharrigan13:10:09

user=> (Http2ServerUpgradeCodec. (.build (Http2FrameCodecBuilder/forServer)) (into-array ChannelHandler [nil]))Reflection warning, NO_SOURCE_PATH:1:1 - call to io.netty.handler.codec.http2.Http2ServerUpgradeCodec ctor can't be resolved.
#object[io.netty.handler.codec.http2.Http2ServerUpgradeCodec
        "0x64a552"
        "io.netty.handler.codec.http2.Http2ServerUpgradeCodec@64a552"]

valerauko13:10:47

arrgh was it the ampersand?

dharrigan13:10:59

[Lio.netty.channel.ChannelHandler;"

valerauko13:10:09

i tried that, but without the ampersand

dharrigan13:10:19

you mean semicolon?

dharrigan13:10:32

ampersand = &

valerauko13:10:34

sorry, that. at this point i'm confusing words

dharrigan13:10:48

yeah, missing the semicolon 🙂

valerauko13:10:58

that's just SOOO java lol

valerauko13:10:02

you saved the day, thanks!"

dharrigan13:10:39

you're most welcome, the ; is part of the class format

dharrigan13:10:47

(or rather field descriptors)

valerauko13:10:19

it'd be great if something yelled at me that my type hint is incorrect in that sense

dharrigan13:10:23

That would be handy, but no 🙂

dharrigan13:10:38

4.3.2. Field Descriptors

dharrigan13:10:24

For primitives, it's fine, i.e., ^long, ^double and so on

dharrigan13:10:38

but with an array of objects, i.e., into-array, you have to tell the compiler what's in the array

dharrigan14:10:10

and for that, you need to use that construct, i.e., "[Ljava.lang.String;"

dharrigan14:10:22

I learnt the hard way too 🙂

vncz13:10:10

Is a good idea to run a Clojure application in production by using cli -M -m app.core directly, or shall we go through a jar anyway?

borkdude13:10:03

@vincenz.chianese I don't think it really matters as long as you tweak your JVM settings properly via -J-D. If you do AOT compilation you will have faster startup, if that matters for redeploys.

borkdude13:10:03

also if deps.edn frequently changes, you will have slightly slower startup time due to classpath calculation

vncz13:10:23

@borkdude Thanks for the answer; what JVM settings are you referring to?

borkdude14:10:38

@vincenz.chianese you can look those up via your favorite search engine. Heap memory etc.

vncz14:10:51

Ah ok ok I thought there was something specific for Clojure's startup

borkdude14:10:30

I think in most cases both approaches should work well

borkdude14:10:14

Keeping deps edn invocations up to date on the server and in local dev needs attention sometimes

borkdude14:10:36

(e.g. -A won't invoke main in a couple of months from now)

andy.fingerhut16:10:40

I am not an expert nor experienced in deploying Clojure apps to a fleet of servers in production, but I believe some people that do prefer to create JAR files and then run them on the servers using something like java <jvm-options> foo.jar. One reason: It makes the versions of dependencies that are used more repeatable across all servers. If a new version of a dependency is published after your app is deployed on some servers, but before others, or after you have done your testing, but before deployed to the first server, clj could find the newer version rather than the one you tested with.

p-himik17:10:16

How? Assuming, there are no snapshots and no overwritten artifacts and that you deploy the same deps.edn everywhere.

hiredman17:10:48

Sometimes people do terrible things like publish a new artifact with the same version

hiredman17:10:10

(google actually did this with guava)

hiredman17:10:04

Or tools-deps could have a big that causes into resolve the deps differently on one of your servers

hiredman17:10:30

Basically dependency resolution and fetching is a complicated process, your deploys will be more reliable of it is not part of the deploy

p-himik17:10:47

Ah, that's what I meant by "overwritten artifacts", yeah. Having a bug is also reasonable, thanks.

hiredman17:10:19

I do think deploying uberjars leaves a lot to be desired, in particular uberjars can get really large, and end up containing a lot of the some bytes between deploys

hiredman17:10:58

the two solutions I've looked at are using binary diffs of uberjars and not building uberjars, but copying dependencies over individually, and deploying a prebuilt file containing a classpath pointing to those dependencies

p-himik18:10:37

Have you seen anything promising or even production ready that handles the latter scenario?

hiredman18:10:25

build tool wise? no I have some code I was playing with for it (https://gist.github.com/hiredman/d68cafb6aa8cea563c7b77d54f522421), and I suspect it is more or less how datomic ions handle deploys, but I am only basing that on how rich described it in a talk he gave

hiredman18:10:32

around here https://youtu.be/thpzXjmYyGk?t=3214 is the point in the rhickey talk that makes me think datomic ions are doing something like that

p-himik18:10:23

Oh right - you had this dialog with dominicm before, and I took notes, including your gist and this YT video. :D Dominic also mentioned this thing that doesn't do everything but can at least facilitate such a deployment: https://github.com/juxt/pack.alpha#skinny-jar

andy.fingerhut18:10:59

Doesn't clojure/cli Clojure CLI tools take more recent versions than are in your deps.edn file if they are published, even if you don't use SNAPSHOT versions?

andy.fingerhut18:10:35

If so, as I believe is true, that can happen at times unknown to you, the user of the dependency.

hiredman18:10:07

the take the most recent thing is only if you depend on a given library twice with different versions

andy.fingerhut18:10:23

Hmmmm, I asked Alex a related question once, and I thought he said it does, but I might not have been asking the question properly.

andy.fingerhut18:10:14

So if dep X is used by three different things you depend upon directly, but you do not depend upon X directly, then you should get the minimum version among what those three direct dependencies ask for?

andy.fingerhut18:10:39

I'm sorry, the maximum, but no later, is what I meant to ask.

andy.fingerhut18:10:13

OK, yeah, that is definitely more reproducible than taking a later version than the maximum.

p-himik18:10:22

This talk gives a good description of the classpath building algorithm. The most relevant part starts around 15 minites. https://www.youtube.com/watch?v=7CM7Ef-dPWQ

andy.fingerhut16:10:10

Usually that wouldn't be a problem, but why tempt fate?