Failed to import record names

You need

(:import [clojure_rte.bdd Bdd])
1 Like

I changed my ns definition to the following, and now Bdd evaluates at the repl.

;; this is WRONG, see post below
(ns clojure-rte.bdd-test
  (:import [clojure_rte.bdd Bdd])
  (:require [clojure-rte.bdd :refer :all ]
            [clojure.pprint :refer [cl-format]]
            [clojure.test :refer :all]))

See the corrected code below.

In general, you should stick to the auto-generated constructor names and just require the namespace containing the record definition ->Bdd and map->Bdd. That way you can avoid the Java implementation details bleeding into other namespaces (that it is a class name and needs to be import'd).

@seancorfield I’m curious how would you spec a function that expects a record of a certain type then ?
I guess I could use a spec that doesn’t care if it’s a record or not and specs the map behind but if I really want to be sure it’s an instance of a certain record, I didn’t find a way without importing the related java class … Any recommandations on this ? Is this bad practice ?

1 Like

It’s hard to remember exactly which things worked and which failed, but during debugging I tried lots of combinations. At some places I wanted to assert the type Bdd, especially in transit when trying to pinpoint the function which was misbehaving. In some cases the parser complains that Bdd is not a recognized var. (assert (instance? Bdd my-value)). It is bizarre to me that what seems like an innocent symbol is not well behaved.

Actually i discovered that this ns declaration is wrong. I get an error when running tests from lein at the shell.

[geminiani:~/Repos/clojure-rte] jimka% lein test
Exception in thread "main" Syntax error compiling at (clojure_rte/bdd_test.clj:1:1).
	at clojure.lang.Compiler.load(Compiler.java:7647)
	at clojure.lang.RT.loadResourceScript(RT.java:381)
	at clojure.lang.RT.loadResourceScript(RT.java:372)
	at clojure.lang.RT.load(RT.java:463)
	at clojure.lang.RT.load(RT.java:428)
	at clojure.core$load$fn__6824.invoke(core.clj:6126)
	at clojure.core$load.invokeStatic(core.clj:6125)
...
Caused by: java.lang.ClassNotFoundException: clojure_rte.bdd.Bdd
	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
	at clojure.lang.DynamicClassLoader.findClass(DynamicClassLoader.java:69)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
...

The problem seems to be that we cannot :import Bdd before :require clojure-rte.bdd. I.e. first :require the package that defines it, then :import the class name.

I’ve changed the name space declaration to the following and the file seems to load properly.

(ns clojure-rte.bdd-test
  (:require [clojure-rte.bdd :refer :all ]
            [clojure.pprint :refer [cl-format]]
            [clojure.test :refer :all])
  ;; this imports the name of the Bdd record, which is otherwise not imported by :require
  (:import [clojure_rte.bdd Bdd]))

Yup. Requiring the namespace causes it to be loaded and compiled, and it that process that defines the record type, and only after that can you reference the class type (you can reference it using the fully-qualified class name if you want – import just creates a shorthand alias for it, so you can use it without the package name).

I think that’s over-specification. I would prefer to spec the keys of the map that the function requires (especially when you look at what is coming in Spec 2).

1 Like

I do wonder what the motivation here is. Why isn’t the class name included by [clojure-rte.bdd :refer :all] ? Was that just a bug in the original implementation of defrecord and how must be maintained for backward compatibility in a less-that-perfect world? Or was it intentional? To me it seems strange if intentional that all symbols except record names be exported?

When I take a look at the clojure.org/reference documentation, I see the following sentence is two locations: the defrecord name must be fully qualified. What does this mean? It seems to be stated a bit vague. For example it is not necessary to fully qualify it in all cases, case in point, its declaration. An example in the documentation at that point would be really helpful.

Also what does it mean that a name be fully qualified? I’m not 100% comfortable with the namespace naming concept especially in the case of projects with lots of levels of hierarchy? For the moment my project hierarchy is pretty simple.

defrecord generates a Java class – that is just like any other Java class you use in Clojure: once it is on the classpath, you can refer to it directly via its fully-qualified name, e.g., java.util.Date, java.time.Instant, clojure_rte.bdd.Bdd.

defrecord also generates two Clojure functions that act as constructors and which are the preferred way to create instances of the record (class): ->RecordName and map->RecordName. Since these are regular Clojure functions, you gain access to them via require – just like any other Clojure function you use.

What import does – all it does – is to allow you to refer to a Java class name by just the class name without the package name:

(import '(java.util Date))

(def now (Date.)) ; can use unqualified class as a shorthand for java.util.Date

(import '(clojure_rte.bdd Bdd))

(instance? Bdd now) ; can use unqualified class as a shorthand for clojure_rte.Bdd

There’s nothing “magical” about defrecord. When it is executed (i.e., when the namespace containing it is loaded and compiled), it generates a Java class and two Clojure functions. Everything else about it falls out of that – consistently with how Clojure deals with Java classes and Clojure functions in namespaces.

Good explanation. Additional question. when can I use (instance? Date my-object) ?
I’ve referring to a conversation on clojurians here.
If I import one of my namespaces, using :as dfa, what does the syntax dfa/xyzzy mean?
What is confusing is that if a record named Dfa has been created in that ns, then I cannot reference it with dfa/Dfa.

Java classes and Clojure functions are two separate things. A record is a Java class (with two generated Clojure functions). That is the entire explanation of why you can’t do what you’re trying to do. I just read over the Slack conversation and it’s the exact same thing I said in my post above: you’re expecting Java classes to work like namespaces or like Clojure functions and they are two different things!

(instance? java.util.Date my-object) ; will work without import

(instance? Date my-object) ; will work only after you import java.util Date

(instance? clojure_rte.bdd.Bdd my-object) ; will work after the record has been defined**

(instance? Bdd my-object) ; will work only after you import clojure_rte.bdd Bdd

** The record is defined when its namespace is loaded/compiled, which means that namespace has to be require'd somewhere before you use the class – the qualified name is available everywhere after something has require'd that namespace; the shorthand is available wherever you’ve import'd the package/class name, which in turn depends on the namespace having been require'd somewhere prior.

dfa/Dfa does not work because you are trying to mix two separate things: a namespace (alias dfa) and a Java class (Dfa). require doesn’t affect Java class access (beyond actually causing the defrecord to create the class). import doesn’t depend on namespaces – it’s only about Java class names.

Thanks for the enlightening and patient explanation, but there’s still something mysterious. One the 1 hand, perhaps it is not necessary to really understand at my state of development, but rather just form good habits. But on the other hand this mystery is troubling.

Part of your explanation, @seancorfield, is confusing to me because Dfa is not a java class (as you claim), it is a symbol used as an identifier to denote a java class, in the same way that my-object is not a function, but rather a symbol used as an identifier which designates some value. The namespace (as I understand) does not manage objects and functions and classes, but it only manages symbols. Am I right, or am I confused? The name spaces just manage which symbols can be abbreviated with short names. But the name spaces does NOT know about which values symbols have.

Does the namespace treat symbols differently depending on what type their value is? The explanation you gave above seems to claim that symbols whose values are java classes are treated differently in the namespace than symbols whose values are function or strings or numbers. This is indeed curious to me because the symbol enters the namespace after the parser encounters it, well before the evaluator determines the value of the symbol in the current evaluation context.

I think this really boils down to the fact that name spaces in clojure do not work exactly the same as they do in Common Lisp, and symbols don’t work the same either. In Common Lisp there’s no distinction between symbol and var— there are only symbols, interned and uninterned.

I think I have the same problem when I use backquote/tilde, especially when I use them outside a macro definition. I’m always in fear that some symbol references something nefarious. I’ve posed a question here, regarding my confusion about backquote.

You are right that I was deliberately omitting some details of symbols and bindings etc, but the fundamental point that I think you’re missing here is that while namespaces handle mappings from symbols to Vars (and then to values or functions), Java class names are completely separate and live in Java packages.

So you have your clojure-rte.bdd namespace containing defrecord and when it is loaded (compiled), it generates three things:

  • A Clojure function whose (symbol) name is clojure-rte.bdd/->Bdd – mapped in the clojure-rte.bdd namespace
  • A Clojure function whose (symbol) name is clojure-rte.bdd/map->Bdd – mapped in the clojure-rte.bdd namespace
  • A Java class whose (symbol) name is clojure_rte.bdd.Bdd – which “lives” in the Java package clojure_rte.bdd

Even though at compilation time, Clojure sees just the symbol Bdd, it will either resolve it to a Clojure binding or Var through the available namespaces or resolve it to a Java class name through the available Java packages, based either on shortcuts introduced via import if it is unqualified or as a fully-qualified Java class name if it is qualified.

Those are two completely separate spaces. I’m not sure I can make it any clearer than that so I hope that completely answers your questions?

2 Likes

@Jim_Newton I wonder if this REPL session helps in terms of seeing the two separate spaces that Clojure names and Java (class) names live in:

user=> javax.sql.DataSource ; fully-qualified class name
javax.sql.DataSource ; resolves to itself
user=> (ns javax.sql)
nil
javax.sql=> (def DataSource 42)
#'javax.sql/DataSource
javax.sql=> DataSource ; looks up symbol in current ns and finds it
42
javax.sql=> (in-ns 'user)
#object[clojure.lang.Namespace 0x72458efc "user"]
user=> javax.sql.DataSource ; still refers to fully-qualified class name
javax.sql.DataSource
user=> javax.sql/DataSource ; note the Clojure qualified syntax
42
user=> (require '[javax.sql :as q])
nil
user=> q/DataSource ; Clojure qualification, resolves to value of Clojure Var in javax.sql ns
42
user=> (import '(javax.sql DataSource)) ; create local alias for javax.sql.DataSource
javax.sql.DataSource
user=> DataSource ; unqualified _class_ name resolves to fully-qualified class name
javax.sql.DataSource

Now moving into unqualified Clojure name lookup:

user=> (require '[javax.sql :refer [DataSource]]) ; refer in the Clojure DataSource symbol...
;; ...see that we get a warning about conflict with the DataSource shorthand from import above:
WARNING: DataSource already refers to: interface javax.sql.DataSource in namespace: user, being replaced by: #'javax.sql/DataSource
nil
user=> DataSource ; unqualified name resolves to referred Clojure Var from javax.sql ns
42
user=> javax.sql.DataSource ; can still use fully-qualified Java class name
javax.sql.DataSource
user=> javax.sql/DataSource ; can still use fully-qualified Clojure symbol
42
1 Like

Reading the above made me realise I’m not really clear on what a namespace alias is. If C is any java class in namespace test (eg. created via a defrecord), and we alias test in another namespace to t, evaluating C with or without the alias produces different results, eg.

(type test.C)
; java.lang.Class
(type t/C) 
; Syntax error compiling at ..
; No such var: t/R

I guess I had taken ‘alias’ literally to mean that evaluating an alias-qualified symbol (not sure what else to call it) was exactly equivalent to evaluating the corresponding fully-qualified one.

In reality the latter retrieves any value from the namespace map (clojure var or java class alike). The former seems only to retrieve vars.

I realise @seancorfield I may only be rewording what you write above (referring to ‘Clojure qualified syntax’), but it’s helpful for me to make explicit my earlier false assumption.

Java classes do not live in namespaces. They live in packages. That’s why t/C doesn’t work.

defrecord creates a Java class in a package name that is similar to the namespace name (- in the namespace will be replaced by _ for example). But the package name and the namespace name are in separate spaces.

You can alias a namespace and then use the alias to refer to Clojure names (but not Java class names).

There is no way to alias a Java package name. You can either use the fully-qualified (package + class) name or you can use import to create a shortcut so that you can use just the class name on its own.

But there is a symbol for the class in the namespace map, eg in the above case

((ns-map 'test) 'C)

evals to test.C

Hence my puzzlement over the difference between using the alias vs fully-qualified lookups. I had assumed (falsely) that both just looked up the value (clojure var or java class) for the key (symbol).

That’s more a convenience of implementation: when you are inside your test namespace, you can refer to C without a qualifier (without a package name). ns-map produces a combination of the public symbols and imports. ns-imports will give you the Java class names. ns-publics will give you the (public) Clojure names. Again, they are separate spaces.

You’ll see if you (import '(java.util Date)) there will now be an entry in the ns-map result for Date – but I hope you wouldn’t expect to be able to reference test/Date or test.Date from another namespace?

1 Like