{"id":18994914,"url":"https://github.com/wevre/let-not","last_synced_at":"2025-09-20T23:45:57.525Z","repository":{"id":62435161,"uuid":"283084661","full_name":"wevre/let-not","owner":"wevre","description":"Monad comprehension for chain of computations, mimicking functionality of C language do-while with break.","archived":false,"fork":false,"pushed_at":"2020-08-18T21:46:32.000Z","size":14,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-29T05:21:30.673Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wevre.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-28T03:13:35.000Z","updated_at":"2020-08-18T21:46:34.000Z","dependencies_parsed_at":"2022-11-01T21:02:25.121Z","dependency_job_id":null,"html_url":"https://github.com/wevre/let-not","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/wevre/let-not","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wevre%2Flet-not","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wevre%2Flet-not/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wevre%2Flet-not/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wevre%2Flet-not/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wevre","download_url":"https://codeload.github.com/wevre/let-not/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wevre%2Flet-not/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276175033,"owners_count":25597792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-20T02:00:10.207Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T17:27:34.208Z","updated_at":"2025-09-20T23:45:57.495Z","avatar_url":"https://github.com/wevre.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# let-not\n\nMonad comprehension for a chain of computations, specified with a `fail?`\npredicate, a `let` binding, and a final `return` expression. It extends the\n`maybe-m` monad such that if any computation returns `nil` _or_ a value such\nthat `(fail? val)` is true, the whole computation will yield that value (i.e.\nshort-circuit). If none fail, the computation returns `result`. The default\n`fail?` predicate is `::break`, implying a typical situation where computations\nare collecting results in a map, and the presence of the key `::break` will\nindicate failure.\n\n# How to install\n\ndeps.edn\n\n    wevre/let-not {:mvn/version \"0.0.2\"}\n\nproject.clj\n\n    [wevre/let-not \"0.0.2\"]\n\n# How to use\n\n```clj\n(require '[wevre.let-not :refer [let-not]])\n\n(defn something-with [x]\n  (if (looks-okay x)\n    {:status \"everything looks good\"}\n    {:error-state \"we failed\"}))\n\n(defn check-a-bunch-of-things [input]\n  (let-not :error-state\n    [a (something-with input)\n     b (something-else-with a)]\n    (final-result-with b)))\n```\n\n# Why do I need this?\n\nI created `let-not` because one of my favorite constructs in imperative\nlanguages (descendants of C) was a `do-while(0)` loop for short-circuiting. I\nwould deal with all my 'unhappy path' items at the beginning and bail out early\nif anything went wrong (cf. Swift's `guard` statement).\n\n```php\ndo {\n   // \u003csomething that might break\u003e\n   // \u003cnext thing that might break\u003e\n   // \u003ceverything is good to go, do your thing\u003e\n   return;\n} while (0);\n// \u003chandle the breaking issue\u003e\n```\n\nThis can also be wrapped in an exception if needed, although I personally try to\nreduce my dependency on those.\n\n```php\ntry do {\n   // \u003cdo breaking/exception stuff\u003e\n   // \u003cdo your thing\u003e\n   return;\n} while(0) catch (Exception e) {\n   // \u003chandle the exception\u003e\n}\n// \u003chandle the breaking issue\u003e\n```\n\nOr you could catch the exception inside the `do-while` and then break from\nthere...lots of variations.\n\nI'm programming in Clojure and I still find myself in situations where I want to\nuse this approach: I need to check that a bunch of stuff is okay and, if it is,\ndo the final thing. My choices are (a) nested `if`s (or `if-let`s or\n`when-let`s) or `cond`; or (b) exceptions.\n\n```clj\n(if \u003cfirst thing is okay\u003e\n  (if \u003csecond thing is okay\u003e\n    (if \u003cthird thing is okay\u003e\n      ...   ;; pyramid of doom\n        \u003cdo your thing\u003e\n        \u003chandle nth error\u003e)\n      ...\n      \u003chandle 2nd error\u003e)\n   \u003chandle 1st error\u003e)\n\n(try\n  (do\n    \u003cthing that might throw\u003e\n    \u003canother thing that might throw\u003e\n    ...\n    \u003cdo your thing\u003e)\n  (catch ...)\n\n(cond\n  \u003cfails first test\u003e \u003chandle 1st error\u003e\n  \u003cfails second test\u003e \u003chandle 2nd error\u003e\n  ...\n  \u003cone final check\u003e \u003cdo your thing\u003e\n  :else \u003chandle something you didn't think about\u003e)\n```\n\nIn the above code, \"handle error\" might mean returing nil or some other value\nthat indicates failure, it might mean throwing an exception. In all three\napproaches, which are already cumbersome, it gets messy if you need to bind\nintermediate values along the way. You have to intersperse your code with `let`\nbindings which turn pyramids of doom into _super_ pyramids of doom.\n\n# Where would I use it?\n\n`let-not` cleans up code where you are testing your way through a list of\nconditions, and might need to keep track of intermediate values along the way. I\nthink there are two main uses:\n\n 1. As a replacement for exceptions.\n\n 2. As a replacement for `cond` or nested `if`s (especially if you need to keep\n    track of intermediate values).\n\n# An example\n\nHere is an example of using it to re-write the sample from\n[clojure/tools.cli](https://github.com/clojure/tools.cli), replacing the `cond`\nused in the `validate-args` function with an approach using `let-not`. This\nmaybe isn't the most scintillating of examples. We don't really need to keep\ntrack of any intermediate values. But it still demonstrates nicely the use of\n`let-not`. When I find a better practical example, I'll replace it.\n\n## Original clojure/tools.cli example code\n\n```clj\n(defn validate-args\n  \"Validate command line arguments. Either return a map indicating the program\n  should exit (with a error message, and optional ok status), or a map\n  indicating the action the program should take and the options provided.\"\n  [args]\n  (let [{:keys [options arguments errors summary]} (parse-opts args cli-options)]\n    (cond\n      (:help options) ; help =\u003e exit OK with usage summary\n      {:exit-message (usage summary) :ok? true}\n      errors ; errors =\u003e exit with description of errors\n      {:exit-message (error-msg errors)}\n      ;; custom validation on arguments\n      (and (= 1 (count arguments))\n           (#{\"start\" \"stop\" \"status\"} (first arguments)))\n      {:action (first arguments) :options options}\n      :else ; failed custom validation =\u003e exit with usage summary\n      {:exit-message (usage summary)})))\n```\n\n## Replace `cond` with `let-not`\n\n```clj\n(require '[wevre.let-not :refer [let-not])\n\n(defn validate-args-let-not\n  \"Validate command line arguments with `let-not`. At each step, if a condition\n  fails, return a map with included :exit-message key, otherwise return the prior\n  step's result.\"\n  [args]\n  (let-not :exit-message\n    [{:keys [options arguments errors summary] :as res} (parse-opts args cli-options)\n     res (if (:help options) {:exit-message (usage summary) :ok? true} res)\n     res (if errors {:exit-message (error-msg errors)} res)\n     res (if (not= 1 (count arguments)) {:exit-message (usage summary)} res)\n     res (if (nil? (#{\"start\" \"stop\" \"status\"} (first arguments))) {:exit-message (usage summary)} res)]\n    {:action (first arguments) :options options}))\n```\n\n# License\n\nCopyright 2020 Mike Weaver\n\nLicensed under the Eclipse Public License 2.0, see 'license.txt'.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwevre%2Flet-not","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwevre%2Flet-not","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwevre%2Flet-not/lists"}