Fork me on GitHub
#clojure-dev
<
2020-11-07
>
Jim Newton12:11:20

I have noticed that the defn macro does not allow me to define a function with a qualified name. (defn foo/bar ...) fails with an error such as:

Syntax error macroexpanding clojure.core/defn at (rte_construct.clj:401:1).
rte/expand - failed: simple-symbol? at: [:fn-name] spec: :clojure.core.specs.alpha/defn-args
However, I can use (def ...) to achieve the same thing with no such error occurring. I suspect that the error is erroneous. I.e., the syntax checker is overzealous. If it is really important that defn not be used to define functions with fully qualified names, shouldn't def have the same restriction? And if using def for this purpose is OK, then shouldn't the restriction be relaxed on defn? Why is the restriction in place for defn ?

borkdude12:11:24

@jimka.issy You should use intern for that.

borkdude12:11:42

defn only allows you to define vars in the current namespace, so a qualified symbol is not appropriate

Jim Newton12:11:15

appropriate in what sense? would it break something in the language? What do you mean about using intern? For example if i'm at the REPL, i might very well like to (defn ...) any function in my project. Right?

borkdude12:11:22

defn is just sugar over def and the docstring of def says: > Creates and interns a global var with the name > of symbol in the current namespace (ns) or locates such a var if > it already exists.

borkdude12:11:42

If you want to intern a var in a different namespace, use intern

Jim Newton12:11:49

so how can I use intern to define a function in a different namespace without interning into the current one? sorry I don't understand your suggestion.

borkdude12:11:04

bar=> (intern 'foo 'foo-fn  (fn []))
#'foo/foo-fn

Jim Newton12:11:15

OK, that's great. But it doesn't explain why defn has the extra syntax restriction that the name must be a simple symbol?

Jim Newton13:11:17

For example, I can define -defn as follows

(defmacro -defn 
  "Like defn, but allows symbol naming the function to be in
  any namespace, not just a simple symbol"
  [name doc-string lambda-list & body]
  `(def ~name ~doc-string (fn ~lambda-list ~@body)))
Is there anything dangerous about this, other than it doesnt handle the optional docstring as defn does, and doesnt handle var-args function defs?

borkdude13:11:51

I'm not sure why defn doesn't allow qualified symbols, but my guess would be that it's not very common to define things outside of the current namespace.

borkdude13:11:16

And for the uncommon case you can reach for intern.

borkdude13:11:30

Or use in-ns, etc.

Jim Newton13:11:34

I agree that it is probably the common case. However, I'd still like to know the reason for such a hostile syntax check?

Jim Newton13:11:11

If there is a real reason for the syntax check, then my use of -defn may have the same problem. So I'd love to know about it.

borkdude13:11:51

The check is according to the docstring. Not letting slip through things that are against the docstring is helpful. You may disagree with the docstring, but that doesn't make it different.

Jim Newton13:11:29

Ahh the syntax check simply enforces the stated documentation.

Jim Newton13:11:16

So the question changes. Rather than asking about the syntax check, I need to ask about why the function is documented in such a restrictive way which doesn't match the implementation?

Jim Newton13:11:13

Michiel, I know you're not the one who did the implementation. so i'm not complaining in your direction.

borkdude13:11:14

I think your question is: why doesn't defn let me define things outside the current namespace. That I don't know, other than my guess above.

borkdude13:11:51

@jimka.issy A good place to post your question might be http://ask.clojure.org/

borkdude13:11:09

This may also shed some light on it: https://stackoverflow.com/a/18141019/6264. def is a special form, handled by the compiler

Jim Newton13:11:56

Which of the following is clearer?

(defn and ;; bdd/and
  "Perform a Boolean AND on 0 or more Bdds."
  ([] true)
  ([bdd] bdd)
  ([bdd1 bdd2]
   (cond
     (= false bdd1) false
     (= false bdd2) false
     (= true bdd1) bdd2
     (= true bdd2) bdd1
     (identical? bdd1 bdd2) bdd1
     :else (binary-op and bdd1 bdd2)))
  ([bdd1 bdd2 & bdds]
   (reduce and (apply cons bdd1 bdd2 bdds))))
vs
(ns-defn bdd/and
  "Perform a Boolean AND on 0 or more Bdds."
  ([] true)
  ([bdd] bdd)
  ([bdd1 bdd2]
   (cond
     (= false bdd1) false
     (= false bdd2) false
     (= true bdd1) bdd2
     (= true bdd2) bdd1
     (identical? bdd1 bdd2) bdd1
     :else (binary-op and bdd1 bdd2)))
  ([bdd1 bdd2 & bdds]
   (reduce and (apply cons bdd1 bdd2 bdds))))

andy.fingerhut13:11:13

I did not design Clojure, but I suspect that those who did find it perfectly clear that def and defn always and only are used to define Vars within the current namespace. The most common practice by far in Clojure is to have an ns form near the beginning of each file, and then not to do in-ns after that in source code files. (There are exceptions, but they are unusual). In this usual case, the namespace of every def and defn in the same file is very clearly understood.

andy.fingerhut13:11:05

Based on my experience with Clojure, I would personally find it confusing if there were def and/or defn forms that created Vars in namespaces other than the current one.

Jim Newton14:11:25

I can't say whether would be confusing or not. the the case is that def allows it and defn does not.

andy.fingerhut14:11:35

It seems that perhaps the implemention of def allows it only if the namespace is the current namespace.

Jim Newton14:11:09

hmm.. yes i've only tried it with an alias to the current ns.

andy.fingerhut14:11:15

You asked which of two alternatives would be clearer. I tried to give reasons above why I think it is perfectly clear which namespace a def or defn is in, even if there is no namespace in the symbol given to def or defn -- because it is understood to be in the current namespace.

andy.fingerhut14:11:22

And for the majority of Clojure source files, every def and defn is in the same namespace, i.e. the one whose ns form is near the beginning of the file.

andy.fingerhut14:11:10

I'm not a designer of Clojure, just a close observer, so do not treat my words with the weight of someone who has designed it.

Jim Newton14:11:45

OK, it's not a real hinderance for me. and in the end it's not really so important. just seems inconsistent to me, with no compelling reason. But with lisp, it's very easy to work around most syntactical problems, whether real or perceived

bronsa15:11:42

@jimka.issy def doesn't allow you to define vars outside the current ns either

3
bronsa15:11:39

foo=> (ns bar)
nil
bar=> (def foo/bar 1)
Syntax error compiling def at (REPL:1:1).
Can't refer to qualified var that doesn't exist
bar=> (def bar/bar 1)
#'bar/bar
bar=> (ns foo)
nil
foo=> (def bar/bar 1)
Syntax error compiling def at (REPL:1:1).
Can't create defs outside of current ns

Jim Newton15:11:33

ahhh interesting. So that sounds like the syntax check is really wrong for defn. Because I've aliased the current ns to foo and then attempted to (defn foo/my-fun ....) which fails and (def foo/my-fn ...) which succeeds. Looks like the syntax check for defn should be changed to allow either a simple symbol, or a qualified symbol which resolves to the current namespace.

bronsa14:11:07

So that sounds like the syntax check is really wrong for defn

bronsa14:11:15

it's precisely how it's intended to be

bronsa14:11:42

the fact that def allows the current namespace is a bit of a historical leftover