Hy Style Guide

The Hy style guide intends to be a set of ground rules for the Hyve (yes, the Hy community prides itself in appending Hy to everything) to write idiomatic Hy code. Hy derives a lot from Clojure & Common Lisp, while always maintaining Python interoperability.

Layout & Indentation

The #1 complaint about Lisp?

It’s too weird looking with all those parentheses! How do you even read that?

And, they’re right! Lisp was originally much too hard to read. Then they figured out layout and indentation. And it was glorious.

The Three Laws

Here’s the secret: Real Lispers don’t count the brackets. They fade into the background. When reading Lisp, disregard the trailing closing brackets—those are for the computer, not the human. As in Python, read the code structure by indentation.

Lisp code is made of trees—Abstract Syntax Trees—not strings. S-expressions are very direct textual representation of AST. That’s the level of homoiconicity—the level Lisp macros operate on. It’s not like the C-preprocessor or Python’s interpolated eval-string tricks that see code as just letters. That’s not how to think of Lisp code; think tree structure, not delimiters.

  1. Closing brackets must NEVER be left alone, sad and lonesome on their own line.
;; PREFERRED
(defn fib [n]
  (if (<= n 2)
      n
      (+ (fib (- n 1))
         (fib (- n 2)))))  ; Lots of Irritating Superfluous Parentheses
                                ; L.I.S.P. ;))

