Pergunta

I am new to clojure and the main thing I am struggling with is writing readable code. I often end up with functions like the one below.

(fn rep 
  ([lst n]
    (rep (rest lst)
    n
    (take n 
      (repeat (first lst)))))
  ([lst n out]
    (if
      (empty? lst)
      out
      (rep 
        (rest lst) n
        (concat out (take n 
          (repeat 
            (first lst))))))))

with lots of build ups of end brackets. What are the best ways of reducing this or formatting it in a way that makes it easier to spot missing brackets?

Foi útil?

Solução

An editor that colors the parenthesis is extremely helpful in this case. For example, here's what your code looks in my vim editor (using vimclojure):

Rainbow colors

Since you didn't say which editor you use, you'll have to find the rainbow-coloring feature for your editor appropriately.

Outras dicas

Using Emacs's paredit mode (emulated in a few other editors too) means you're generally - unless you're copy/pasting with mouse/forced-unstructured selections - dealing with matched brackets/braces/parentheses and related indenting with no counting needed.

Emacs with https://github.com/technomancy/emacs-starter-kit (highly recommended!) has paredit enabled for clojure by default. Otherwise, see http://emacswiki.org/emacs/ParEdit

In addition to having an editor that supports brace matching, you can also try to make your code less nested. I believe that your function could be rewritten as:

(defn rep [coll n] (mapcat (partial repeat n) coll))

Of course this is more of an art (craft) than science, but some pointers (in random order):

  • Problems on 4clojure and their solutions by top users (visible after solving particular problems) - I believe that Chris Houser is there under the handle chouser
  • Speaking of CH - "The Joy of Clojure" is a very useful read
  • Browsing docs on clojure.core - there are a lot of useful functions there
  • -> and ->> threading macros are very useful for flattening nested code
  • stackoverflow - some of the brightest and most helpful people in the world answer questions there ;-)

I cannot echo strongly enough how valuable it is to use paredit, or some similar feature in another editor. It frees you from caring at all about parens - they always match themselves up perfectly, and tedious, error-prone editing tasks like "change (foo (bar x) y) into (foo (bar x y))" become a single keystroke. For a week or so paredit will frustrate you beyond belief as it prevents you from doing things manually, but once you learn the automatic ways of handling parens, you will never be able to go back.

I recently heard someone say, and I think it's roughly accurate, that writing lisp without paredit is like writing java without auto-complete (possible, but not very pleasant).

(fn rep 
  ([lst n]
    (rep lst n nil))
  ([lst n acc]
    (if-let [s (seq lst)]
      (recur (rest s) n (concat acc (repeat n (first s))))
      acc)))

that's more readable, i think. note that:

  • you should use recur when tail recursing
  • you should test with seq - see http://clojure.org/lazy
  • repeat can take a count
  • concat will drop nil, which saves repeating yourself
  • you don't need to start a new line for every open paren

as for the parens - your editor/ide should take care of that. i am typing blind here, so forgive me if it's wrong...

[Rafał Dowgird's code is shorter; i am learning too...]

[updated:] after re-reading the "lazy" link, i think i have been handling lazy sequences incorrectly,

I'm not sure you can avoid all the brackets. However, what I've seen Lispers do is use an editor with paren matching/highlight and maybe even rainbow brackets: http://emacs-fu.blogspot.com/2011/05/toward-balanced-and-colorful-delimiters.html

Frankly, these are the kind of features that would be useful for non-Lisp editors too :)

Always use 100% recycled closing parentheses made from at least 75% post-consumer materials; then you don't have to feel so bad about using so many.

Format it however you like. It is the editor's job to display code in whatever style the reader prefers. I like the C-style hierarchical tree-shaped format with single brackets on their own lines (all the LISPers boil with rage at that :-)))))))))))))

But, I sometimes use this style:

  (fn rep 
  ([lst n]
    (rep (rest lst)
    n
    (take n 
      (repeat (first lst)) )  )   )    )

which is an update on the traditional style in which brackets are spaced (log2 branch-level)

The reason I like space is that my eyesight is poor and I simply cannot read dense text. So to the angry LISPers who are about to tell me to do things the traditional way I say, well, everyone has their own way, relax, it's ok.

Can't wait for someone to write a decent editor in Clojure though, which is not a text editor but an expression editor**, then the issue of formatting goes away. I'm writing one myself but it takes time. The idea is to edit expressions by applying functions to them, and I navigate the code with a zipper, expression-by-expression, not by words or characters or lines. The code is represented by whatever display function you want.

** yes, I know there's emacs/paredit, but I tried emacs and didn't like it sorry.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top