Fork me on GitHub
#clojure
<
2024-07-16
>
roklenarcic11:07:26

I’ve been trying to use compojure-swagger, but I always get this error when I try to access the API pages:

java.lang.AbstractMethodError: Method linked/map/LinkedMap.isEmpty()Z is abstract
or on newer Java,
java.lang.AbstractMethodError: Receiver class linked.map.LinkedMap does not define or inherit an implementation of the resolved method 'abstract boolean isEmpty()' of interface java.util.Map.
Does anyone know why this happens?

p-himik11:07:13

What's the stacktrace of the exception?

roklenarcic11:07:43

java.lang.AbstractMethodError: Receiver class linked.map.LinkedMap does not define or inherit an implementation of the resolved method 'abstract boolean isEmpty()' of interface java.util.Map.
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithoutTypeInfo(MapSerializer.java:752)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:720)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:35)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:808)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithoutTypeInfo(MapSerializer.java:764)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:720)
	at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:35)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:479)
	at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:318)
	at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4719)
	at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsBytes(ObjectMapper.java:3987)
	at jsonista.core$write_value_as_bytes.invokeStatic(core.clj:229)
	at jsonista.core$write_value_as_bytes.invoke(core.clj:221)
	at muuntaja.format.json$encoder$reify__101519.encode_to_bytes(json.clj:43)
	at muuntaja.core$create_coder$encode__101918.invoke(core.clj:344)
	at clojure.core$update.invokeStatic(core.clj:6233)
	at clojure.core$update.invoke(core.clj:6223)
	at muuntaja.core$create$_handle_response__101985.invoke(core.clj:443)
	at muuntaja.core$create$reify__101987.format_response(core.clj:486)
	at muuntaja.middleware$wrap_format_response$fn__102058.invoke(middleware.clj:132)
	at muuntaja.middleware$wrap_format_negotiate$fn__102051.invoke(middleware.clj:96)

roklenarcic11:07:07

so it’s trying to serialize a description to JSON

roklenarcic11:07:25

but this type should be serializable and it should extend Map, just as far as sources say

p-himik11:07:23

There's a chance you have some ancient version of, I assume, frankiesardo/linked on your classpath that doesn't implement isEmpty.

roklenarcic11:07:21

yeah you are right, metosin/compojure-api includes both frankiesardo/linked and ikitommi/linked

p-himik11:07:29

Not in their project.clj in the repo.

roklenarcic11:07:20

it includes ikitommi/linked directly and frankiesardo/linked via metosin/ring-swagger

p-himik11:07:40

Ah. Sounds like a good reason to create an issue. :)

roklenarcic11:07:06

I think that the newest alpha solves this by upgrading dependencies

roklenarcic11:07:29

still uses old library

p-himik12:07:40

Yeah, the way a fork of linked was introduced is not ideal. Partially because it makes things harder for pretty much everyone, including the maintainers of compojure-api themselves.

roklenarcic12:07:28

but it’s the same people so why not just change it to the ikitommi version in metosin/ring-swagger , that’s an easy fix

p-himik12:07:02

Because one has to remember why that dependency has been added there in the first place, 6 years ago. :) That's why I said it makes things harder even for the maintainers.

oyakushev19:07:32

Secret multimethod feature they don't want you to know about! When you work with multimethods and get a stacktrace (as an error or in a profiler), it can be hard to figure out which exact method was being called. Sometimes, you have a line number to trace it back to the real method, sometimes you don't. Continued in the thread.

oyakushev19:07:46

Consider this example:

(defmulti countdown (fn [x] (cond (zero? x) :zero 
                                  (odd? x) :odd 
                                  :else :even)))

(defmethod countdown :odd [x] (countdown (dec x)))

(defmethod countdown :even [x] (countdown (dec x)))

(defmethod countdown :zero [x] (throw (Exception. "BOOM")))

(countdown 5)

Exception BOOM
	user/eval5945/fn--5946 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5941/fn--5942 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5949/fn--5950 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5941/fn--5942 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5949/fn--5950 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5941/fn--5942 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
Here, all method implementations have these opaque user/eval5945/fn-- names that don't even hint they are multimethod implementations. But what if I told you that you can name your methods to distinguish them in the stacktrace?
(defmethod countdown :odd I-am-odd! [x] (countdown (dec x)))

(defmethod countdown :even I-am-even! [x] (countdown (dec x)))

(defmethod countdown :zero I-am-zero! [x] (throw (Exception. "BOOM")))

Exception BOOM
	user/eval5965/I-am-zero!--5966 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5957/I-am-odd!--5958 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5961/I-am-even!--5962 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5957/I-am-odd!--5958 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5961/I-am-even!--5962 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
	user/eval5957/I-am-odd!--5958 (NO_SOURCE_FILE:1)
	clojure.lang.MultiFn.invoke (MultiFn.java:229)
See? The stacktrace now clearly distinguishes between different dispatches of the multimethod. WHAT THE HELL??? Figuring out why this works is left as an exercise for the curious reader:bulb:.

🆒 14
exitsandman19:07:48

I imagine the names end up naming the underlying fn

🏆 2
exitsandman19:07:12

named "anonymous" functions are actually a feature I end up using a lot; although lambdas should be kept short and sweet, having an immediate description of what a moderately complex lambda does helps readability a lot in my experience

👍 2
2
1
igrishaev20:07:09

If you check defmethod code, you’ll see that it accepts everything that fn does. Thus, one can pass name as well. I never did that so yes, thank you for sharing this

Lispyyy00:07:20

to be honest, it is still hard to find where it is wrong.🤯