Fork me on GitHub
#clojure
<
2023-02-25
>
borkdude11:02:08

It's Hyrum's law time! In this snippet, I show that (alias/Rec.) works but only under some circumstances (it depends on Rec being imported). Would you say that (user/Rec.) is undefined behavior or somehow supported? And why does it even work under some circumstances but not all? Here is a Github issue where this came up: https://github.com/babashka/babashka/issues/1502#issuecomment-1444699760

user=> (defrecord Rec [])
user.Rec
user=> (ns foo)
nil
foo=> (alias 'u 'user)
nil
foo=> (u/Rec.)
Syntax error (IllegalArgumentException) compiling new at (REPL:1:1).
Unable to resolve classname: Rec
foo=> (user/Rec.)
Syntax error (IllegalArgumentException) compiling new at (REPL:1:1).
Unable to resolve classname: Rec
foo=> (import 'user.Rec)
user.Rec
foo=> (user/Rec.)
#user.Rec{}
foo=> (Rec.) ;; without prefix
#user.Rec{}
foo=> user/Rec
Syntax error compiling at (REPL:0:0).
No such var: user/Rec
foo=> (u/Rec.)
#user.Rec{}
foo=> (macroexpand '(u/Rec.))
(new Rec)

borkdude11:02:12

It seems tools.analyzer doesn't even care about the alias being present in the namespace or not, unlike Clojure:

foo=> (j/analyze '(x/Rec.))
{:args [], :children [:class :args], :op :new, :env {:context :ctx/expr, :locals {}, :ns foo, :column 13, :line 1, :file "NO_SOURCE_PATH"}, :o-tag user.Rec, :class {:op :const, :env {:context :ctx/expr, :locals {}, :ns foo, :column 13, :line 1, :file "NO_SOURCE_PATH"}, :type :class, :literal? true, :val user.Rec, :form Rec, :o-tag java.lang.Class, :tag java.lang.Class}, :top-level true, :form (new Rec), :tag user.Rec, :validated? true, :raw-forms ((x/Rec.))}
cc @U060FKQPN

Alex Miller (Clojure team)13:02:42

You can’t use aliases for classes so this shouldn’t be expected to work

borkdude13:02:19

Perfect, thanks

Alex Miller (Clojure team)13:02:56

That is, this isn’t an alias, it’s a class name

Alex Miller (Clojure team)13:02:22

I think some of the weirdness is that class name resolution does not expect a namespace part to the symbol and in some cases just ignores it

borkdude13:02:31

This aligns with how I see it and both clojure and babashka supported it (bb doesn't anymore) but I wanted to know if it was intentional on clojure's side. It seems the answer is no Other points: • you can leave out the alias because you imported the class, it's weird to include that alias I'd say. • When constructing a record, you might as well go through the var: (alias/->Rec)

Alex Miller (Clojure team)13:02:05

You should always go through the var

Alex Miller (Clojure team)13:02:48

That is the portable path to construction. The class is an implementation detail designed for interop on the host side

👍 2
Alex Miller (Clojure team)13:02:21

I would say we should make the analyzer throw on a class name with a namespace, except there are a lot of sloppy macros in the wild that use class names as symbols without ~’ that expand to namespaced class names. So adding a throw would invalidate a lot of existing compiled code. I believe with-open is one example like this

Alex Miller (Clojure team)13:02:20

(We will fix with-open though)

Alex Miller (Clojure team)13:02:20

Maybe that is something that could be listed though, if it’s not already

borkdude13:02:37

The above issue did not originate from a macro but I have encountered similar cases, especially with protocol implementations.

borkdude13:02:03

I'm not sure how you could ever end up with an aliased class name in a macro though, without starting with one to begin with