;; How the experienced Lisper sees it. Indented trees. Like Python.
(defn fib [n
  (if (<= n 2
      n
      (+ (fib (- n 1
         (fib (- n 2

;; BAD
;; We're trying to ignore them and you want to give them their own line?
;; Hysterically ridiculous.
(defn fib [
    n
]  ; My eyes!
  (if (<= n 2)
    n
    (+ (fib (- n 1)) (fib (- n 2)))
  )
)  ; GAH, BURN IT WITH FIRE!
  1. New lines must ALWAYS be indented past their parent opening bracket.
;; PREFERRED
(foo (, arg1
        arg2))

;; BAD. And evil.
;; Same bracket structure as above, but not enough indent.
(foo (, arg1
  arg2))

;; PREFERRED. Same indent as above, but now it matches the brackets.
(fn [arg]
  arg)

;; Remember, when reading Lisp, you ignore the trailing brackets.
;; Look at what happens if we remove them.
;; Can you tell where they should go by the indentation?

(foo (, arg1
        arg2

(foo (, arg1
  arg2

(fn [arg
  arg

;; See how the structure of those last two became indistinguishable?

;; Reconstruction of the bad example by indent.
;; Not what we started with, is it?
(foo (, arg1)
  arg2)

;; Beware of brackets with reader syntax.
;; You still have to indent past them.

;; BAD
`#{(foo)
 [email protected][(bar)
  1 2]}

;; Above, no trail.
`#{(foo
 [email protected][(bar
  1 2

;; Reconstruction. Is. Wrong.
`#{(foo)}
 [email protected][(bar)]
  1 2

;; PREFERRED
`#{(foo)
   [email protected][(bar)
      1
      2]}

;; OK
;; A string is an atom, not a HySequence.
(foo "abc
  xyz")

;; Still readable without trailing brackets.
(foo "abc
  xyz"  ; Double-quote isn't a closing bracket. Don't ignore it.
  1. New lines must NEVER be indented past the previous element’s opening bracket.
;; BAD
((get-fn q)
  x
  y)

;; The above with trailing brackets removed. See the problem?
((get-fn q
  x
  y

;; By indentation, this is where the brackets should go.
((get-fn q
  x
  y))

;; OK
((get-fn q) x
            y)

;; The above without trailing brackets. Still OK (for humans).
((get-fn q) x  ; The ) on this line isn't trailing!
            y

;; PREFERRED, since the ) should end the line.
((get-fn q)
 x
 y)

Limits

Follow PEP 8 rules for line limits, viz.

  • 72 columns max for text (docstrings and comments).
  • 79 columns max for other code, OR
  • 99 for other code if primarily maintained by a team that can agree to 99.

Whitespace

AVOID trailing spaces. They suck!

AVOID tabs in code. Indent with spaces only.

PREFER the \t escape sequence to literal tab characters in one-line string literals.

  • Literal tabs are OK inside multiline strings if you also add a warning comment.
  • But \t is still PREFERRED in multiline strings.
  • The comment should PREFERABLY appear just before the string.
  • But a blanket warning at the top of a function, class, or file is OK.

Alignment

Line up arguments to function calls when splitting over multiple lines.

  • The first argument PREFERABLY stays on the first line with the function name,
  • but may instead start on the next line indented one space past its parent bracket.
;; PREFERRED. All args aligned with first arg.
(foofunction arg1
             (barfunction bararg1
                          bararg2
                          bararg3)  ; Aligned with bararg1.
             arg3)

;; BAD
(foofunction arg1
             (barfunction bararg1
               bararg2  ; Wrong. Looks like a macro body.
                    bararg3)  ; Why?!
             arg3)

;; PREFERRED. Args can all go on one line if it fits.
(foofunction arg1
             (barfunction bararg1 bararg2 bararg3)
             arg3)

;; OK. Args not on first line, but still aligned.
(foofunction
  arg1  ; Indented one column past parent (
  (barfunction
    bararg1  ; Indent again.
    bararg2  ; Aligned with bararg1.
    bararg3)
  arg3)  ; Aligned with arg1.

Hold it Open

If you need to separate a bracket trail use a #_ / comment to hold it open. This avoids violating law #1.

;; PREFERRED
[(foo)
 (bar)
 (baz)]

;; OK, especially if the list is long. (Not that three is long.)
;; This is better for version control line diffs.
[  ; Opening brackets can't be "trailing closing brackets" btw.
 (foo)
 (bar)
 (baz)
 #_ /]  ; Nothing to see here. Move along.

;; Examples of commenting out items at the end of a list follow.
;; As with typing things in the REPL, these cases are less important
;; if you're the only one that sees them. But even so, maintaining
;; good style can help prevent errors.

;; BAD and a syntax error. Lost a bracket.
[(foo)
 ;; (bar)
 ;; (baz)]

;; BAD. Broke law #1.
[(foo)
 ;; (bar)
 ;; (baz)
 ]

;; PREFERRED
;; The discard syntax respects code structure,
;; so it's less likely to cause errors.
[(foo)
 #_(bar)
 #_(baz)]

;; OK. Adding a final discarded element makes line comments safer.
[(foo)
 ;; (bar)
 ;; (baz)
 #_ /]

Snuggle

Brackets like to snuggle, don’t leave them out in the cold!

;; PREFERRED
[1 2 3]
(foo (bar 2))

;; BAD
[ 1 2 3 ]
( foo ( bar 2 ) )

;; BAD. And ugly.
[ 1 2 3]
(foo( bar 2) )

Grouping

Use whitespace to show implicit groups, but be consistent within a form.

;; Older Lisps would typically wrap such groups in even more parentheses.
;; (The Common Lisp LOOP macro was a notable exception.)
;; But Hy takes after Clojure, which has a lighter touch.

;; BAD. Can't tell key from value without counting
{1 9 2 8 3 7 4 6 5 5}

;; PREFERRED. This can fit on one line. Clojure would have used commas
;; here, but those aren't whitespace in Hy. Use extra spaces instead.
{1 9  2 8  3 7  4 6  5 5}

;; OK. And preferred if it couldn't fit on one line.
{1 9
 2 8
 3 7
 4 6
 5 5}  ; Newlines show key-value pairs in dict.

;; BAD
;; This grouping makes no sense.
#{1 2
  3 4}  ; It's a set, so why are there pairs?

;; BAD
;; This grouping also makes no sense. But, it could be OK in a macro or
;; something if this grouping was somehow meaningful there.
[1
 1 2
 1 2 3]  ; wHy do you like random patterns? [sic pun, sorry]

;; Be consistent. Separate all groups the same way in a form.

;; BAD
{1 9  2 8
 3 7  4 6  5 5}  ; Pick one or the other!

;; BAD
{1 9  2 8 3 7  4 6  5 5}  ; You forgot something.

;; Groups of one must also be consistent.

;; PREFERRED
(foo 1 2 3)  ; No need for extra spaces here.

;; OK, but you could have fit this on one line.
(foo 1
     2
     3)

;; OK, but you still could have fit this on one line.
[1
 2]

;; BAD
(foo 1 2  ; This isn't a pair?
     3)  ; Lines or spaces--pick one or the other!

;; PREFERRRED
(foofunction (make-arg)
             (get-arg)
             #tag(do-stuff)  ; Tags belong with what they tag.
             #* args  ; #* goes with what it unpacks.
             :foo spam
             :bar eggs  ; Keyword args are also pairs. Group them.
             #** kwargs)

;; PREFERRED. Spaces divide groups on one line.
(quux :foo spam  :bar eggs  #* with-spam)
{:foo spam  :bar eggs}

;; OK. The colon is still enough to indicate groups.
(quux :foo spam :bar eggs #* with-spam)
{:foo spam :bar eggs}
;; OK.
("foo" spam "bar" eggs}

;; BAD. Can't tell key from value.
(quux :foo :spam :bar :eggs :baz :bacon)
{:foo :spam :bar :eggs :baz :bacon}
{"foo" "spam" "bar" "eggs" "baz" "bacon"}

;; PREFERRED
(quux :foo :spam  :bar :eggs  :baz :bacon)
{:foo :spam  :bar :eggs  :baz :bacon}
{"foo" "spam"  "bar" "eggs"  "baz" "bacon"}

;; OK. Yep, those are pairs too.
(setv x 1
      y 2)

;; PREFERRED. This fits on one line.
(setv x 1  y 2)

;; BAD. Doesn't separate groups.
(print (if (< n 0.0)
           "negative"
           (= n 0.0)
           "zero"
           (> n 0.0)
           "positive"
           "not a number"))

;; BAD. And evil. Broke law #3. Shows groups but args aren't aligned.
(print (if (< n 0.0)
               "negative"
           (= n 0.0)
               "zero"
           (> n 0.0)
               "positive"
           "not a number"))

;; BAD. Shows groups but args aren't aligned.
;; If the then-parts weren't atoms, this would break law #3.
(print (if (< n 0.0)
         "negative"
           (= n 0.0)
         "zero"
           (> n 0.0)
         "positive"
           "not a number"))

;; OK. Redundant (do) forms allow extra indent to show groups
;; without violating law #3.
(print (if (< n 0.0)
           (do
             "negative")
           (= n 0.0)
           (do
             "zero")
           (> n 0.0)
           (do
             "positive")
           "not a number"))

Separate toplevel forms (including toplevel comments not about a particular form) with a single blank line, rather than two as in Python.

  • This can be omitted for tightly associated forms.

Methods within a defclass need not be separated by blank line.

Special Arguments

Macros and special forms are normally indented one space past the parent bracket, but can also have “special” arguments that are indented like function arguments.

  • Macros with an &rest body argument contain an implicit do.
  • The body is never special, but the arguments before it are.
;; PREFERRED
(assoc foo  ; foo is special
  "x" 1  ; remaining args are not special. Indent 2 spaces.
  "y" 2)

;; PREFERRED
;; The do form has no special args. Indent like a function call.
(do (foo)
    (bar)
    (baz))

;; OK
;; No special args to distinguish. This is also valid function indent.
(do
  (foo)
  (bar)
  (baz))

;; PREFERRED
(defn fib [n]
  (if (<= n 2)
      n
      (+ (fib (- n 1))
         (fib (- n 2)))))

;; OK
(defn fib
      [n]  ; name and argslist are special. Indent like function args.
  ;; The defn body is not special. Indent 1 space past parent bracket.
  (if (<= n 2)
      n
    (+ (fib (- n 1))  ; Emacs-style else indent.
       (fib (- n 2)))))

Removing Whitespace

Removing whitespace can also make groups clearer.

;; lookups

;; OK
(. foo ["bar"])

;; PREFERRED
(. foo["bar"])

;; BAD. Doesn't show groups clearly.
(import foo foo [spam :as sp eggs :as eg] bar bar [bacon])

;; OK. Extra spaces show groups.
(import foo  foo [spam :as sp  eggs :as eg]  bar  bar [bacon])

;; PREFERRED. Removing spaces is even clearer.
(import foo foo[spam :as sp  eggs :as eg] bar bar[bacon])

;; OK. Newlines show groups.
(import foo
        foo [spam :as sp
             eggs :as eg]
        bar
        bar [bacon])

;; PREFERRED, It's more consistent with the preferred one-line version.
(import foo
        foo[spam :as sp
            eggs :as eg]
        bar
        bar[bacon])

;; Avoid whitespace after tags.

;; Note which shows groups better.

;; BAD
(foofunction #tag "foo" #tag (foo) #* (get-args))

;; OK
(foofunction #tag "foo"  #tag (foo)  #* (get-args))

;; PREFERRED
(foofunction #tag"foo" #tag(foo) #*(get-args))

;; PREFERRED
;; Can't group these by removing whitespace. Use extra spaces instead.
(foofunction #x foo  #x bar  #* args)

;; OK
;; Same idea, but this could have fit on one line.
(foofunction #x foo
             #x bar
             #* args)

;; OK, but you don't need to separate function name from first arg.
(foofunction  #x foo  #x bar  #* args)

;; OK. But same idea.
;; No need to separate the first group from the function name.
(foofunction
  #x foo
  #x bar
  #* args)

;; PREFERRED. It's still clear what this is tagging.
;; And you don't have to re-indent.
#_
(def foo []
  stuff)

;; OK, but more work.
#_(def foo []
    stuff)

;; BAD, you messed up the indent and broke law #2.
#_(def foo []
  stuff)

;; BAD, keep the tag grouped with its argument.
#_

(def foo []
  stuff)

Close Bracket, Close Line

A single closing bracket SHOULD end the line, unless it’s in the middle of an implicit group.

  • If the forms are small and simple you can maybe leave them on one line.

A train of closing brackets MUST end the line.

;; One-liners are overrated.
;; Maybe OK if you're just typing into the REPL.
;; But even then, maintaining good style can help prevent errors.

;; BAD. One-liner is too hard to read.
(defn fib [n] (if (<= n 2) n (+ (fib (- n 1)) (fib (- n 2)))))

;; BAD. Getting better, but the first line is still too complex.
(defn fib [n] (if (<= n 2) n (+ (fib (- n 1))
                                (fib (- n 2)))))
;; OK. Barely.
(defn fib [n]
  (if (<= n 2) n (+ (fib (- n 1))  ; This line is pushing it.
                    (fib (- n 2)))))

;; OK
(defn fib [n]  ; Saw a "]", newline.
  (if (<= n 2)  ; OK to break here, since there's only one pair.
      n
    (+ (fib (- n 1))  ; Whitespace separation (Emacs else-indent).
       (fib (- n 2)))))

;; OK
(defn fib [n]  ; Saw a "]", end line. (Margin comments don't count.)
  (if (<= n 2) n  ; Saw a ")", but it's in a pair starting in this line.
      (+ (fib (- n 1))  ; Saw a "))" MUST end line.
         (fib (- n 2)))))

;; OK. Pairs.
(print (if (< n 0.0) "negative"  ; Single ) inside group. No break.
           (= n 0.0) "zero"
           (> n 0.0) "positive"
           :else "not a number"))  ; :else is not magic; True works too.

;; OK. Avoided line breaks at single ) to show pairs.
(print (if (< n 0.0) "negative"
           (= n 0.0) "zero"
           (> n 0.0) (do (do-foo)  ; Single ) inside group. No break.
                         (do-bar)
                         "positive")
           "not a number"))  ; Implicit else is PREFERRED.

;; BAD
(print (if (< n 0.0) "negative"
           (= n 0.0) "zero"
           (and (even? n)
                (> n 0.0)) "even-positive"  ; Bad. "))" must break.
           (> n 0.0) "positive"
           "not a number"))

;; BAD
(print (if (< n 0.0) "negative"
           (= n 0.0) "zero"
           (and (even? n)
                (> n 0.0)) (do (do-foo)  ; Y U no break?
                               (do-bar)
                               "even-positive")
           (> n 0.0) "positive"
           "not a number"))

;; OK. Blank line separates multiline groups.
(print (if (< n 0.0) "negative"

           (= n 0.0) "zero"

           (and (even? n)
                (> n 0.0))
           (do (do-foo)
               (do-bar)
                "even-positive")

           (> n 0.0) "positive"

           "not a number"))

;; BAD. Groups are not separated consistently.
(print (if (< n 0.0) "negative"
           (= n 0.0) "zero"

           (> n 0.0)
           (do (do-foo)
               "positive")

           "not a number"))

;; OK. Single )'s and forms are simple enough.
(with [f (open "names.txt")]
  (-> (.read f) .strip (.replace "\"" "") (.split ",") sorted)))

;; PREFERRED. Even so, this version is much clearer.
(with [f (open "names.txt")]
  (-> (.read f)
      .strip
      (.replace "\"" "")
      (.split ",")
      sorted)))

Comments

Prefer docstrings to comments where applicable—in fn, defclass, at the top of the module, and in any other macros derived from these that can take a docstring (e.g. defmacro/g!, deftag, defn).

Docstrings contents follow the same conventions as Python.

The (comment) macro is still subject to the three laws. If you’re tempted to violate them, consider discarding a string instead with #_.

Semicolon comments always have one space between the semicolon and the start of the comment. Also, try to not comment the obvious.

Comments with more than a single word should start with a capital letter and use punctuation.

Separate sentences with a single space.

;; This commentary is not about a particular form.
;; These can span multiple lines. Limit them to column 72, per PEP 8.
;; Separate them from the next form or form comment with a blank line.

;; PREFERRED.
(setv ind (dec x))  ; Indexing starts from 0,
                                ; margin comment continues on new line.

;; OK
;; Style-compliant but just states the obvious.
(setv ind (dec x))  ; Sets index to x-1.

;; BAD
(setv ind (dec x));typing words for fun

;; Comment about the whole foofunction call.
;; These can also span mulitple lines.
(foofunction ;; Form comment about (get-arg1). Not a margin comment!
             (get-arg1)
             ;; Form comment about arg2. The indent matches.
             arg2)

Indent form comments at the same level as the form they’re commenting about; they must always start with exactly two semicolons ;;. Form comments appear directly above what they’re commenting on, never below.

General toplevel commentary is not indented; these must always start with exactly two semicolons ;; and be separated from the next form with a blank line. For long commentary, consider using a #_ applied to a string for this purpose instead.

Margin comments start two spaces from the end of the code; they must always start with a single semicolon ;. Margin comments may be continued on the next line.

When commenting out entire forms, prefer the #_ syntax. But if you do need line comments, use the more general double-colon form.

Coding Style

Pythonic Names

Use Python’s naming conventions where still applicable to Hy.

  • The first parameter of a method is self,
  • of a classmethod is cls.

Threading Macros

PREFER the threading macro or the threading tail macros when encountering deeply nested s-expressions. However, be judicious when using them. Do use them when clarity and readability improves; do not construct convoluted, hard to understand expressions.

;; BAD. Not wrong, but could be much clearer with a threading macro.
(setv NAMES
  (with [f (open "names.txt")]
    (sorted (.split (.replace (.strip (.read f))
                              "\""
                              "")
                    ","))))

;; PREFERRED. This compiles exactly the same way as the above.
(setv NAMES
  (with [f (open "names.txt")]
    (-> (.read f)
        .strip
        (.replace "\"" "")
        (.split ",")
        sorted)))

;; BAD. Probably. The macro makes it less clear in this case.
(defn square? [x]
  (->> 2
       (pow (int (sqrt x)))
       (= x)))

;; OK. Much clearer that the previous example above.
(defn square? [x]
  (-> x
      sqrt
      int
      (pow 2)
      (= x))

;; PREFERRED. Judicious use.
;; You don't have to thread everything if it improves clarity.
(defn square? [x]
  (= x (-> x sqrt int (pow 2))))

;; OK. Still clear enough with no threading macro this time.
(defn square? [x]
  (= x (pow (int (sqrt x))  ; saw a "))", break.
            2))  ; aligned with first arg to pow

Method Calls

Clojure-style dot notation is PREFERRED over the direct call of the object’s method, though both will continue to be supported.

;; PREFERRED
(with [fd (open "/etc/passwd")]
  (print (.readlines fd)))

;; OK
(with [fd (open "/etc/passwd")]
  (print (fd.readlines)))

Use More Arguments

PREFER using multiple arguments to multiple forms. But judicious use of redundant forms can clarify intent. AVOID the separating blank line for toplevel forms in this case.

;; BAD
(setv x 1)
(setv y 2)
(setv z 3)
(setv foo 9)
(setv bar 10)

;; OK
(setv x 1
      y 2
      z 3
      foo 9
      bar 10)

;; PREFERRED
(setv x 1
      y 2
      z 3)
(setv foo 9
      bar 10)

Imports

As in Python, group imports.

  • Standard library imports (including Hy’s) first.
  • Then third-party modules,
  • and finally internal modules.

PREFER one import form for each group.

PREFER alphabetical order within groups.

Require macros before any imports and group them the same way.

But sometimes imports are conditional or must be ordered a certain way for programmatic reasons, which is OK.

;; PREFERRED
(require hy.extra.anaphoric [%])
(require thirdparty [some-macro])
(require mymacros [my-macro])

(import json re)
(import numpy :as np
        pandas :as pd)
(import mymodule1)

Underscores

Prefer hyphens when separating words.

  • PREFERRED foo-bar
  • BAD foo_bar

Don’t use leading hyphens, except for “operators” or symbols meant to be read as including one, e.g. -Inf, ->foo.

Prefix private names with an underscore, not a dash. to avoid confusion with negated literals like -Inf, -42 or -4/2.

  • PREFERRED _x
  • BAD -x

Write Python’s magic “dunder” names the same as in Python. Like __init__, not --init-- or otherwise, to be consistent with the private names rule above.

Private names should still separate words using dashes instead of underscores, to be consistent with non-private parameter names and such that need the same name sans prefix, like foo-bar, not foo_bar.

  • PREFERRED _foo-bar
  • BAD _foo_bar
;; BAD
;; What are you doing?
(_= spam 2)  ; Throwing it away?
(_ 100 7)  ; i18n?

;; PREFERRED
;; Clearly subtraction.
(-= spam 2)
(- 100 7)

;; BAD
;; This looks weird.
(_>> foo bar baz)

;; PREFERRED
;; OH, it's an arrow!
(->> foo bar baz)

;; Negative x?
(setv -x 100)  ; BAD. Unless you really meant that?

;; PREFERRED
;; Oh, it's just a module private.
(setv _x 100)

;; BAD
(class Foo []
  (defn __init-- [self] ...))

;; OK
(class Foo []
  ;; Less weird?
  (defn --init-- [self] ...))

;; PREFERRED
(class Foo []
  (defn __init__ [self] ...))

;; OK, but would be module private. (No import *)
(def ->dict [&rest pairs]
  (dict (partition pairs)))

Thanks