3
votes

I have a function definition like so:

(defn init-globals [] (def *board-width* 15))

I want to be able to reformat so that it looks something like:

(defn init-globals []
   (def *board-width* 15))

I would like the formatting to be done by an intelligent agent that doesn't merely indent everything a fixed amount, or just prints one element per line etc. This is something that perltidy does.

perltidy takes code like:

sub foo {
my $args = shift;    $a = $args->{a}; $long_var = $args->{b}; }

and turns it into:

sub foo {
    my $args = shift;
    $a        = $args->{a};
    $long_var = $args->{b};
}

Notice how it considers the "sub" defintion as a whole, and even the "=" on the last two assignments line up. While I don't necessarily need this level of formatting, I would like something that takes a list (or more accurately, a form), that is a function with args and subexpressions, and makes it as human readable as possible.

The reason my function definition is a single line is because it's generated from a macro. I don't want to clutter up the macro with formatting instructions (if indeed I can even do this without generating a string instead of a list) because macros are hard enough as it is. While I can eval the list into my REPL just fine, I would also like to write the generated code to a file as if it were typed in by hand i.e something you could check into source control.

I've looked at things like (clojure.pprint/pp), and using emacs 'indent-sexp and smartparen's 'sp-indent-defun, but they don't split the output into multiple lines among other things.

I can interop with emacs/elisp commands, so if there's a way to do it with emacs, that would work too.

2
def inside other forms is usually a bad ideanoisesmith
@noisesmith I think the outer def here is supposed to be a macro emitting the list (def ...) and the backquote got lost in translation.arrdem
just use any proper clojure code editor from the following list: [:emacs] and it'll do it for you ;)iced

2 Answers

4
votes

If this is what you want to do, then I'd suggest simply printing your generated code (or however you want to write it to a file) and then throwing cljfmt at it.

There exist tools such as bbloom's Fipp which can do similar code pretty-printing from inside a Clojure instance, but to the best of my knowledge cljfmt does a better job.

I think that it's worth considering why you are attempting to save macroexpanded code, since the cost of macroexpansion is nominal and it would be considered better practice to generate code as-needed rather than to generate code, save it, and then handle it as if it had been hand-written. Retaining code generation also means that your codebase is more flexible in that you can just tweak the generator and reload the namespaces involved rather than having to regenerate the generated code, and then reformatting and recompile that.

2
votes

There is lispy - my extension with short bindings for general LISP programming.

It supports any LISP, and even has Clojure inline eval, inline args, inline doc, and goto-symbol via CIDER.

It also has a naive multi-lining shortcut that does exactly what you want for this particular function.

With lispy-mode on, you need to position your cursor either on the paren before defn or after the paren at the end of the line. Then press M to format an expression over multiple lines.

To format it back into a single line, press O.

To improve a badly formatted expression like this one:

(defn init-globals[](def *board-width* 15
                      )
  )

navigate to a proper paren and press i.

If you happen not to be fully satisfied with a result of particular M, you can quickly fix it manually, using h, j, k, l and f, d for by-paren navigation.