Clojure functions are compiled into implementations of clojure.lang.IFn - you can see from https://clojure.github.io/clojure/javadoc/clojure/lang/IFn.h... that this interface simply has a number of overloads of an invoke method taking variable numbers of Object parameters. Since all values can be converted to Object, either directly for reference types or via a boxing conversion, no type checking is required to dispatch a call. With a form like
(some-fn 1, "abc", (Object.))
the some-fn symbol is resolved in the current context (to a Var for functions defined with defn), the result is cast (not checked!) to an instance of IFn and the call to the method with required arity is bound. This can go wrong in multiple ways: the some-fn symbol cannot be resolved, the bound object doesn't implement IFn, the bound IFn doesn't support the number of supplied arguments, the arguments are not of the expected type. Clojure doesn't check any of these, whereas the corresponding Java code would.Protocol methods just get compiled into an implementation of IFn which searches for the implementation to dispatch to based on the runtime type of the first argument, so it doesn't introduce static type checking in any way.
You guys make it out like Clojure is doing something extra to hide Java types, but it doesn’t. What Clojure does is really minimal on top of Java. It barely hides anything.
If you give it type, it will check type. If you don’t give a type, it falls back to a default type, Object, which IS a TYPE. The fact that Clojure compiler cannot deal with GraalVM SVM Pointer type tells you that it’s checking type, because Pointer is not an Object! I found this out the hard way: https://yyhh.org/blog/2021/02/writing-c-code-in-javaclojure-...
“One limitation that one needs to be aware of when writing native image related Clojure code, is that most things in the GraalVM SDK inherit from org.graalvm.word.WordBase, not from java.lang.Object, which breaks the hidden assumption of a lot of Clojure constructs.”
These libraries aren't using protocols for type safety though, they're using them as a performance optimization. That's certainly a perfectly fine reason to use protocols, and I agree that it's a completely legitimate use case. It's the whole right tool for the job thing. If you're writing something where performance is the top concern, then that's what protocols are for.
I very much agree with you that that there are people who focus on low level code, and those who focus on application level code. The style of coding will be different depending on the type of problem you're solving. You're right that I failed to qualify my original statement regarding protocols being an anti-pattern.
You're absolutely correct that we should take a pragmatic approach towards using language features. Hence why the context of whether protocols are the right tool to each for lies in the type of code you're writing. And of course, some people find it easier to have more structure to help with their reasoning. Although, I'd argue tools like Malli work better there.
To sum up, I'm not arguing against protocols being useful or that there's no place for them. We started this discussion talking about whether protocols provide equivalent guarantees to Java's type system. I disagree regarding that. However, I also don't think that this is a real problem. Otherwise, use of something like core.typed would've become prevalent by now.
In any case, it is possible to write Clojure in a way that is type checked at compile time. And it is an acceptable way to write Clojure. I just want to clear the air.