This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-25
Channels
- # announcements (16)
- # babashka (110)
- # babashka-sci-dev (11)
- # beginners (50)
- # biff (3)
- # calva (1)
- # clj-commons (19)
- # clj-kondo (1)
- # clojure (17)
- # clojure-art (19)
- # clojure-austin (5)
- # clojure-berlin (2)
- # clojure-denmark (3)
- # clojure-europe (101)
- # clojurescript (84)
- # clr (1)
- # core-async (2)
- # emacs (3)
- # helix (5)
- # honeysql (4)
- # hyperfiddle (8)
- # introduce-yourself (2)
- # jobs (1)
- # lsp (18)
- # membrane (3)
- # reagent (5)
- # releases (3)
- # shadow-cljs (10)
- # tools-deps (24)
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)
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 @U060FKQPNYou can’t use aliases for classes so this shouldn’t be expected to work
That is, this isn’t an alias, it’s a class name
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
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)
You should always go through the var
That is the portable path to construction. The class is an implementation detail designed for interop on the host side
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
(We will fix with-open though)
Maybe that is something that could be listed though, if it’s not already