Domanda

In his Clojure Style Guide the author writes the following,

Prefer using :require :refer :all over :use in ns macro

He does not give any explanation why this is a good idea. Is there a good reason to avoid the use of :use in the ns macro?

È stato utile?

Soluzione 2

My impression is that there are two reasons. 1) In doing so, you are more likely to avoid any name clashes and/or accidental overshadowing. 2) Such practices give greater transparency about the ultimate source of the functions and variables used in the code. The first one is pretty practical, and Chiron's answer provides a great demonstration.

The second one is more subtle, but if you are using a lot of libraries, it can be pretty important. Let's say you do:

(ns your-namespace
  (:use [library-one]
        [library-two]
        [library-three]
        [library-four]
        [library-five]))

And then somewhere, later in your code, you invoke:

(important-function some-arg some-other-arg)

If important-function is defined in, for example, library-three, then you (or whoever is trying to make sense of your code) have to go digging through each of those libraries' code to figure out what it does--because there's nothing to indicate which library is its source! Well, actually, if you have a REPL available, you should be able to figure it out by doing:

your-namespace> `important-function
> library-three/important-function

But that kind of hidden information is probably not the best thing to inflict on other people (including your future self) who may wish to understand your code. On the other hand, :required libraries always come with a prefix. So instead, your code would look like:

(ns your-namespace
  (:require [library-one]
            [library-two]
            [library-three]
            [library-four]
            [library-five]))

...

(library-three/important-function some arg some-other-arg)

Which makes it very clear where important-function is defined. Note that this should be equivalent to specifying :refer :all. Although :refer :all could indicate to others that you considered specifically which vars to reference, and made a conscious decision to include all of them from that library.

If you are not worried about name clashing...well you probably should be. But if you still aren't, and you want to be clear about vars' sources, then you could always do :use with :only. For example:

(ns your-namespace
  (:use [library-one :only []]
        [library-two :only []]
        [library-three :only [important-function]]
        [library-four :only []]
        [library-five :only []]))

...

(important-function some arg some-other-arg)

Obviously, if you are :useing libraries with blank :only vectors, than you might as well not :use them in the first place, I just wanted to unify with the previous examples. And doing :require may seem verbose, but you can always shorten it by aliasing with :as. Fragment example:

(ns your-namespace
  (:require [library-three :as L3]))

(L3/important-function some-arg some-other-arg)

See also, this SO question, and this guide about libraries and namespaces in Clojure.

Altri suggerimenti

I'd suggest you take a look at the original discussion, which lead to this rule. The core rationale can be found here. I'll include here the start of the discussion:

There's been discussion previously about the complexity of the ns macro. In particular the fact that :use causes all vars to be referred by default is widely seen as unfortunate, and the distinction between use and require is a source of conceptual overhead for people new to the language. We can't change the fact that :use refers everything by default without breaking lots of existing code. But it would be possible to enhance :require to support referring specified vars, leaving us free to deprecate or otherwise discourage the use of :use.

Rather than this form:

(ns mork.test
  (:use [mork.stats :only [summarize-group]]
        [mork.utils :only [strip resolve-fn]]
        [clojure.test])
  (:require [mork.view :as view]
            [clojure.java.io :as io]
            [cheshire.core :as json])
  (:import (java.io PushbackReader))

We could use this:

(ns mork.test
  (:require [mork.stats :refer [summarize-group]]
            [mork.utils :refer [strip resolve-fn]]
            [clojure.test :refer :all]
            [mork.view :as view]
            [clojure.java.io :as io]
            [cheshire.core :as json])
  (:import (java.io PushbackReader))

It has been agreed upon in previous threads that keeping :import as a distinct concept from :require is desirable, and I agree that we shouldn't conflate between Clojure vars and Java classes.

There are rare cases when referring all the vars in a namespace is acceptable, (in particular when writing test namespaces it seems reasonable to bring in all of clojure.test and the namespace under test) so we should probably support :refer :all for such a case, as long as it's not the default.

Separation of concerns. require let you load namespaces, refer let you define how you refer to vars within those namespaces. use complects the two concerns.

With :require you have only one place where you are defining your namespace imports and you can easily switch from referring all functions to referring a few functions or just requiring the namespace (or mix and match however you like).

(:require [foo.bar]
          [foo.baz :refer :all :as baz]
          [foo.foobar :as fb :refer [bazbaz]])

Because with :require you will have a clear indication which namespace owns a function.

(:require [clojure.json :as json])
(:require [clojure.xml  :as xml])

(json/read "file")
(xml/read  "file")

In case of :use

(read "file")

How you are going to know which namespaces owns read function? If I'm maintaining your code, I have to check ns macro and start searching for read function in each use qualified namespace.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top