8000 GitHub - Engelberg/better-cond: A version of cond that supports :let clauses, and a number of other conveniences.
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

A version of cond that supports :let clauses, and a number of other conveniences.

License

Notifications You must be signed in to change notification settings

Engelberg/better-cond

< 8000 script type="application/json" data-target="react-partial.embeddedData">{"props":{"initialPayload":{"allShortcutsEnabled":false,"path":"/","repo":{"id":62122896,"defaultBranch":"master","name":"better-cond","ownerLogin":"Engelberg","currentUserCanPush":false,"isFork":false,"isEmpty":false,"createdAt":"2016-06-28T08:16:28.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/138571?v=4","public":true,"private":false,"isOrgOwned":false},"currentUser":null,"refInfo":{"name":"master","listCacheKey":"v0:1657171905.018914","canEdit":false,"refType":"branch","currentOid":"fda59f9faa456d3ac45ae55ab9682e6bdce3b9d0"},"tree":{"items":[{"name":".clj-kondo","path":".clj-kondo","contentType":"directory"},{"name":".github/workflows","path":".github/workflows","contentType":"directory","hasSimplifiedPath":true},{"name":"resources/clj-kondo.exports/better-cond/better-cond","path":"resources/clj-kondo.exports/better-cond/better-cond","contentType":"directory","hasSimplifiedPath":true},{"name":"src/better_cond","path":"src/better_cond","contentType":"directory","hasSimplifiedPath":true},{"name":"test/better_cond","path":"test/better_cond","contentType":"directory","hasSimplifiedPath":true},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"CHANGES.md","path":"CHANGES.md","contentType":"file"},{"name":"LICENSE","path":"LICENSE","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"deps.edn","path":"deps.edn","contentType":"file"},{"name":"project.clj","path":"project.clj","contentType":"file"}],"templateDirectorySuggestionUrl":null,"readme":null,"totalCount":11,"showBranchInfobar":false},"fileTree":null,"fileTreeProcessingTime":null,"foldersToFetch":[],"treeExpanded":false,"symbolsExpanded":false,"isOverview":true,"overview":{"banners":{"shouldRecommendReadme":false,"isPersonalRepo":false,"showUseActionBanner":false,"actionSlug":null,"actionId":null,"showProtectBranchBanner":false,"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_repo","releasePath":"/Engelberg/better-cond/releases/new?marketplace=true","showPublishActionBanner":false},"interactionLimitBanner":null,"showInvitationBanner":false,"inviterName":null,"actionsMigrationBannerInfo":{"releaseTags":[],"showImmutableActionsMigrationBanner":false,"initialMigrationStatus":null}},"codeButton":{"contactPath":"/contact","isEnterprise":false,"local":{"protocolInfo":{"httpAvailable":true,"sshAvailable":null,"httpUrl":"https://github.com/Engelberg/better-cond.git","showCloneWarning":null,"sshUrl":null,"sshCertificatesRequired":null,"sshCertificatesAvailable":null,"ghCliUrl":"gh repo clone Engelberg/better-cond","defaultProtocol":"http","newSshKeyUrl":"/settings/ssh/new","setProtocolPath":"/users/set_protocol"},"platformInfo":{"cloneUrl":"https://desktop.github.com","showVisualStudioCloneButton":false,"visualStudioCloneUrl":"https://windows.github.com","showXcodeCloneButton":false,"xcodeCloneUrl":"xcode://clone?repo=https%3A%2F%2Fgithub.com%2FEngelberg%2Fbetter-cond","zipballUrl":"/Engelberg/better-cond/archive/refs/heads/master.zip"}},"newCodespacePath":"/codespaces/new?hide_repo_select=true\u0026repo=62122896"},"popovers":{"rename":null,"renamedParentRepo":null},"commitCount":"76","overviewFiles":[{"displayName":"README.md","repoName":"better-cond","refName":"master","path":"README.md","preferredFileType":"readme","tabName":"README","richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003ebetter-cond\u003c/h1\u003e\u003ca id=\"user-content-better-cond\" class=\"anchor\" aria-label=\"Permalink: better-cond\" href=\"#better-cond\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eA variation on cond which sports let bindings, when-let bindings, when-some bindings, when, do and implicit else for Clojure and Clojurescript.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cem\u003eNew in version 2.0 and above:\u003c/em\u003e\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eCond supports \u003ccode\u003edo\u003c/code\u003e for a single-line side effect.\u003c/li\u003e\n\u003cli\u003eCond supports \u003ccode\u003ewhen-some\u003c/code\u003e (like \u003ccode\u003ewhen-let\u003c/code\u003e but tests for non-nil).\u003c/li\u003e\n\u003cli\u003eCond allows symbols as an alternative to keywords for let, when-let, when-some, when, and do.\u003c/li\u003e\n\u003cli\u003eTwo new macros: \u003ccode\u003edefnc\u003c/code\u003e and \u003ccode\u003edefnc-\u003c/code\u003e are like \u003ccode\u003edefn\u003c/code\u003e and \u003ccode\u003edefn-\u003c/code\u003e with an implicit cond wrapped around the body.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003ebetter-cond 2.1.5\u003c/code\u003e requires Clojure 1.9 alpha 16 or higher. If you are still on Clojure 1.8, use \u003ccode\u003ebetter-cond 1.0.1\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eUsage\u003c/h2\u003e\u003ca id=\"user-content-usage\" class=\"anchor\" aria-label=\"Permalink: Usage\" href=\"#usage\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAdd the following line to your leiningen dependencies:\u003c/p\u003e\n\u003cdiv class=\"snippet-clipboard-content notranslate position-relative overflow-auto\" data-snippet-clipboard-copy-content=\"[better-cond \u0026quot;2.1.5\u0026quot;]\"\u003e\u003cpre class=\"notranslate\"\u003e\u003ccode\u003e[better-cond \"2.1.5\"]\n\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eRequire better-cond in your namespace header:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" (ns example.core\n (:require [better-cond.core :as b]))\n\n (b/cond\n (odd? a) 1\n\n :let [a (quot a 2)]\n ; a has been rebound to the result of (quot a 2) for the remainder\n ; of this cond.\n\n :when-let [x (fn-which-may-return-falsey a),\n y (fn-which-may-return-falsey (* 2 a))]\n ; this when-let binds x and y for the remainder of the cond and\n ; bails early with nil unless x and y are both truthy\n\n :when-some [b (fn-which-may-return-nil x),\n \t c (fn-which-may-return-nil y)]\n ; this when-some binds b and c for the remainder of the cond and\n ; bails early with nil unless b and c are both not nil\n\n :when (seq x)\n ; the above when bails early with nil unless (seq x) is truthy\n ; it could have been written as: (not (seq x)) nil\n\n :do (println x)\n ; A great way to put a side-effecting statement, like a println\n ; into the middle of a cond\n\n (odd? (+ x y)) 2\n\n 3)\n ; This version of cond lets you have a single trailing element\n ; which is treated as a final :else clause.\n ; Stylistically, I recommend explicitly using :else unless\n ; the previous line is a :let, :when-let, or :when-some clause, in which\n ; case the implicit else tends to look more natural.\"\u003e\u003cpre\u003e (\u003cspan class=\"pl-k\"\u003ens\u003c/span\u003e \u003cspan class=\"pl-e\"\u003eexample.core\u003c/span\u003e\n (\u003cspan class=\"pl-c1\"\u003e:require\u003c/span\u003e [better-cond.core \u003cspan class=\"pl-c1\"\u003e:as\u003c/span\u003e b]))\n\n (\u003cspan class=\"pl-en\"\u003eb/cond\u003c/span\u003e\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e a) \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e\n\n \u003cspan class=\"pl-c1\"\u003e:let\u003c/span\u003e [a (\u003cspan class=\"pl-en\"\u003equot\u003c/span\u003e a \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e)]\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e a has been rebound to the result of (quot a 2) for the remainder\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e of this cond.\u003c/span\u003e\n\n \u003cspan class=\"pl-c1\"\u003e:when-let\u003c/span\u003e [x (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e a),\n y (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e a))]\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e this when-let binds x and y for the remainder of the cond and\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e bails early with nil unless x and y are both truthy\u003c/span\u003e\n\n \u003cspan class=\"pl-c1\"\u003e:when-some\u003c/span\u003e [b (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e x),\n \t c (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e y)]\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e this when-some binds b and c for the remainder of the cond and\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e bails early with nil unless b and c are both not nil\u003c/span\u003e\n\n \u003cspan class=\"pl-c1\"\u003e:when\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eseq\u003c/span\u003e x)\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e the above when bails early with nil unless (seq x) is truthy\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e it could have been written as: (not (seq x)) nil\u003c/span\u003e\n\n \u003cspan class=\"pl-c1\"\u003e:do\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e x)\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e A great way to put a side-effecting statement, like a println\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e into the middle of a cond\u003c/span\u003e\n\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e+\u003c/span\u003e x y)) \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e\n\n \u003cspan class=\"pl-c1\"\u003e3\u003c/span\u003e)\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e This version of cond lets you have a single trailing element\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e which is treated as a final :else clause.\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e Stylistically, I recommend explicitly using :else unless\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e the previous line is a :let, :when-let, or :when-some clause, in which\u003c/span\u003e\n \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e case the implicit else tends to look more natural.\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eor alternatively, use it:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" (ns example.core\n (:refer-clojure :exclude [cond])\n (:require [better-cond.core :refer [cond]]))\n\n (cond\n (odd? a) 1\n :let [a (quot a 2)]\n :when-let [x (fn-which-may-return-falsey a),\n y (fn-which-may-return-falsey (* 2 a))]\n :when-some [b (fn-which-may-return-nil x),\n c (fn-which-may-return-nil y)]\n :when (seq x)\n :do (println x)\n (odd? (+ x y)) 2\n 3)\"\u003e\u003cpre\u003e (\u003cspan class=\"pl-k\"\u003ens\u003c/span\u003e \u003cspan class=\"pl-e\"\u003eexample.core\u003c/span\u003e\n (\u003cspan class=\"pl-c1\"\u003e:refer-clojure\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e:exclude\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e])\n (\u003cspan class=\"pl-c1\"\u003e:require\u003c/span\u003e [better-cond.core \u003cspan class=\"pl-c1\"\u003e:refer\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e]]))\n\n (\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e a) \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e\n \u003cspan class=\"pl-c1\"\u003e:let\u003c/span\u003e [a (\u003cspan class=\"pl-en\"\u003equot\u003c/span\u003e a \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e)]\n \u003cspan class=\"pl-c1\"\u003e:when-let\u003c/span\u003e [x (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e a),\n y (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e a))]\n \u003cspan class=\"pl-c1\"\u003e:when-some\u003c/span\u003e [b (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e x),\n c (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e y)]\n \u003cspan class=\"pl-c1\"\u003e:when\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eseq\u003c/span\u003e x)\n \u003cspan class=\"pl-c1\"\u003e:do\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e x)\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e+\u003c/span\u003e x y)) \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e\n \u003cspan class=\"pl-c1\"\u003e3\u003c/span\u003e)\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIn Clojurescript, it is best to use \u003ccode\u003e:require-macros\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" (ns example.core\n (:refer-clojure :exclude [cond])\n (:require-macros [better-cond.core :refer [cond]]))\"\u003e\u003cpre\u003e (\u003cspan class=\"pl-k\"\u003ens\u003c/span\u003e \u003cspan class=\"pl-e\"\u003eexample.core\u003c/span\u003e\n (\u003cspan class=\"pl-c1\"\u003e:refer-clojure\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e:exclude\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e])\n (\u003cspan class=\"pl-c1\"\u003e:require-macros\u003c/span\u003e [better-cond.core \u003cspan class=\"pl-c1\"\u003e:refer\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e]]))\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAs of version 2.0.0, writing let, when-let, when-some, when, and do as keywords is optional. So you can also write it like this, if you prefer:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" (cond\n (odd? a) 1\n let [a (quot a 2)]\n when-let [x (fn-which-may-return-falsey a),\n y (fn-which-may-return-falsey (* 2 a))]\n when-some [b (fn-which-may-return-nil x),\n c (fn-which-may-return-nil y)]\n when (seq x)\n do (println x)\n (odd? (+ x y)) 2\n 3)\"\u003e\u003cpre\u003e (\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e a) \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003elet\u003c/span\u003e [a (\u003cspan class=\"pl-en\"\u003equot\u003c/span\u003e a \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e)]\n \u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e [x (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e a),\n y (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e a))]\n \u003cspan class=\"pl-k\"\u003ewhen-some\u003c/span\u003e [b (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e x),\n c (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e y)]\n \u003cspan class=\"pl-k\"\u003ewhen\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eseq\u003c/span\u003e x)\n \u003cspan class=\"pl-k\"\u003edo\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e x)\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e+\u003c/span\u003e x y)) \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e\n \u003cspan class=\"pl-c1\"\u003e3\u003c/span\u003e)\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAfter trying it both ways in my code, I've come to prefer writing them as keywords, but both forms will continue to be supported.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe \u003ccode\u003edefnc\u003c/code\u003e and \u003ccode\u003edefnc-\u003c/code\u003e macros behave like Clojure's built-in \u003ccode\u003edefn\u003c/code\u003e and \u003ccode\u003edefn-\u003c/code\u003e, but they implicitly wrap the body of the function in \u003ccode\u003econd\u003c/code\u003e, saving you another level of indenting.\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(defnc f [a]\n (odd? a) 1\n let [a (quot a 2)]\n when-let [x (fn-which-may-return-falsey a),\n y (fn-which-may-return-falsey (* 2 a))]\n when-some [b (fn-which-may-return-nil x),\n c (fn-which-may-return-nil y)]\n when (seq x)\n do (println x)\n (odd? (+ x y)) 2\n 3)\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e \u003cspan class=\"pl-e\"\u003ef\u003c/span\u003e [a]\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e a) \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e\n \u003cspan class=\"pl-e\"\u003elet\u003c/span\u003e [a (\u003cspan class=\"pl-en\"\u003equot\u003c/span\u003e a \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e)]\n \u003cspan class=\"pl-e\"\u003ewhen-let\u003c/span\u003e [x (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e a),\n y (\u003cspan class=\"pl-en\"\u003efn-which-may-return-falsey\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e a))]\n \u003cspan class=\"pl-e\"\u003ewhen-some\u003c/span\u003e [b (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e x),\n c (\u003cspan class=\"pl-en\"\u003efn-which-may-return-nil\u003c/span\u003e y)]\n \u003cspan class=\"pl-e\"\u003ewhen\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eseq\u003c/span\u003e x)\n \u003cspan class=\"pl-e\"\u003edo\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e x)\n (\u003cspan class=\"pl-en\"\u003eodd?\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003e+\u003c/span\u003e x y)) \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e\n \u003cspan class=\"pl-c1\"\u003e3\u003c/span\u003e)\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eBecause this \u003ccode\u003econd\u003c/code\u003e has an implicit else, you can use \u003ccode\u003edefnc\u003c/code\u003e for almost all functions you would have created with \u003ccode\u003edefn\u003c/code\u003e, even those that do not actually use cond.\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(defnc f [x] (* x 2)) ; This works as expected\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e \u003cspan class=\"pl-e\"\u003ef\u003c/span\u003e [x] (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e x \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e)) \u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e This works as expected\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe only time you wouldn't want to use \u003ccode\u003edefnc\u003c/code\u003e is when you are taking advantage of the implicit do offered by \u003ccode\u003edefn\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(defn f [x]\n (println x)\n (* x 2))\n\n; The above makes use of defn's implicit do, but if desired,\n; could be rewritten with defnc as:\n\n(defnc f [x]\n do (println x)\n (* x 2))\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003edefn\u003c/span\u003e \u003cspan class=\"pl-e\"\u003ef\u003c/span\u003e [x]\n (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e x)\n (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e x \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e))\n\n\u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e The above makes use of defn's implicit do, but if desired,\u003c/span\u003e\n\u003cspan class=\"pl-c\"\u003e\u003cspan class=\"pl-c\"\u003e;\u003c/span\u003e could be rewritten with defnc as:\u003c/span\u003e\n\n(\u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e \u003cspan class=\"pl-e\"\u003ef\u003c/span\u003e [x]\n \u003cspan class=\"pl-e\"\u003edo\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e x)\n (\u003cspan class=\"pl-en\"\u003e*\u003c/span\u003e x \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e))\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eI personally tend to write everything with \u003ccode\u003edefnc\u003c/code\u003e now, as it makes it easier to insert let bindings and conditional responses later. \u003ccode\u003edefnc\u003c/code\u003e is implemented using the spec for Clojure's built-in \u003ccode\u003edefn\u003c/code\u003e, so it can handle all the same things: multiple arities, pre/post-map, metadata map, docstring, etc.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIn order to support multiple bindings in cond's :when-let and :when-some clauses, better-cond.core also contains a version of \u003ccode\u003eif-let\u003c/code\u003e, \u003ccode\u003eif-some\u003c/code\u003e, \u003ccode\u003ewhen-let\u003c/code\u003e, and \u003ccode\u003ewhen-some\u003c/code\u003e which can take multiple name-expression pairs in the binding vector (the ones built into Clojure can only take a single name and expression). The test passes only when all the names evaluate to something truthy (or non-nil for if-some/when-some). You may find it useful to use better-cond's \u003ccode\u003eif-let\u003c/code\u003e, \u003ccode\u003eif-some\u003c/code\u003e, \u003ccode\u003ewhen-let\u003c/code\u003e, and \u003ccode\u003ewhen-some\u003c/code\u003e directly.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAs with \u003ccode\u003econd\u003c/code\u003e, if you use \u003ccode\u003eif-let\u003c/code\u003e, \u003ccode\u003eif-some\u003c/code\u003e, \u003ccode\u003ewhen-let\u003c/code\u003e, or \u003ccode\u003ewhen-some\u003c/code\u003e you'll need to qualify with the namespace or namespace alias (i.e., \u003ccode\u003eb/if-let\u003c/code\u003e, \u003ccode\u003eb/when-let\u003c/code\u003e, \u003ccode\u003eb/when-some\u003c/code\u003e) or you'll need to exclude the Clojure version from your namespace:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" (ns example.core\n (:refer-clojure :exclude [cond if-let if-some when-let when-some])\n (:require [better-cond.core :refer [cond if-let if-some when-let when-some defnc defnc-]]))\"\u003e\u003cpre\u003e (\u003cspan class=\"pl-k\"\u003ens\u003c/span\u003e \u003cspan class=\"pl-e\"\u003eexample.core\u003c/span\u003e\n (\u003cspan class=\"pl-c1\"\u003e:refer-clojure\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e:exclude\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-some\u003c/span\u003e])\n (\u003cspan class=\"pl-c1\"\u003e:require\u003c/span\u003e [better-cond.core \u003cspan class=\"pl-c1\"\u003e:refer\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefnc-\u003c/span\u003e]]))\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eYou could also \u003ccode\u003e:refer :all\u003c/code\u003e if you are on Clojure and not Clojurescript. If you want the whole shebang, and you want to replace Clojure's defn with defnc, your namespace header would look like this:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\" (ns example.core\n (:refer-clojure :exclude [cond if-let if-some when-let when-some defn defn-])\n (:require [better-cond.core :refer [cond if-let if-some when-let when-some defnc defnc-]\n\t :rename {defnc defn, defnc- defn-}]))\"\u003e\u003cpre\u003e (\u003cspan class=\"pl-k\"\u003ens\u003c/span\u003e \u003cspan class=\"pl-e\"\u003eexample.core\u003c/span\u003e\n (\u003cspan class=\"pl-c1\"\u003e:refer-clojure\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003e:exclude\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefn\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefn-\u003c/span\u003e])\n (\u003cspan class=\"pl-c1\"\u003e:require\u003c/span\u003e [better-cond.core \u003cspan class=\"pl-c1\"\u003e:refer\u003c/span\u003e [\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eif-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e \u003cspan class=\"pl-k\"\u003ewhen-some\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefnc-\u003c/span\u003e]\n\t \u003cspan class=\"pl-c1\"\u003e:rename\u003c/span\u003e {\u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e defn, \u003cspan class=\"pl-k\"\u003edefnc-\u003c/span\u003e \u003cspan class=\"pl-k\"\u003edefn-\u003c/span\u003e}]))\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e(As of the time of this writing, Cursive \u003ca href=\"https://github.com/cursive-ide/cursive/issues/1544\" data-hovercard-type=\"issue\" data-hovercard-url=\"/cursive-ide/cursive/issues/1544/hovercard\"\u003edoes not have code completion or adjustable indenting for symbols that have been renamed from other namespaces\u003c/a\u003e.)\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eI use this library on a daily basis, and it is hugely useful in preventing the code from getting deeply nested, helping to make the code dramatically clearer. Try it -- you'll be hooked.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThis is a feature that has been discussed since the early days of Clojure. There was a \u003ca href=\"http://dev.clojure.org/jira/browse/CLJ-200\" rel=\"nofollow\"\u003eJIRA issue for this\u003c/a\u003e for seven years.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eKnown Issue\u003c/h2\u003e\u003ca id=\"user-content-known-issue\" class=\"anchor\" aria-label=\"Permalink: Known Issue\" href=\"#known-issue\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003edefnc\u003c/code\u003e and \u003ccode\u003edefnc-\u003c/code\u003e macros do not preserve primitive type hint info on return value of function. Type hints on function's arguments work fine. See \u003ca href=\"https://dev.clojure.org/jira/browse/CLJ-2381\" rel=\"nofollow\"\u003ehttps://dev.clojure.org/jira/browse/CLJ-2381\u003c/a\u003e. Until this issue is resolved, don't use \u003ccode\u003edefnc\u003c/code\u003e or \u003ccode\u003edefnc-\u003c/code\u003e when you need a primitive return type. It works fine on primitive inputs, just not the return value.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eRationale\u003c/h2\u003e\u003ca id=\"user-content-rationale\" class=\"anchor\" aria-label=\"Permalink: Rationale\" href=\"#rationale\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eAs a heavy user of Clojure since its first release, I have observed in my own code and in others' that one of the biggest sources of deeply-nested code and creep-to-the-right indenting is an alternation of lets and if/cond tests.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIn Clojure, we tend not to use looping constructs nearly as much as in mainstream languages, because higher-order functions like map, filter, and reduce handle the vast majority of our looping needs. This means that many Clojure functions are simply expressed as a series of name bindings (let) and conditional tests (cond), so alleviating this source of indenting has a bi 8000 g payoff.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eAs a teacher of Scheme and Clojure for many years, I've noticed that when newcomers balk at \"all the parens\", many times what they are \u003cem\u003ereally\u003c/em\u003e balking at is the increased level of nesting/indenting in the language. This is especially an issue for people coming from mainstream languages where names are introduced by assignments, which do not increase the indenting level. Several other functional languages have addressed this pain point for newcomers. One of the first changes that the F# designers made to the syntax they borrowed from OCaml was to change name binding so that it wouldn't increase the indentation level. Racket, a dialect of Scheme like Clojure, uses \u003ccode\u003edefine\u003c/code\u003e as a way to introduce local variables without increasing indenting (this strategy doesn't work in Clojure, because \u003ccode\u003edef\u003c/code\u003e in Clojure always creates \u003cem\u003eglobal\u003c/em\u003e variables).\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eNames matter\u003c/h3\u003e\u003ca id=\"user-content-names-matter\" class=\"anchor\" aria-label=\"Permalink: Names matter\" href=\"#names-matter\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eI think one thing experienced programmers can all agree on is that names matter -- a lot. As someone who thinks a lot about the psychology of programming, I've observed that it is important to reduce as much as possible the psychological friction of introducing names in your code. If there's friction, programmers use names less frequently. Increased typing means increased friction, but more importantly, \u003cem\u003estructural changes\u003c/em\u003e to the code means increased friction.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eLet's say I'm writing a cond statement, and I realize that the next several tests will be about the same field of some data structure. A silly example:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(cond\n ... some other test/expressions\n (\u0026gt; (count (:name (:pet customer))) 20) (need-bigger-plaque)\n (= (:name (:pet customer)) \u0026quot;Fido\u0026quot;) (use-premade-fido-plaque)\n ... tests continue\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n ... some other test/expressions\n (\u003cspan class=\"pl-en\"\u003e\u0026gt;\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003ecount\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:name\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:pet\u003c/span\u003e customer))) \u003cspan class=\"pl-c1\"\u003e20\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003eneed-bigger-plaque\u003c/span\u003e)\n (\u003cspan class=\"pl-en\"\u003e=\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:name\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:pet\u003c/span\u003e customer)) \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003eFido\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003euse-premade-fido-plaque\u003c/span\u003e)\n ... tests continue\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eI know that it will be clearer if I give a name to \u003ccode\u003e(:name (:pet customer))\u003c/code\u003e (and also more efficient, since I won't have to lookup the field multiple times). But this refactoring causes cognitive friction:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(cond\n ... some other test/expressions\n :else (let [pet-name (:name (:pet customer))]\n (cond\n (\u0026gt; (count pet-name) 20) (need-bigger-plaque)\n (= pet-name \u0026quot;Fido\u0026quot;) (use-premade-fido-plaque)\n ... tests continue\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n ... some other test/expressions\n \u003cspan class=\"pl-c1\"\u003e:else\u003c/span\u003e (\u003cspan class=\"pl-k\"\u003elet\u003c/span\u003e [pet-name (\u003cspan class=\"pl-c1\"\u003e:name\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:pet\u003c/span\u003e customer))]\n (\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n (\u003cspan class=\"pl-en\"\u003e\u0026gt;\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003ecount\u003c/span\u003e pet-name) \u003cspan class=\"pl-c1\"\u003e20\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003eneed-bigger-plaque\u003c/span\u003e)\n (\u003cspan class=\"pl-en\"\u003e=\u003c/span\u003e pet-name \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003eFido\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003euse-premade-fido-plaque\u003c/span\u003e)\n ... tests continue\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003ein a way that this does not:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(cond\n ... some other test/expressions\n :let [pet-name (:name (:pet customer))]\n (\u0026gt; (count pet-name) 20) (need-bigger-plaque)\n (= pet-name \u0026quot;Fido\u0026quot;) (use-premade-fido-plaque)\n ... tests continue\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n ... some other test/expressions\n \u003cspan class=\"pl-c1\"\u003e:let\u003c/span\u003e [pet-name (\u003cspan class=\"pl-c1\"\u003e:name\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:pet\u003c/span\u003e customer))]\n (\u003cspan class=\"pl-en\"\u003e\u0026gt;\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003ecount\u003c/span\u003e pet-name) \u003cspan class=\"pl-c1\"\u003e20\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003eneed-bigger-plaque\u003c/span\u003e)\n (\u003cspan class=\"pl-en\"\u003e=\u003c/span\u003e pet-name \u003cspan class=\"pl-s\"\u003e\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003eFido\u003cspan class=\"pl-pds\"\u003e\"\u003c/span\u003e\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003euse-premade-fido-plaque\u003c/span\u003e)\n ... tests continue\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003ePsychologically, these two versions feel totally different because the latter version is simply an \u003cem\u003einsertion\u003c/em\u003e of a line that lets me refactor and simplify the later lines. The first way requires me to change the structure of my code, which I am unlikely to do unless I feel it is absolutely necessary. Also, from a practical standpoint, I can't do the indenting version more than a couple of times before my code gets unwieldy and unreadable because it is so far off to the right side of my screen.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eInspection matters\u003c/h3\u003e\u003ca id=\"user-content-inspection-matters\" class=\"anchor\" aria-label=\"Permalink: Inspection matters\" href=\"#inspection-matters\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMany Clojure programmers use the println debugging method when trying to understand the behavior of their code. When you want to inspect a value that is flowing through a cond, adding a println statement ordinarily involves significant refactoring, and no one wants to make major changes to their code just to inspect it. better-cond turns this into a simple matter:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(cond\n ... some other test/expressions\n :do (println (:name (:pet customer)))\n (\u0026gt; (count (:name (:pet customer))) 20) (need-bigger-plaque)\n ... tests continue\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003econd\u003c/span\u003e\n ... some other test/expressions\n \u003cspan class=\"pl-c1\"\u003e:do\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:name\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:pet\u003c/span\u003e customer)))\n (\u003cspan class=\"pl-en\"\u003e\u0026gt;\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003ecount\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:name\u003c/span\u003e (\u003cspan class=\"pl-c1\"\u003e:pet\u003c/span\u003e customer))) \u003cspan class=\"pl-c1\"\u003e20\u003c/span\u003e) (\u003cspan class=\"pl-en\"\u003eneed-bigger-plaque\u003c/span\u003e)\n ... tests continue\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThere is tremendous value in being able to drop a print statement into the middle of a cond so effortlessly.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eMinimizing rightward drift\u003c/h3\u003e\u003ca id=\"user-content-minimizing-rightward-drift\" class=\"anchor\" aria-label=\"Permalink: Minimizing rightward drift\" href=\"#minimizing-rightward-drift\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eI have gotten so used to the power of better-cond to minimize rightward drift, that sometimes I even use it to help the aesthetics of a function that has little to do with cond. For example:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(defnc solutions-general [clauses]\n :let [[object-\u0026gt;int int-\u0026gt;object] (build-transforms clauses)\n transformed-clauses (mapv (clause-transformer object-\u0026gt;int) clauses)]\n :when-let [solver (create-solver transformed-clauses)]\n :let [timeout (.getTimeoutMs solver)]\n :when-let [solution (.findModel solver timeout)]\n :let [untransformed-solution ((clause-transformer int-\u0026gt;object) solution)]\n (vec untransformed-solution)))\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003edefnc\u003c/span\u003e \u003cspan class=\"pl-e\"\u003esolutions-general\u003c/span\u003e [clauses]\n \u003cspan class=\"pl-c1\"\u003e:let\u003c/span\u003e [[object-\u0026gt;int int-\u0026gt;object] (\u003cspan class=\"pl-en\"\u003ebuild-transforms\u003c/span\u003e clauses)\n transformed-clauses (\u003cspan class=\"pl-en\"\u003emapv\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eclause-transformer\u003c/span\u003e object-\u0026gt;int) clauses)]\n \u003cspan class=\"pl-c1\"\u003e:when-let\u003c/span\u003e [solver (\u003cspan class=\"pl-en\"\u003ecreate-solver\u003c/span\u003e transformed-clauses)]\n \u003cspan class=\"pl-c1\"\u003e:let\u003c/span\u003e [timeout (\u003cspan class=\"pl-en\"\u003e.getTimeoutMs\u003c/span\u003e solver)]\n \u003cspan class=\"pl-c1\"\u003e:when-let\u003c/span\u003e [solution (\u003cspan class=\"pl-en\"\u003e.findModel\u003c/span\u003e solver timeout)]\n \u003cspan class=\"pl-c1\"\u003e:let\u003c/span\u003e [untransformed-solution ((\u003cspan class=\"pl-en\"\u003eclause-transformer\u003c/span\u003e int-\u0026gt;object) solution)]\n (\u003cspan class=\"pl-en\"\u003evec\u003c/span\u003e untransformed-solution)))\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cem\u003eNote: In the above example, I've taken advantage of the optional implicit else on the last line of better-cond, which feels especially natural when the second-to-last line is a :let or :when-let. And remember, you can omit the colons in front of :let and :when-let if you prefer the aesthetics.\u003c/em\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eCompare with:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-clojure notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"(defn solutions-general [clauses]\n (let [[object-\u0026gt;int int-\u0026gt;object] (build-transforms clauses)\n transformed-clauses (mapv (clause-transformer object-\u0026gt;int) clauses)]\n (when-let [solver (create-solver transformed-clauses)]\n (let [timeout (.getTimeoutMs solver)]\n (when-let [solution (.findModel solver timeout)]\n (let [untransformed-solution ((clause-transformer int-\u0026gt;object) solution)]\n (vec untransformed-solution)))))))\"\u003e\u003cpre\u003e(\u003cspan class=\"pl-k\"\u003edefn\u003c/span\u003e \u003cspan class=\"pl-e\"\u003esolutions-general\u003c/span\u003e [clauses]\n (\u003cspan class=\"pl-k\"\u003elet\u003c/span\u003e [[object-\u0026gt;int int-\u0026gt;object] (\u003cspan class=\"pl-en\"\u003ebuild-transforms\u003c/span\u003e clauses)\n transformed-clauses (\u003cspan class=\"pl-en\"\u003emapv\u003c/span\u003e (\u003cspan class=\"pl-en\"\u003eclause-transformer\u003c/span\u003e object-\u0026gt;int) clauses)]\n (\u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e [solver (\u003cspan class=\"pl-en\"\u003ecreate-solver\u003c/span\u003e transformed-clauses)]\n (\u003cspan class=\"pl-k\"\u003elet\u003c/span\u003e [timeout (\u003cspan class=\"pl-en\"\u003e.getTimeoutMs\u003c/span\u003e solver)]\n (\u003cspan class=\"pl-k\"\u003ewhen-let\u003c/span\u003e [solution (\u003cspan class=\"pl-en\"\u003e.findModel\u003c/span\u003e solver timeout)]\n (\u003cspan class=\"pl-k\"\u003elet\u003c/span\u003e [untransformed-solution ((\u003cspan class=\"pl-en\"\u003eclause-transformer\u003c/span\u003e int-\u0026gt;object) solution)]\n (\u003cspan class=\"pl-en\"\u003evec\u003c/span\u003e untransformed-solution)))))))\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eIt's a matter of taste, of course, whether you want to use cond for a function like this, but I definitely am glad to have a tool in my arsenal to help tame and prevent heavily indented code.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eWhat about threading macros?\u003c/h3\u003e\u003ca id=\"user-content-what-about-threading-macros\" class=\"anchor\" aria-label=\"Permalink: What about threading macros?\" href=\"#what-about-threading-macros\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eMy stylistic opinion is that threading macros are best used for short runs of piping the result from one function into another. It works best when the names of the functions clearly indicate what is being done to the value. But as the run gets longer, or you are using more general-purpose functions, there are significant benefits from giving names to the intermediate computations. Some people do this in the form of comments off to the right of each line, explaining what value is being threaded -- I personally prefer to use names that are actually part of the code.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eThe introduction of cond-\u0026gt;, as-\u0026gt;, and some-\u0026gt; addressed some of the pain points of interleaving naming and testing for heavy users of threading macros. I believe it is valuable to have similar functionality inside cond.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eIf you are a big fan of threading macros, take a look at \u003ca href=\"https://github.com/maitria/packthread\"\u003ehttps://github.com/maitria/packthread\u003c/a\u003e which addresses some of the same issues in that context.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eCan't you just put all the name bindings at the top, before your cond?\u003c/h3\u003e\u003ca id=\"user-content-cant-you-just-put-all-the-name-bindings-at-the-top-before-your-cond\" class=\"anchor\" aria-label=\"Permalink: Can't you just put all the name bindings at the top, before your cond?\" href=\"#cant-you-just-put-all-the-name-bindings-at-the-top-before-your-cond\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eNo, a lot of the time you can't name something until it exists, and knowing it exists is predicated on making other tests. For example, you can't meaningfully start talking about the first and rest of a sequence until you know that the sequence is not empty, or that the thing even is a sequence.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eHow do I remember the syntax?\u003c/h3\u003e\u003ca id=\"user-content-how-do-i-remember-the-syntax\" class=\"anchor\" aria-label=\"Permalink: How do I remember the syntax?\" href=\"#how-do-i-remember-the-syntax\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eThe syntax is inspired by the way that \u003ccode\u003e:let\u003c/code\u003e and \u003ccode\u003e:when\u003c/code\u003e work inside a for comprehension, extending the syntax to three new keywords: \u003ccode\u003e:when-let\u003c/code\u003e. \u003ccode\u003e:when-some\u003c/code\u003e and \u003ccode\u003e:do\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eLicense\u003c/h2\u003e\u003ca id=\"user-content-license\" class=\"anchor\" aria-label=\"Permalink: License\" href=\"#license\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eDerived from an early version of cgrand/utils, written by Christophe Grand under the Eclipse Public License.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003eDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\u003c/p\u003e\n\u003c/article\u003e","loaded":true,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":[{"level":1,"text":"better-cond","anchor":"better-cond","htmlText":"better-cond"},{"level":2,"text":"Usage","anchor":"usage","htmlText":"Usage"},{"level":2,"text":"Known Issue","anchor":"known-issue","htmlText":"Known Issue"},{"level":2,"text":"Rationale","anchor":"rationale","htmlText":"Rationale"},{"level":3,"text":"Names matter","anchor":"names-matter","htmlText":"Names matter"},{"level":3,"text":"Inspection matters","anchor":"inspection-matters","htmlText":"Inspection matters"},{"level":3,"text":"Minimizing rightward drift","anchor":"minimizing-rightward-drift","htmlText":"Minimizing rightward drift"},{"level":3,"text":"What about threading macros?","anchor":"what-about-threading-macros","htmlText":"What about threading macros?"},{"level":3,"text":"Can't you just put all the name bindings at the top, before your cond?","anchor":"cant-you-just-put-all-the-name-bindings-at-the-top-before-your-cond","htmlText":"Can't you just put all the name bindings at the top, before your cond?"},{"level":3,"text":"How do I remember the syntax?","anchor":"how-do-i-remember-the-syntax","htmlText":"How do I remember the syntax?"},{"level":2,"text":"License","anchor":"license","htmlText":"License"}],"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FEngelberg%2Fbetter-cond"}},{"displayName":"LICENSE","repoName":"better-cond","refName":"master","path":"LICENSE","preferredFileType":"license","tabName":"EPL-1.0","richText":null,"loaded":false,"timedOut":false,"errorMessage":null,"headerInfo":{"toc":null,"siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FEngelberg%2Fbetter-cond"}}],"overviewFilesProcessingTime":0}},"appPayload":{"helpUrl":"https://docs.github.com","findFileWorkerPath":"/assets-cdn/worker/find-file-worker-7d7eb7c71814.js","findInFileWorkerPath":"/assets-cdn/worker/find-in-file-worker-708ec8ade250.js","githubDevUrl":null,"enabled_features":{"copilot_workspace":null,"code_nav_ui_events":false,"react_blob_overlay":false,"accessible_code_button":true,"github_models_repo_integration":false}}}}

Repository files navigation

better-cond

A variation on cond which sports let bindings, when-let bindings, when-some bindings, when, do and implicit else for Clojure and Clojurescript.

New in version 2.0 and above:

  • Cond supports do for a single-line side effect.
  • Cond supports when-some (like when-let but tests for non-nil).
  • Cond allows symbols as an alternative to keywords for let, when-let, when-some, when, and do.
  • Two new macros: defnc and defnc- are like defn and defn- with an implicit cond wrapped around the body.

better-cond 2.1.5 requires Clojure 1.9 alpha 16 or higher. If you are still on Clojure 1.8, use better-cond 1.0.1.

Usage

Add the following line to your leiningen dependencies:

[better-cond "2.1.5"]

Require better-cond in your namespace header:

 (ns example.core
   (:require [better-cond.core :as b]))

 (b/cond
   (odd? a) 1

   :let [a (quot a 2)]
   ; a has been rebound to the result of (quot a 2) for the remainder
   ; of this cond.

   :when-let [x (fn-which-may-return-falsey a),
              y (fn-which-may-return-falsey (* 2 a))]
   ; this when-let binds x and y for the remainder of the cond and
   ; bails early with nil unless x and y are both truthy

   :when-some [b (fn-which-may-return-nil x),
     	       c (fn-which-may-return-nil y)]
   ; this when-some binds b and c for the remainder of the cond and
   ; bails early with nil unless b and c are both not nil

   :when (seq x)
   ; the above when bails early with nil unless (seq x) is truthy
   ; it could have been written as: (not (seq x)) nil

   :do (println x)
   ; A great way to put a side-effecting statement, like a println
   ; into the middle of a cond

   (odd? (+ x y)) 2

   3)
   ; This version of cond lets you have a single trailing element
   ; which is treated as a final :else clause.
   ; Stylistically, I recommend explicitly using :else unless
   ; the previous line is a :let, :when-let, or :when-some clause, in which
   ; case the implicit else tends to look more natural.

or alternatively, use it:

 (ns example.core
   (:refer-clojure :exclude [cond])
   (:require [better-cond.core :refer [cond]]))

 (cond
   (odd? a) 1
   :let [a (quot a 2)]
   :when-let [x (fn-which-may-return-falsey a),
              y (fn-which-may-return-falsey (* 2 a))]
   :when-some [b (fn-which-may-return-nil x),
               c (fn-which-may-return-nil y)]
   :when (seq x)
   :do (println x)
   (odd? (+ x y)) 2
   3)

In Clojurescript, it is best to use :require-macros:

 (ns example.core
   (:refer-clojure :exclude [cond])
   (:require-macros [better-cond.core :refer [cond]]))

As of version 2.0.0, writing let, when-let, when-some, when, and do as keywords is optional. So you can also write it like this, if you prefer:

 (cond
   (odd? a) 1
   let [a (quot a 2)]
   when-let [x (fn-which-may-return-falsey a),
             y (fn-which-may-return-falsey (* 2 a))]
   when-some [b (fn-which-may-return-nil x),
              c (fn-which-may-return-nil y)]
   when (seq x)
   do (println x)
   (odd? (+ x y)) 2
   3)

After trying it both ways in my code, I've come to prefer writing them as keywords, but both forms will continue to be supported.

The defnc and defnc- macros behave like Clojure's built-in defn and defn-, but they implicitly wrap the body of the function in cond, saving you another level of indenting.

(defnc f [a]
  (odd? a) 1
  let [a (quot a 2)]
  when-let [x (fn-which-may-return-falsey a),
            y (fn-which-may-return-falsey (* 2 a))]
  when-some [b (fn-which-may-return-nil x),
             c (fn-which-may-return-nil y)]
  when (seq x)
  do (println x)
  (odd? (+ x y)) 2
  3)

Because this cond has an implicit else, you can use defnc for almost all functions you would have created with defn, even those that do not actually use cond.

(defnc f [x] (* x 2)) ; This works as expected

The only time you wouldn't want to use defnc is when you are taking advantage of the implicit do offered by defn:

(defn f [x]
  (println x)
  (* x 2))

; The above makes use of defn's implicit do, but if desired,
; could be rewritten with defnc as:

(defnc f [x]
  do (println x)
  (* x 2))

I personally tend to write everything with defnc now, as it makes it easier to insert let bindings and conditional responses later. defnc is implemented using the spec for Clojure's built-in defn, so it can handle all the same things: multiple arities, pre/post-map, metadata map, docstring, etc.

In order to support multiple bindings in cond's :when-let and :when-some clauses, better-cond.core also contains a version of if-let, if-some, when-let, and when-some which can take multiple name-expression pairs in the binding vector (the ones built into Clojure can only take a single name and expression). The test passes only when all the names evaluate to something truthy (or non-nil for if-some/when-some). You may find it useful to use better-cond's if-let, if-some, when-let, and when-some directly.

As with cond, if you use if-let, if-some, when-let, or when-some you'll need to qualify with the namespace or namespace alias (i.e., b/if-let, b/when-let, b/when-some) or you'll need to exclude the Clojure version from your namespace:

    (ns example.core
      (:refer-clojure :exclude [cond if-let if-some when-let when-some])
      (:require [better-cond.core :refer [cond if-let if-some when-let when-some defnc defnc-]]))

You could also :refer :all if you are on Clojure and not Clojurescript. If you want the whole shebang, and you want to replace Clojure's defn with defnc, your namespace header would look like this:

    (ns example.core
      (:refer-clojure :exclude [cond if-let if-some when-let when-some defn defn-])
      (:require [better-cond.core :refer [cond if-let if-some when-let when-some defnc defnc-]
	                              :rename {defnc defn, defnc- defn-}]))

(As of the time of this writing, Cursive does not have code completion or adjustable indenting for symbols that have been renamed from other namespaces.)

I use this library on a daily basis, and it is hugely useful in preventing the code from getting deeply nested, helping to make the code dramatically clearer. Try it -- you'll be hooked.

This is a feature that has been discussed since the early days of Clojure. There was a JIRA issue for this for seven years.

Known Issue

defnc and defnc- macros do not preserve primitive type hint info on return value of function. Type hints on function's arguments work fine. See https://dev.clojure.org/jira/browse/CLJ-2381. Until this issue is resolved, don't use defnc or defnc- when you need a primitive return type. It works fine on primitive inputs, just not the return value.

Rationale

As a heavy user of Clojure since its first release, I have observed in my own code and in others' that one of the biggest sources of deeply-nested code and creep-to-the-right indenting is an alternation of lets and if/cond tests.

In Clojure, we tend not to use looping constructs nearly as much as in mainstream languages, because higher-order functions like map, filter, and reduce handle the vast majority of our looping needs. This means that many Clojure functions are simply expressed as a series of name bindings (let) and conditional tests (cond), so alleviating this source of indenting has a big payoff.

As a teacher of Scheme and Clojure for many years, I've noticed that when newcomers balk at "all the parens", many times what they are really balking at is the increased level of nesting/indenting in the language. This is especially an issue for people coming from mainstream languages where names are introduced by assignments, which do not increase the indenting level. Several other functional languages have addressed this pain point for newcomers. One of the first changes that the F# designers made to the syntax they borrowed from OCaml was to change name binding so that it wouldn't increase the indentation level. Racket, a dialect of Scheme like Clojure, uses define as a way to introduce local variables without increasing indenting (this strategy doesn't work in Clojure, because def in Clojure always creates global variables).

Names matter

I think one thing experienced programmers can all agree on is that names matter -- a lot. As someone who thinks a lot about the psychology of programming, I've observed that it is important to reduce as much as possible the psychological friction of introducing names in your code. If there's friction, programmers use names less frequently. Increased typing means increased friction, but more importantly, structural changes to the code means increased friction.

Let's say I'm writing a cond statement, and I realize that the next several tests will be about the same field of some data structure. A silly example:

(cond
  ... some other test/expressions
  (> (count (:name (:pet customer))) 20)  (need-bigger-plaque)
  (= (:name (:pet customer)) "Fido") (use-premade-fido-plaque)
  ... tests continue

I know that it will be clearer if I give a name to (:name (:pet customer)) (and also more efficient, since I won't have to lookup the field multiple times). But this refactoring causes cognitive friction:

(cond
  ... some other test/expressions
  :else (let [pet-name (:name (:pet customer))]
          (cond
            (> (count pet-name) 20) (need-bigger-plaque)
            (= pet-name "Fido") (use-premade-fido-plaque)
            ... tests continue

in a way that this does not:

(cond
  ... some other test/expressions
  :let [pet-name (:name (:pet customer))]
  (> (count pet-name) 20) (need-bigger-plaque)
  (= pet-name "Fido") (use-premade-fido-plaque)
  ... tests continue

Psychologically, these two versions feel totally different because the latter version is simply an insertion of a line that lets me refactor and simplify the later lines. The first way requires me to change the structure of my code, which I am unlikely to do unless I feel it is absolutely necessary. Also, from a practical standpoint, I can't do the indenting version more than a couple of times before my code gets unwieldy and unreadable because it is so far off to the right side of my screen.

Inspection matters

Many Clojure programmers use the println debugging method when trying to understand the behavior of their code. When you want to inspect a value that is flowing through a cond, adding a println statement ordinarily involves significant refactoring, and no one wants to make major changes to their code just to inspect it. better-cond turns this into a simple matter:

(cond
  ... some other test/expressions
  :do (println (:name (:pet customer)))
  (> (count (:name (:pet customer))) 20) (need-bigger-plaque)
  ... tests continue

There is tremendous value in being able to drop a print statement into the middle of a cond so effortlessly.

Minimizing rightward drift

I have gotten so used to the power of better-cond to minimize rightward drift, that sometimes I even use it to help the aesthetics of a function that has little to do with cond. For example:

(defnc solutions-general [clauses]
  :let [[object->int int->object] (build-transforms clauses)
        transformed-clauses (mapv (clause-transformer object->int) clauses)]
  :when-let [solver (create-solver transformed-clauses)]
  :let [timeout (.getTimeoutMs solver)]
  :when-let [solution (.findModel solver timeout)]
  :let [untransformed-solution ((clause-transformer int->object) solution)]
  (vec untransformed-solution)))

Note: In the above example, I've taken advantage of the optional implicit else on the last line of better-cond, which feels especially natural when the second-to-last line is a :let or :when-let. And remember, you can omit the colons in front of :let and :when-let if you prefer the aesthetics.

Compare with:

(defn solutions-general [clauses]
  (let [[object->int int->object] (build-transforms clauses)
        transformed-clauses (mapv (clause-transformer object->int) clauses)]
    (when-let [solver (create-solver transformed-clauses)]
      (let [timeout (.getTimeoutMs solver)]
        (when-let [solution (.findModel solver timeout)]
          (let [untransformed-solution ((clause-transformer int->object) solution)]
            (vec untransformed-solution)))))))

It's a matter of taste, of course, whether you want to use cond for a function like this, but I definitely am glad to have a tool in my arsenal to help tame and prevent heavily indented code.

What about threading macros?

My stylistic opinion is that threading macros are best used for short runs of piping the result from one function into another. It works best when the names of the functions clearly indicate what is being done to the value. But as the run gets longer, or you are using more general-purpose functions, there are significant benefits from giving names to the intermediate computations. Some people do this in the form of comments off to the right of each line, explaining what value is being threaded -- I personally prefer to use names that are actually part of the code.

The introduction of cond->, as->, and some-> addressed some of the pain points of interleaving naming and testing for heavy users of threading macros. I believe it is valuable to have similar functionality inside cond.

If you are a big fan of threading macros, take a look at https://github.com/maitria/packthread which addresses some of the same issues in that context.

Can't you just put all the name bindings at the top, before your cond?

No, a lot of the time you can't name something until it exists, and knowing it exists is predicated on making other tests. For example, you can't meaningfully start talking about the first and rest of a sequence until you know that the sequence is not empty, or that the thing even is a sequence.

How do I remember the syntax?

The syntax is inspired by the way that :let and :when work inside a for comprehension, extending the syntax to three new keywords: :when-let. :when-some and :do.

License

Derived from an early version of cgrand/utils, written by Christophe Grand under the Eclipse Public License.

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

About

A version of cond that supports :let clauses, and a number of other conveniences.

Resources

License

Stars

Watchers

Forks

Packages

No packages published
0