Style

8 views
Skip to first unread message

Paul Drummond

unread,
Mar 6, 2008, 5:20:58 AM3/6/08
to Clojure
When looking at boot.clj, I can't help but feel the positioning of
meta data and the affect it has on the style and indentation of the
rest of the definition makes the code difficult to read. Take the
first definition in boot.clj for example:

(def
#^{:arglists '([& args])
:doc "Creates a new list containing the items."}
list (. clojure.lang.PersistentList creator))

As my eyes scan over this, the actual var being defined is hidden in
the noise. Meta data which I am not really interested in as a reader
(except for the doc string) dominates the definition and has
precedence over everything including the name of the var being
defined. Also the var name being in the first column almost makes it
look separated from the forms above it. Thanks to Python, I have
grown very accustomed to expecting logical blocks of code to be
intended at the same level so this style is foreign to me.

I just wanted to start a discussion about this and determine whether
you are up for suggestions Rich but I understand if it's low priority
right now or if you are sticking with the style for good reasons. I'm
sure I *will* get used to it if it's here to stay :)

Anyhow, here's my brain dump of ideas on alternatives:

----

At the very least, moving the name above the meta data would be a
major improvement because the reader (as in person reading the code)
would see the name immediately after the def and then everything
regarding the definition would be indented correctly below:

(def list
#^{:arglists '([& args])
:doc "Creates a new list containing the items."}
(. clojure.lang.PersistentList creator))

However, IMO a far better approach (and more radical change) would be
to promote doc strings to make them part of the def as I believe they
are important enough to stand out from the meta data. Then, the meta
data itself could be above the def (as in other languages such as Java
and Python) which seems to be the most unobtrusive place for some
reason) as follows:

#^{:arglists '([& args])}
(def list
"Creates a new list containing the items."
(. clojure.lang.PersistentList creator))

And a function would look like this:

#^{:tag Boolean }
(defn nil? [x]
"Returns true if x is nil, false otherwise."
(identical? x nil))

-----

I know style issues regarding programming languages is stupendously
subjective and communities could potentially spend years arguing over
details but really the chosen style is irrelevant. The point of this
post is to highlight the problems with the current style which are:

a)
meta-data is too prominent

b)
currently, the most important elements of a definition are hidden in
the noise - the name and the doc string.

Cheers,
Paul.

Marko Kocić

unread,
Mar 6, 2008, 5:35:15 AM3/6/08
to Clojure


On Mar 6, 11:20 am, Paul Drummond
<paul.drummond.webm...@googlemail.com> wrote:
> #^{:arglists '([& args])}
> (def list
>     "Creates a new list containing the items."
>     (. clojure.lang.PersistentList creator))
>
> And a function would look like this:
>
> #^{:tag Boolean }
> (defn nil? [x]
>     "Returns true if x is nil, false otherwise."
>     (identical? x nil))

This looks much better to me then the current style.
Positionally :doc
resembles to CL and elisp docstrings, while metadata resembles Java
annotations (and javadocs).

I'm only thinking about which style will be promoted for
multifunctions
like this one?
(defn test
([] 0)
([x] x)
([x & rest] (apply x rest)))

Josip Gracin

unread,
Mar 6, 2008, 10:01:17 AM3/6/08
to Clojure
On Thu, 2008-03-06 at 02:20 -0800, Paul Drummond wrote:
> (def
> #^{:arglists '([& args])
> :doc "Creates a new list containing the items."}
> list (. clojure.lang.PersistentList creator))

I don't like the looks of this either. However, I cannot think
of a solution which would look good and simultaneously have
the elegance and consistency of the above. In my code, I would
probably prefer adding meta-data after and outside of the definition.
Except the type tag which doesn't affect the looks of the definition.

jonathan...@gmail.com

unread,
Mar 6, 2008, 10:20:44 AM3/6/08
to Clojure
I've simply started continuing as if the extra metadata was not there,
which seems to make function defs much easier to read.

(defn
#^{ :doc "Scale a list of numeric 'values' to a max height of
'height'."}
scale-values ([values height]
(let [highest (apply max (filter identity values))]
(map #(if % (dec (- height (/ (* % (- height 3)) highest))) nil)
values))))

Jonathan

Rich Hickey

unread,
Mar 6, 2008, 11:03:17 AM3/6/08
to Clojure


On Mar 6, 5:20 am, Paul Drummond
<paul.drummond.webm...@googlemail.com> wrote:
> When looking at boot.clj, I can't help but feel the positioning of
> meta data and the affect it has on the style and indentation of the
> rest of the definition makes the code difficult to read. \

Agreed. See the discussion here:

http://groups.google.com/group/clojure/browse_frm/thread/fa84dc8f785a9c32/d6dee9c1a79f953d?#d6dee9c1a79f953d

> Take the
> first definition in boot.clj for example:

I think it is important to avoid the first few definitions in boot.clj
as examples, as during this part of the bootstrap process many aspects
of Clojure do not yet exist - no defmacro, no defn, many functions are
not yet defined. Things here are inevitably going to be uglier than
elsewhere. For instance :arglists is created by defn and normally
never seen in the source.

> I just wanted to start a discussion about this and determine whether
> you are up for suggestions Rich but I understand if it's low priority
> right now or if you are sticking with the style for good reasons. I'm
> sure I *will* get used to it if it's here to stay :)
>

As discussed before, I have gone ahead with putting in the docs and
some other metadata so we can all get some experience with it, but it
will change.

> At the very least, moving the name above the meta data would be a
> major improvement because the reader (as in person reading the code)
> would see the name immediately after the def and then everything
> regarding the definition would be indented correctly below:
>

