Fork me on GitHub
#clojure
<
2023-01-03
>
roklenarcic11:01:43

I find this weird problem with overloads and Clojure. The two overloads differ in the middle argument type, the last argument type is always String, but unless I add ^String to the last argument, reflection is used here and I get reflection warning

p-himik11:01:32

Your screenshot is not a proof of reflection. It's just your IDE complaining.

roklenarcic11:01:04

Reflection warning, /Users/....test.clj:20:11 - call to method appendReplacement on java.util.regex.Matcher can't be resolved (argument types: java.lang.StringBuilder, unknown).

roklenarcic11:01:15

Here’s a function:

(defn add-replacement [^StringBuilder base ^Matcher m replacement]
  (.appendReplacement m modified replacement))

roklenarcic11:01:19

this will produce a warning

roklenarcic11:01:27

(defn add-replacement [^StringBuilder base ^Matcher m replacement]
  (.appendReplacement m modified ^String replacement))
will not

roklenarcic11:01:56

which makes absolutely no sense, because last argument type doesn’t vary between overloads

p-himik12:01:10

I see. It does make sense, but only if you shift your perspective a bit. As you can see, the actual warning is about not being able to resolve the method - not about not being able to disambiguate between two methods. And that's because the last argument is of type unknown. You haven't specified that it's a string, so it can be anything. And Clojure can't find a method that accepts anything.

p-himik12:01:57

It does it even with just one argument, with just one function with that name:

(set! *warn-on-reflection* true)
=> true
(def x "b")
(.replaceFirst (re-matcher #"." "a") x)
Reflection warning, /tmp/form-init17067666774915114420.clj:2:1 - call to method replaceFirst on java.util.regex.Matcher can't be resolved (argument types: unknown).
=> #'dev/x
=> "b"

p-himik12:01:38

Oh, NVM - there are 2 replaceFirst function. Let me check once more.

p-himik12:01:22

Alright, I stand corrected - there's no warning for .reset.

p-himik12:01:59

Right, judging by the implementation of InstanceMethodExpr, it'll start checking argument types only if there are more than 1 methods with the desired name and amount of arguments.

Alex Miller (Clojure team)15:01:46

if you think there's a bug here, please report it on https://ask.clojure.org

vlaaad15:01:48

How do I safely read a tagged literal with dots in it? e.g.:

(read-string "[1 2 3 (- 1 2) #foo.bar 1]")
=> throws ClassNotFound
And why the way to do it is an undocumented dynamic var? i.e.:
(binding [*suppress-read* true]
  (read-string "[1 2 3 (- 1 2) #foo.bar 1]"))
=> [1 2 3 (- 1 2) #foo.bar 1]
is it safe to use?

2
Alex Miller (Clojure team)16:01:57

why do you have a tagged literal with dots in it?

Alex Miller (Clojure team)16:01:39

the tag is read as a symbol and symbols with dots are assumed to be classes by various parts of the symbol resolvers

Alex Miller (Clojure team)16:01:02

it would be better to use namespaces per normal symbol resolution - #foo/bar

vlaaad16:01:05

It's a problem with prepl where the remote process has a defrecord, so it serializes the record as a tagged map with dots, but the repl process does not have a record definition

Alex Miller (Clojure team)16:01:38

you're sending something on the wire the consumer needs to instantiate so it has to have that

Alex Miller (Clojure team)16:01:59

there is no way to instantiate a record without having the record type

vlaaad16:01:36

I don't want to instantiate a record in the local process, I want to have something

Alex Miller (Clojure team)16:01:02

then the prepl server needs to print records in a way that can be read by any consumer (like as a map)

vlaaad16:01:59

Generally I don't want my users to setup their prepl servers for Reveal to deserialize common data structures like records

Sam Ritchie16:01:54

To be fair, a record is not a “common data structure” - it’s a specific class whose definition must be evaluated to make instances

Sam Ritchie16:01:09

The common data structure version is a map with the same kv pairs as the record

vlaaad16:01:33

It's not uncommon for Clojure users to use records, so I, as a tool author, would prefer that commonly used features work over prepl

Alex Miller (Clojure team)16:01:05

they do ... if the client has the record definition

Sam Ritchie16:01:19

Yeah of course but swap in “classes” for “records” and it’s clear why this couldn’t work in Java proper, right?

Alex Miller (Clojure team)16:01:02

prepl is print/read based. the prepl server needs to print things that clients can read

Alex Miller (Clojure team)16:01:47

records (by default) print in a way that preserves type. types require type definitions to read.

Alex Miller (Clojure team)16:01:03

so you either need to print in a way that does not require type, or provide the type on the client

vlaaad16:01:57

Isn't the point of prepl that you can connect to a remote process that does not necessarily has the same setup and still work with it? E.g. what if a remote prepl process is cljs, while local is clj

vlaaad16:01:07

I know I can instruct the prepl server to serialize records differently, I just don't want my users to have to do it for a tool to work

Sam Ritchie17:01:06

In that case, you still have to stick to the subset of types that both clojurescript and clojure can understand

Sam Ritchie17:01:52

Like you couldn’t send a JS object, right? You’d have to convert it to something the JVM side could read. Same problem with records thst are only present on one side

Alex Miller (Clojure team)17:01:39

We are actually working on some stuff in this area that could help btw

vlaaad17:01:30

Yeah, I can't send a JS object, and that's fine, a #js tagged literal is enough on the JVM side. Glad it's being worked on!

Nundrum20:01:59

Is there some form that works like doseq, but steps over the binding in sync?

(frobsync [x [1 2 3 4]
           y [1 2 3 4]]
  [x y])
=> '([1 1] [2 2] [3 3] [4 4])

Nundrum20:01:31

I get that I can do this with map/mapv but want the ergonomics of the binding form.

dpsutton20:01:37

generally make the pairs you want at the beginning

foo=> (for [[x y] (map vector [1 2 3 4] [1 2 3 4])]
     [x y])
([1 1] [2 2] [3 3] [4 4])

6
2
Nundrum22:01:50

Yeah, that did the trick. Thanks 🙂

Wanishing06:01:07

A bit shorter:

user=> (for [x (range 1 5)] 
            [x x])
([1 1] [2 2] [3 3] [4 4])