diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 2d40326..8491f36 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -36,8 +36,7 @@ The Tao of Hy The following illustrates a brief list of design decisions that went into the making of Hy. -+ Look like a Lisp; DTRT with it (e.g. dashes turn to underscores, earmuffs - turn to all-caps). ++ Look like a Lisp; DTRT with it (e.g. dashes turn to underscores). + We're still Python. Most of the internals translate 1:1 to Python internals. + Use Unicode everywhere. + Fix the bad decisions in Python 2 when we can (see ``true_division``). @@ -51,91 +50,559 @@ into the making of Hy. Layout & Indentation ==================== -+ Avoid trailing spaces. They suck! +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 indentation. And it was glorious. -+ Indentation shall be 2 spaces (no hard tabs), except when matching - the indentation of the previous line. +The Three Laws +-------------- - .. code-block:: clj +Here's the secret: *Real Lispers don't count the brackets.* +When reading Lisp, disregard the trailing brackets--those are for the computer, not the human. +As in Python, read the code structure by indentation. +These are the three laws that make this possible. - ;; Good (and preferred) - (defn fib [n] - (if (<= n 2) - n - (+ (fib (- n 1)) (fib (- n 2))))) +1. Brackets must *never* be left alone, sad and lonesome on their own line. - ;; Still okay - (defn fib [n] - (if (<= n 2) n (+ (fib (- n 1)) (fib (- n 2))))) - - ;; Still okay - (defn fib [n] - (if (<= n 2) - n - (+ (fib (- n 1)) (fib (- n 2))))) - - ;; Hysterically ridiculous - (defn fib [n] - (if (<= n 2) - n ;; yes, I love randomly hitting the space key - (+ (fib (- n 1)) (fib (- n 2))))) - - -+ Parentheses must *never* be left alone, sad and lonesome on their own - line. - - .. code-block:: clj + .. code-block:: clj ;; Good (and preferred) (defn fib [n] (if (<= n 2) n - (+ (fib (- n 1)) (fib (- n 2))))) + (+ (fib (- n 1)) + (fib (- n 2))))) ;; Hysterically ridiculous - (defn fib [n] + (defn fib [ + n + ] ; my eyes! (if (<= n 2) n (+ (fib (- n 1)) (fib (- n 2))) ) ) ; GAH, BURN IT WITH FIRE +2. New lines must *always* be indented past their parent opening bracket. -+ Inline comments shall be two spaces from the end of the code; they - must always have a space between the comment character and the start - of the comment. Also, try to not comment the obvious. + .. code-block:: clj + + ;; Acceptable + (foo (, arg1 + arg2)) + + ;; Unacceptable + (foo (, arg1 + arg2)) ; Doesn't go far enough. + + ;; Look at what happens if we remove the trailing brackets from the above examples. + ;; Can you tell where they go? + + (foo (, arg1 + arg2 + + (foo (, arg1 + arg2 + + ;; Judging by indentation, this is where the brackets should go. + + (foo (, arg1 + arg2)) + + (foo (, arg1) ; not what we started with, is it? + arg2) + + ;; No, it's not at all obvious it should have gone the other way. + + (fn [arg + arg + + (fn [arg] + arg) + + ;; Beware of brackets with reader syntax. You still have to indent past them. + + ;; NO! + `#{(foo) + ~@[(bar) + 1 2]} + + ;; Good. + `#{(foo) + ~@[(bar) + 1 + 2]} + +3. New lines must *never* be indented past the previous element's opening bracket. + + .. code-block:: clj + + ;; 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)) + + ;; acceptable + ((get-fn q) x ; the ")" on this line isn't trailing. + y) + + ;; preferred, since the ) should end the line. + ((get-fn q) + x + y) + +Furthermore +----------- + ++ Avoid trailing spaces. They suck! + ++ Limit lines to 100 characters. + ++ Line up arguments to function calls when splitting over multiple lines. + + .. code-block:: clj + + (foofunction arg1 + (barfunction bararg1 + bararg2 + bararg3) ; aligned with bararg1 + arg3) + + (foofunction arg1 + (barfunction bararg1 + bararg2) ; Wrong. Looks like a macro body. + arg3) + + (foofunction arg1 + (barfunction bararg1 bararg2 bararg3) ; acceptable. + arg3) + + ;; indenting one space past the parent bracket is acceptable for long lines + (foofunction + arg1 ; acceptable, but better to keep it on the same line as foofunction + (barfunction + bararg1 ; indent again + bararg2 + bararg3) + arg3) ; aligned with arg1 + ++ If you need to separate a bracket trail use a ``#_ /`` comment to hold it open. + (This avoids violating law #1.) + + .. code-block:: clj + + ;; There are basically two reasons to do this--long lists under version control, + ;; and when commenting out the final element during testing. + ;; (Common Lisp might use #+(or) for this.) + + ;; preferred + [(foo) + (bar) + (baz)] + + ;; Acceptable if the list is long. (Three isn't that long though.) + ;; This is better for version control line diffs. + [ + (foo) + (bar) + (baz) + #_ /] + + ;; Unacceptable and an syntax error. Lost a bracket. + [(foo) + ;; (bar) + ;; (baz)] + + ;; Unacceptable. Broke law #1. + [(foo) + ;; (bar) + ;; (baz) + ] + + ;; preferred + [(foo) + #_(bar) + #_(baz)] + + ;; acceptable + [(foo) + #_ + (bar) + #_ + (baz)] + + ;; acceptable + [(foo) + ;; (bar) + ;; (baz) + #_ /] + ++ Brackets like to snuggle, don't leave them out in the cold! + + .. code-block:: clj + + ;;; Good + [1 2 3] + (foo (bar 2)) + + ;;; Bad + [ 1 2 3 ] + ( foo ( bar 2 ) ) + + ;;; Ugly + [ 1 2 3] + (foo( bar 2) ) + ++ Use whitespace to show implicit groups, but be consistent within a form. + + .. code-block:: clj + + ;; Older Lisps would always wrap such groups in even more parentheses. + ;; But Hy takes after Clojure, which has a lighter touch. + + {1 9 + 2 8 + 3 7 + 4 6 + 5 5} ; newlines show key-value pairs in dict + + ;; This grouping makes no sense. + #{1 2 + 3 4} ; It's a set, so why are there pairs? + + ;; This grouping also makes no sense. + [1 + 1 2 + 1 2 3] ; wHy do you like random patterns? [sic pun, sorry] + + ;; BAD. Can't tell key from value without counting + {1 9 2 8 3 7 4 6 5 5} + + ;; Good. Extra spaces can work too, if it fits on one line. + {1 9 2 8 3 7 4 6 5 5} + + ;; Be consistent. Separate all groups the same way in a form. + + {1 9 2 8 + 3 7 4 6 5 5} ; Pick one or the other! + {1 9 2 8 3 7 4 6 5 5} ; You forgot something. + + ;; Groups of one must also be consistent. + + (foo 1 2 3} ; No need for extra spaces here. + (foo 1 + 2 + 3} ; Also acceptable, but you could have fit this on one line. + [1 + 2] ; same + (foo 1 2 ; This isn't a pair? + 3) ; Lines or spaces--pick one or the other! + + (foofunction (make-arg) + (get-arg) + #tag(do-stuff) ; Tags belong with what they tag. + #* args ; #* goes with what it unpacks. + #** kwargs) + + ;; Yep, those are pairs too. + (setv x 1 + y 2) + ++ Macros and special forms can have "special" arguments that are indented like function arguments. + ++ Indent the non-special arguments (usually the body) one space past the parent bracket. + + .. code-block:: clj + + (assoc foo ; foo is special + "x" 1 ; remaining args are not special. Indent 2 spaces. + "y" 2) + + ;; The do form has no special args. Indent like a function call. + (do (foo) + (bar) + (baz)) + + ;; No special args to distinguish, so this is also valid function indent. + (do + (foo) + (bar) + (baz)) + + ;; Preferred. + (defn fib [n] + (if (<= n 2) + n + (+ (fib (- n 1)) ; else clause is not special, but aligning it is OK. + (fib (- n 2))))) + + (defn fib + [n] ; name and argslist are special. Indent like function args. + ;; defn body is not special. Indent 1 space past parent bracket. + (if (<= n 2) + n ; elif pairs are special, indent like function args + (+ (fib (- n 1)) ; else clause is not special. Indent 1 space past parent bracket. + (fib (- n 2))))) + + ++ Removing whitespace can also make groups clearer. + + .. code-block:: clj + + ;;; lookups + ;; acceptable + (. foo ["bar"]) + ;; preferred + (. foo["bar"]) + + ;; Bad. Doesn't show groups clearly + (import foo foo [spam :as sp eggs :as eg] bar bar [bacon]) + + ;; Acceptable. 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]) + + ;; Acceptable. Newlines show groups. + (import foo + foo [spam :as sp + eggs :as eg] + bar + bar [bacon]) + ;; Preferred, since 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. + + (foofunction #tag "foo" #tag (foo) #* (get-args)) + + (foofunction #tag"foo" #tag(foo) #*(get-args)) + + ;; Can't group these by removing whitespace, so use extra spaces instead. + (foofunction #x foo #x bar #* args) + + ;; Same idea. + (foofunction #x foo + #x bar + #* args) + + ;; Acceptable, but you don't need to separate function name from first arg. + (foofunction #x foo #x bar #* args) + + ;; Same idea. Keeping the first group on the same line as the function name is preferable. + (foofunction + #x foo + #x bar + #* args) + + ;; OK. It's still clear what this is tagging. And you don't have to re-indent. + #_ + (def foo [] + stuff) + + ;; also OK, but more work. + #_(def foo [] + stuff) + + ;; Not OK, you messed up the indent and broke law #2. + #_(def foo [] + stuff) + + ;; Not OK, keep the tag grouped with its argument. + #_ + + (def foo [] + stuff) + ++ Any closing bracket(s) (of any kind) must end the line, + unless it's in the middle of an implicit group that started on the line. + + .. code-block:: clj + + ;; One-liners are overrated. + ;; Maybe OK if you're just typing into the REPL. + (defn fib [n] (if (<= n 2) n (+ (fib (- n 1)) (fib (- n 2))))) ; too hard to read! + + ;; getting better. + (defn fib [n] + (if (<= n 2) + n + (+ (fib (- n 1)) (fib (- n 2))))) ; still too hard on this line + + ;; How to do it. + (defn fib [n] ; Saw a "]", newline. + (if (<= n 2) n ; Saw a ")", but leave it since it's in a semantic pair starting in this line. + (+ (fib (- n 1)) ; Saw a "))" line break. + (fib (- n 2))))) + + ;; Acceptable. Pairs. + (print (if (< n 0.0) "negative" + (= n 0.0) "zero" + (> n 0.0) "positive" + :else "not a number")) ; :else is not magic; True would work also. + + ;; Bad. Doesn't separate groups. + (print (if (< n 0.0) + "negative" + (= n 0.0) + "zero" + (> n 0.0) + "positive" + "not a number")) + + ;; This is also acceptable. + (print (if (< n 0.0) "negative" + (= n 0.0) "zero" + (> n 0.0) (do (do-foo) ; Group started this line, so didn't break. + (do-bar) + "positive") + "not a number")) ; :else is implied for the last one. + + ;; Bad. + (print (if (< n 0.0) "negative" + (= n 0.0) "zero" + (and (even? n) + (> n 0.0)) "even-positive" ; Group not started this line! Should break on "))" + (> n 0.0) "positive" + "not a number")) + + ;; Worse. + (print (if (< n 0.0) "negative" + (= n 0.0) "zero" + (and (even? n) + (> n 0.0)) (do (do-foo) ; Group not started this line. + (do-bar) + "even-positive") + (> n 0.0) "positive" + "not a number")) + + ;; Good. Blank line separates 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")) + + ;; Not so good, 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")) + + ;; Acceptable. All groups are separated the same way, with a blank like. + (print (if (< n 0.0) "negative" + + (= n 0.0) "zero" + + (> n 0.0) + (do (do-foo) + "positive") + + "not a number")) + + (defn fib [n] ; saw a "]", newline. + (if (<= n 2) ; OK to break here. Since there's only one pair, we don't have to separate them. + n + (+ (fib (- n 1)) ; non-special indent is another whitespace separation technique. + (fib (- n 2))))) + +Comments +-------- + +Prefer docstrings to comments where applicable--in ``defn``, ``defclass``, and at the top of the module. + +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 shall start with some number of semicolons +and have a space between the semicolons and the start of the comment. +Also, try to not comment the obvious. .. code-block:: clj - ;; Good - (setv ind (dec x)) ; indexing starts from 0 + ;;;; Major Header Labeling a Major Section + ;; Headers should only be one line. + ;; This is non-header commentary, but not about a particular form. + ;; These can span multiple lines. + ;; These are separated from the next form or form comment by a blank line. - ;; Style-compliant but just states the obvious - (setv ind (dec x)) ; sets index to x-1 + ;; Good. + (setv ind (dec x)) ; indexing starts from 0 + ; margin comment continues on the next line. - ;; Bad - (setv ind (dec x));typing words for fun + ;; 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 + + ;;; Minor Header Comment Labeling a Minor Section + + ;; 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) + + ;;;; Footer + + +Header comments shall not be indented, and shall appear only at the toplevel outside of any form. +They must always begin with at least three semicolons--usually ``;;;`` for minor and ``;;;;`` for major headings. +(Emacs recognizes these as headers.) + +Form comments shall be indented 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 shall not be indented; +they 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 shall be 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, +since they're not headers that should appear in the outline, +nor are they margin comment continuations that should be indented automatically. Coding Style ============ -+ Do not use s-expression syntax where vector syntax is intended. - For instance, the fact that the former of these two examples works - is just because the compiler isn't overly strict. In reality, the - correct syntax in places such as this is the latter. - - .. code-block:: clj - - ;; Bad (and evil) - (defn foo (x) (print x)) - (foo 1) - - ;; Good (and preferred) - (defn foo [x] (print x)) - (foo 1) - - + Use 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 @@ -143,19 +610,45 @@ Coding Style .. code-block:: clj - ;; Preferred - (setv *names* - (with [f (open "names.txt")] - (-> (.read f) (.strip) (.replace "\"" "") (.split ",") (sorted)))) + ;; Not so good. + (setv *names* + (with [f (open "names.txt")] + (sorted (.split (.replace (.strip (.read f)) + "\"" + "") + ",")))) - ;; Not so good - (setv *names* - (with [f (open "names.txt")] - (sorted (.split "," (.replace "\"" "" (.strip (.read f))))))) + ;; Preferred. + (setv *names* + (with [f (open "names.txt")] + (-> (.read f) + .strip + (.replace "\"" "") + (.split ",") + sorted))) - ;; Probably not a good idea - (setv square? [x] - (->> 2 (pow (int (sqrt x))) (= x))) + ;; Probably not a good idea. + (setv square? [x] + (->> 2 + (pow (int (sqrt x))) + (= x))) + + ;; better + (setv square? [x] + (-> x + sqrt + int + (pow 2) + (= x)) + + ;; good + (setv square? [x] + (= x (-> x sqrt int (pow 2)))) + + ;; still OK + (setv square? [x] + (= x (pow (int (sqrt x)) + 2)) + Clojure-style dot notation is preferred over the direct call of @@ -163,14 +656,55 @@ Coding Style .. code-block:: clj - ;; Good + ;; Good. (with [fd (open "/etc/passwd")] (print (.readlines fd))) - ;; Not so good + ;; Not so good. (with [fd (open "/etc/passwd")] (print (fd.readlines))) ++ Prefer hyphens when separating words. ``foo-bar``, not ``foo_bar``. + ++ Don't use leading hyphens, except for "operators". + + .. code-block:: clj + + ;; Clearly subtraction. + (-= spam 2) + (- 100 7) + + ;; What are you doing? + (_= spam 2) + (_ 100 7) + + ;; This looks weird. + (_>> foo bar baz) + + ;; OH, it's an arrow! + (->> foo bar baz) + + ;; Negative spam??? + (setv -spam 100) + + ;; Oh, it's just a module private. + (setv _spam 100) + + (class Foo [] + ;; Also weird. + (defn __init-- [self] ...)) + + (class Foo [] + ;; Less weird? + (defn --init-- [self] ...)) + + (class Foo [] + ;; Preferred! + (defn __init__ [self] ...)) + + ;; This kind of name is OK, but would be module private. (No import *) + (def ->dict [&rest pairs] + (dict (partition pairs))) Conclusion ========== @@ -188,10 +722,18 @@ importantly, have fun with Hy. Thanks ====== -+ This guide is heavily inspired from `@paultag`_ 's blog post `Hy - Survival Guide`_ ++ This guide is heavily inspired from `@paultag`_ 's blog post `Hy Survival Guide`_ + The `Clojure Style Guide`_ ++ `Parinfer`_ and `Parlinter`_ (the three laws) ++ The Community Scheme Wiki `scheme-style`_ (ending bracket ends the line) ++ GNU Emacs Lisp Reference Manual `Comment-Tips`_ (how many semicolons?) ++ `Riastradh's Lisp Style Rules`_ .. _`Hy Survival Guide`: https://notes.pault.ag/hy-survival-guide/ .. _`Clojure Style Guide`: https://github.com/bbatsov/clojure-style-guide .. _`@paultag`: https://github.com/paultag +.. _`Parinfer`: https://shaunlebron.github.io/parinfer/ +.. _`Parlinter`: https://github.com/shaunlebron/parlinter +.. _`scheme-style`: http://community.schemewiki.org/?scheme-style +.. _`Comment-Tips`: https://www.gnu.org/software/emacs/manual/html_node/elisp/Comment-Tips.html +.. _`Riastradh's Lisp Style Rules`: http://mumble.net/~campbell/scheme/style.txt