Fork me on GitHub
#clojure
<
2023-09-12
>
igrishaev13:09:17

A "case + int + reflection warning" question. This code triggers a warning although the type hint is set:

(let [^int result (get-some-result)]
  (case result
    0 :foo
    1 :bar))
;; case has int tests, but tested expression is not primitive.
But this code is OK:
(let [result (get-some-result)]
  (case (int result)
    0 :foo
    1 :bar))
I've been using (int case-expression) for years but now I'd like to know does it work so?

igrishaev13:09:53

A more general question is, that where should I put a type hint to avoid coercing to (int ...) explicitly?

rolt13:09:15

(defn get-some-results ^long [] 1) ?

Alex Miller (Clojure team)13:09:37

0 and 1 are longs here, not ints

igrishaev13:09:12

ah, right! thank you Alex!

Alex Miller (Clojure team)13:09:44

but not sure if your warning was from clojure or linter?

igrishaev13:09:05

it's from Clojure when I reload a namespace

igrishaev13:09:49

Performance warning, /home/foo/Bar/xxx/api/src/model/foo.clj:77:11 - case has int tests, but tested expression is not primitive.

igrishaev13:09:42

but yes, ^long solves the problem

vemv13:09:02

> (defn get-some-results ^long [] 1) Fairly sure that a defn cannot return a primitive type - primitives don't survive across clojure function call boundaries Likewise you cannot simply:

(let [^long result (get-some-result)])
...you should instead write:
(let [result (long (get-some-result))])
Doing otherwise may silence warnings, which doesn't necessairly mean they're effectively addressed :) I recommend giving https://clojure.org/reference/java_interop#primitives a careful look

igrishaev13:09:51

Well, this works fine:

(defn get-foo ^long [] 42)

(let [result (get-foo)]
    (case result
      1 :boo
      2 :baa
      42 :lol))

:lol

rolt13:09:00

Functions have limited support for primitive arguments and return type: type hints for long and double (only these) generate primitive-typed overloads. Note that this capability is restricted to functions of arity no greater than 4.

โœ… 1
Alex Miller (Clojure team)13:09:01

> Fairly sure that a defn cannot return a primitive type it does for ^long or ^double

vemv13:09:29

TIL then!

igrishaev13:09:44

I believe, here is a list of all the possible types combination: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/IFn.java#L97

Alex Miller (Clojure team)13:09:21

yes, all combinations of ^long or ^double up to arity 4

pesterhazy13:09:51

It seems that folders in the classpath aren't 100% dynamic. Is this correct? When a folder doesn't exist at startup, it doesn't seem to make it into the classpath even if specified in deps.edn. This tripped me up because the target folder is often in .gitignore and doesn't exist until the first process startup ๐Ÿงต

pesterhazy13:09:16

Here's the surprising (to me!) repl session. This is running in an empty folder:

pesterhazy13:09:18

~/temp$ % clj -Sdeps '{:paths ["src" "target"]}'
Clojure 1.11.1
user=> (slurp ( "file.txt"))
Execution error (IllegalArgumentException) at user/eval1 (REPL:1).
Cannot open <nil> as a Reader.
user=> (clojure.java.shell/sh "mkdir" "-p" "target")
{:exit 0, :out "", :err ""}
user=> (spit "target/file.txt" "hi")
nil
user=> (slurp ( "file.txt"))
Execution error (IllegalArgumentException) at user/eval7 (REPL:1).
Cannot open <nil> as a Reader.
user=>
~/temp$ % clj -Sdeps '{:paths ["src" "target"]}'
Clojure 1.11.1
user=> (slurp ( "file.txt"))
"hi"

pesterhazy13:09:58

As you can see, resource doesn't find file.txt until I restart the clojure process. I'm wondering if this is a java or tools.deps behavior, and if people have found a good workaround for "non-existent (at startup) folders containing resources"

teodorlu13:09:03

not really an answer to the question, but I sometimes use a target/.gitkeep file to avoid errors like this. Then I check it in with git add -f target/.gitkeep && git commit.

๐Ÿ‘ 2
practicalli-johnny14:09:42

I believe it's a JVM thing. Adding class files to an existing classpath is relatively dynamic on the JVM. Adding new paths to a running JVM is often requires a custom class loader (similar to web servers like tomcat). The easiest approach is to create the necessary directories before running the repl (or restart the repl after adding a new path). Not sure if the new hot loading tools on Clojure 1.12 support paths. I think hot loading jars is more amenable that paths in the JVM (but it's been a decade since I last did that sort of thing)

๐Ÿ‘ 2
Samuel Ludwig18:09:12

Is there a way for me to expose an external library function with its docstring/multiple clauses/etc in my own namespace? For example, I'd like to transparently wrap something like:

(ns external.funny-math)
(defn funny-addition
  "This adds some numbers, most of the time"
  ([x] x)
  ([x y] (+ x y))
  ([x y z] "no addition here, try more arguments")
  ([x y z & ns] (apply + x y z ns)))
I could do
(ns internal.maths
  (:require [external.funny-math :as fm]))

(def funny-addition fm/funny-addition)
which works from a functionality standpoint when I call internal.maths/funny-addition, but this doesn't let me naturally view the docstring/arities that I normally would be able to if I inspected the external library function itself

Joshua Suskalo19:09:58

You can do something like this:

(def funny-addition fm/funny-addition)
(alter-var-meta #'funny-addition #(merge % (select-keys (meta #'fm/funny-addition) [:doc :arglists])))

Joshua Suskalo19:09:40

The library potemkin does this for you, although I believe it's usually considered unidiomatic these days, mostly because it means that any kind of "go to definition" functionality will give you confusing results.

Samuel Ludwig19:09:33

ahhhh very interesting, appreciate the inputs! maybe it'd just be preferred to copy over the :arglists and :doc, I think thats all I'd want to "forward" over

Samuel Ludwig19:09:48

and that shouldn't mess up the go-to-defs

vemv11:09:43

In case it helps, cider-nrepl latest detects the (def funny-addition fm/funny-addition) pattern and reflects all relevant metadata, in a non-intrusive way (nothing is mutated). If you use other tooling, it's a sensible feature to request.

๐Ÿš€ 1
genmeblog15:09:58

What about :refer in :require?

Joshua Suskalo15:09:35

that does not add the var to ns-publics of the current ns which means it's unavailable to consumers of the referring namespace.