As suggested in the prior discussion, this is the most doable. Having
lived with it a bit, I most miss the name at the start of the defn, so
I know what the rest is about.

> However, IMO a far better approach (and more radical change) would be
> to promote doc strings to make them part of the def as I believe they
> are important enough to stand out from the meta data.

> And a function would look like this:
>
> #^{:tag Boolean }
> (defn nil? [x]
> "Returns true if x is nil, false otherwise."
> (identical? x nil))
>

As Marko mentions in a subsequent message in this thread, putting the
args before the docstring doesn't work for fns overloaded on arity.
I'm not sure I like having the other metadata precede the line with
the name, again burying it, but there is some precedent as you say,
albeit in language without a reader and code-as-data.

I also think using an editor with good highlighting/folding changes
the story, sometimes dramatically. That's not an argument for the
status quo, but there will be limits to what indentation and order
alone can do.

I'm open to suggestions and may have some time to work on this. One
thing I'd like to remind everyone is that even though these attributes
end up being metadata on the vars, they need not be expressed as
metadata on the source forms. Using metadata on the name form in the
first cut was just a convenience, and has some nice properties for
macros, but if the data-to-become-the-metadata ends up inside the defn
form, it is just part of defn's syntax, which is already composed of
ordinary forms and order dependencies and could have others.

Rich

Rich Hickey

unread,
Mar 6, 2008, 3:01:18 PM3/6/08
to Clojure
Current ideas:

(defn name doc-string? attr-map? args body)

and

(defn name doc-string? attr-map? (args body)+ attr-map?)

e.g.

(defn =
"Equality. Returns true if obj1 equals obj2, false if not. Same as
Java obj1.equals(obj2) except it also works for nil, and compares
numbers in a type-independent manner. Clojure's immutable data
structures define equals() (and thus =) as a value, not an
identity, comparison."

{:tag Boolean}

[x y]
(. clojure.lang.RT (equal x y)))

(defn mymax
"mymax [xs+] gets the maximum value in xs using > "

{:user/comment "this is the best fn ever!"}

([x] x)
([x y] (if (> x y) x y))
([x y & more]
(reduce mymax (mymax x y) more))

{:test (fn []
(assert (= 42 (max 2 42 5 4))))})

If you have extensive additional attributes, test cases, examples etc,
using the (args body) form and placing the longer metadata afterwards
will be preferred.

Doc strings and all attributes supplied this way will become metadata
on the var exactly as now, and will be added to any metadata on the
name symbol, allowing macros that generate defn an easy way to pass
along attributes.

Thoughts?

Rich

Paul Drummond

unread,
Mar 6, 2008, 4:05:57 PM3/6/08
to Clojure
> (defn mymax
> "mymax [xs+] gets the maximum value in xs using > "
> {:user/comment "this is the best fn ever!"}
> ([x] x)
> ([x y] (if (> x y) x y))
> ([x y & more]
> (reduce mymax (mymax x y) more))
> {:test (fn []
> (assert (= 42 (max 2 42 5 4))))})
>

Yep, that looks good to me.

And of course, putting the args before the docstring won't work for
fns overloaded on arity. Ugh, I missed that!

But yes, this is definitely better and looking at it, I think the
position of the meta-data is actually better inside the defn than
outside and being able to specify it in more than one position within
the def is neat.

Paul.

jon

unread,
Mar 7, 2008, 10:45:14 AM3/7/08
to Clojure
> Thoughts?

Just wanted to add another thumbs-up.. seems tidy and logical to me :)


This may be a dumb question, but suppose you were writing test
functions (or docstrings) during development, and you decided to strip
them out before release.. what would be the recommended technique?
ie.
- might there be 'cool test framework stuff' you'll lose by avoiding
the {:test ...} metadata altogether, and putting tests in separate
unrelated files?
- alternatively, would you define tests in separate files and 'hook'
them onto {:test ...} for the relevant vars afterwards using (with-
meta ...)?
- is it daft to imagine some macro-style-magic that reads a source
file, removes {:doc .. :test .. :whatever ..} and spits out a
'stripped' source file?

Thanks, Jon

Stuart Sierra

unread,
Mar 7, 2008, 6:20:00 PM3/7/08
to Clojure
On Mar 7, 10:45 am, jon <superuser...@googlemail.com> wrote:
> - is it daft to imagine some macro-style-magic that reads a source
> file, removes {:doc .. :test .. :whatever ..} and spits out a
> 'stripped' source file?

I know Common Lisp utilities have been written to process and output
Lisp source, so it should be eminently feasible.

-Stuart

jon

unread,
Mar 9, 2008, 5:58:34 PM3/9/08
to Clojure
> Thoughts?

Just been looking at latest boot.clj in svn.
The current improvements already make things look much better to me,
but I just had a thought..

(defn =
"| Equality. Returns true if obj1 equals obj2, false if not. Same as
| Java obj1.equals(obj2) except it also works for nil, and compares
| numbers in a type-independent manner. Clojure's immutable data
| structures define equals() (and thus =) as a value, not an
identity,
| comparison."
{:tag Boolean}
[x y] (. clojure.lang.RT (equal x y)))

What if the convention was that all docstrings have 'space*
verticalbar space' stripped off the start of every line before
(pretty) printing?
(or similar variation such as 'space* colon space')
I think it helps the code look a bit tidier, and wouldn't that also
make any aligning-the-text problems go away?
What does anyone think?
Thanks, Jon
Reply all
Reply to author
Forward
0 new messages