{"id":19938378,"url":"https://github.com/igjoshua/farolero","last_synced_at":"2025-05-16T14:07:05.106Z","repository":{"id":47027269,"uuid":"354348782","full_name":"IGJoshua/farolero","owner":"IGJoshua","description":"Thread-safe Common Lisp style conditions and restarts for Clojure(Script) and Babashka.","archived":false,"fork":false,"pushed_at":"2024-04-17T20:47:08.000Z","size":335,"stargazers_count":382,"open_issues_count":2,"forks_count":4,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-14T10:19:19.776Z","etag":null,"topics":["babashka","clojure","clojurescript"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IGJoshua.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-03T17:08:18.000Z","updated_at":"2025-04-11T01:08:04.000Z","dependencies_parsed_at":"2024-04-17T21:47:11.625Z","dependency_job_id":null,"html_url":"https://github.com/IGJoshua/farolero","commit_stats":{"total_commits":285,"total_committers":3,"mean_commits":95.0,"dds":0.01754385964912286,"last_synced_commit":"423b8daedde1a25b2064243f3b8f0e6c3f321386"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGJoshua%2Ffarolero","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGJoshua%2Ffarolero/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGJoshua%2Ffarolero/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IGJoshua%2Ffarolero/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IGJoshua","download_url":"https://codeload.github.com/IGJoshua/farolero/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254544146,"owners_count":22088807,"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","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":["babashka","clojure","clojurescript"],"created_at":"2024-11-12T23:39:39.459Z","updated_at":"2025-05-16T14:07:05.079Z","avatar_url":"https://github.com/IGJoshua.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# farolero\n![farolero](img/farolero.jpg)\n\u003e **farolero** masc. n.\n\u003e\n\u003e Historical Spanish, meaning \"lamplighter\", e.g. \"A lamplighter claimed to have\n\u003e seen Jack the Ripper on this street last night.\"\n\n[![cljdoc badge](https://cljdoc.org/badge/org.suskalo/farolero)](https://cljdoc.org/d/org.suskalo/farolero/CURRENT)\n[![Clojars Project](https://img.shields.io/clojars/v/org.suskalo/farolero.svg)](https://clojars.org/org.suskalo/farolero)\n\n# Latest News\n\nWith the release of `1.5.0`, farolero now supports\n[Babashka](https://babashka.org/)!\n\n# Introduction\n\nError handling in Clojure is not yet a solved problem. Each method of handling\nerrors commonly used comes with downsides. Representing error states with nil is\nconvenient for code structure, but prevents detailed error information from\nbeing conveyed to the program outside of logs. The either monad requires special\nsyntax to be convenient for use and offers no options for error recovery.\nExceptions are the default way to handle errors in the JVM, but Clojure has no\neasy way to extend the exception mechanism with new types, limiting how much\ncontrol you have over which errors you handle without re-throwing. Condition\nlibraries like [special](https://github.com/clojureman/special) give the\nprogrammer tools for reporting errors but limited options in recovery, or break\nin multithreaded contexts.\n\nThis library implements an improved version of these conditions, very close to\nthe spec defined for Common Lisp's conditions and restarts. This method of\nhandling errors follows the Clojure philosophy of decomplection by separating\nerror handling into three parts: reporting, reconciliation, and recovery.\n\n## Installation\nThe library is available on Clojars. Just add the following to your `deps.edn`\nfile in the `:deps` key.\n\n```\n{org.suskalo/farolero {:mvn/version \"1.5.0\"}}\n```\n\nIf you use [clj-kondo](https://github.com/clj-kondo/clj-kondo) then you may also\nwant to import the configuration and hooks included with the library. This can\nbe done by running the following command:\n\n```\n$ clj-kondo --copy-configs --dependencies --lint \"$(clojure -Spath)\"\nImported config to .clj-kondo/org.suskalo/farolero.\n```\n\n## ClojureScript Support\nAs of right now, the only thing that ClojureScript lacks significant support for\nis the interactive debugger, because ClojureScript environments vary so much\nfrom project to project. That said, you can still write your own custom\ndebuggers, but `assert` and `check-type` won't fully integrate with them at the\nmoment.\n\n## Usage\nIn this library there are three major components: conditions, handlers, and\nrestarts. Each one represents one of the three parts error handling is split\ninto when using this library. In places where an error might arise, you bind\nrestarts, named sections of code which provide ways to recover from an error.\n\nIf you're an experienced Common Lisper, then most of this should be review, but\nyou may wish to skim further ahead to the code examples to see the few places\nwhere the syntax differs.\n\nIn the examples below, you can try them out in a repl after the following\nrequire:\n\n``` clojure\n(require '[farolero.core :as far :refer [handler-bind handler-case restart-case]])\n```\n\n### Handlers\nHandlers are functions that are run when an error is encountered to decide how\nto recover from the situation.\n\n```clojure\n(handler-case (far/signal ::signal)\n  (::signal [condition]\n    (println condition)\n    (println \"Handled the signal!\")\n    :result))\n;; :user/signal\n;; Handled the signal!\n;; =\u003e :result\n```\n\nThe macro `handler-case` executes the expression it's passed in a context where\nthe handlers below are called when a condition is signaled. In general,\n`handler-case` is used when you can replace the entire expression wholesale with\nthe result from the handler. When a condition with a handler is signaled,\ncontrol flow is immediately passed out of the expression and to the handler.\n\n```clojure\n(handler-case (do (far/signal ::signal)\n                  (println \"Never reached\"))\n  (::signal [condition]\n    (println \"Handled the signal!\")\n    :result))\n;; Handled the signal!\n;; =\u003e :result\n```\n\nThis construct acts very similarly to Java's `throw` and `catch`. However,\nadditional arguments beyond the condition can be passed to the handler.\n\n```clojure\n(handler-case (far/signal ::signal \"world\" :other-argument)\n  (::signal [condition s v]\n    (println \"Hello,\" s)\n    (prn v)))\n;; Hello, world\n;; :other-argument\n;; =\u003e nil\n```\n\nThis works through the entire dynamic scope of the expression passed, so the\nsignal may be made arbitrarily deep in the stack.\n\n```clojure\n(defn f\n  []\n  (far/signal ::signal :result))\n\n(defn g\n  []\n  (f))\n\n(handler-case (g)\n  (::signal [condition res]\n    res))\n;; =\u003e :result\n```\n\nIf a condition is signaled and there's no handler bound, then `signal` will\nreturn nil.\n\n```clojure\n(far/signal ::signal)\n;; =\u003e nil\n```\n\n### Conditions\nConditions are the values that get signaled. Namespaced keywords are used for\nthe default signals, but they aren't the only values which can be used. Any\nobject except for an un-namespaced keyword may be used as a signal.\n\n```clojure\n(handler-case (far/signal (RuntimeException. \"An exception\"))\n  (Exception [ex]\n    (println (.getMessage ex))\n    :result))\n;; An exception\n;; =\u003e :result\n```\n\nThis example also shows that handlers are applied with regard for inheritance.\nThis inheritance is both through Java's inheritance hierarchy, and also by\nClojure's default hierarchy.\n\n```clojure\n(handler-case (far/signal ::far/simple-condition)\n  (::far/condition [condition]\n    :result))\n;; =\u003e :result\n```\n\nWhen you call `signal` with any value, farolero will ensure that it derives from\n`:farolero.core/condition`, at least indirectly. If the value derives from\n`:farolero.core/condition` indirectly, then nothing changes.\n\n```clojure\n(contains? (ancestors ::random-condition) ::far/condition)\n;;  =\u003e false\n(handler-case (far/signal ::random-condition)\n  (::far/condition [condition]\n    :result))\n;; =\u003e :result\n(contains? (ancestors ::random-condition) ::far/condition)\n;; =\u003e true\n```\n\nThere are multiple ways to signal conditions with farolero. The way to signal\nconditions we've used so far is `signal`. In addition there are `warn`, `error`,\nand `cerror` (we'll talk about `cerror` when we discuss restarts).\n\n```clojure\n(handler-case (far/error ::random-error)\n  (::far/error [condition]\n    :result))\n;; =\u003e :result\n```\n\nConditions used for `warn` are made to derive `:farolero.core/warning`, and for\n`error` and `cerror` the conditions derive `:farolero.core/error`. All Java\nclasses that extend from Exception also derive `:farolero.core/error`, and the\nsame for js/Error in ClojureScript.\n\nWhen you know the return value to be used as a replacement for the whole\nexpression, `handler-case` is the way to bind a handler. However, in some cases\nyou may not want to abort execution of the expression in order to handle the\ncondition. In these cases, `handler-bind` is more appropriate.\n\n```clojure\n(handler-bind [::signal (fn [condition]\n                          (println \"In the condition handler.\"))]\n  (far/signal ::signal))\n;; In the condition handler.\n;; =\u003e nil\n```\n\nIf a handler bound in this way returns normally (rather than via e.g. `throw`),\nthen `signal` (and the other condition signaling functions) will keep searching\nfor another handler which applies.\n\n```clojure\n(handler-bind [::far/condition (fn [condition]\n                                          (println \"In outer handler\"))]\n  (handler-bind [::signal (fn [condition]\n                            (println \"In inner handler\"))]\n    (far/signal ::signal)))\n;; In inner handler\n;; In outer handler\n;; =\u003e nil\n```\n\nIf calling `warn` and all the handlers return normally, or no handler is found,\nthen the condition is printed to `*err*`.\n\n```clojure\n(far/warn \"something went weird\")\n;; WARNING: something went weird\n;; =\u003e nil\n```\n\n### Restarts\nHandlers give you a method of reacting to conditions when they are signaled.\nRestarts provide a method of resuming the computation based on what environment\nit's executing in. The macro `restart-case` mirrors `handler-case`, but with\n`invoke-restart` taking the place of `signal`.\n\n```clojure\n(restart-case (far/invoke-restart ::restart)\n  (::restart []\n    (println \"Invoked the restart!\")\n    :result))\n;; Invoked the restart!\n;; =\u003e :result\n```\n\nUnlike handlers, there is no inheritance between different restarts. Jumping to\na particular restart must be done by exact name, and only keywords can be used\nas restart names.\n\nJust like `handler-case`, invoking a restart in `restart-case` immediately\nunwinds to outside of the expression and invokes the restart.\n\n```clojure\n(restart-case (do (far/invoke-restart ::restart)\n                  (println \"Never reached\"))\n  (::restart []\n    (println \"Invoked the restart!\")\n    :result))\n;; Invoked the restart!\n;; =\u003e :result\n```\n\nThe `warn` and `cerror` functions each bind a restart that can be used by\nhandlers for the condition which gets signaled. The `warn` function binds\n`:farolero.core/muffle-warning` (which can be called by the `muffle-warning`\nfunction) which prevents the warning from being printed and continues execution\nof the program.\n\n```clojure\n(handler-bind [::warning (fn [condition]\n                           (far/muffle-warning))]\n  (far/warn ::warning))\n;; =\u003e nil\n```\n\nThe `cerror` function binds a `:farolero.core/continue` restart (which can be\ncalled by the `continue` function) which continues as if the error never\nhappened. The first argument to `cerror` is text that describes what ignoring\nthe error will do, and is used for interactive debugging.\n\n```clojure\n(handler-bind [::error (fn [condition]\n                         (far/continue))]\n  (far/cerror \"Ignore the error\" ::error))\n;; =\u003e nil\n```\n\nWhen binding restarts, a test function can be provided that will be called to\ntest if the restart should be visible at any given time. This function must take\noptional rest arguments for a condition the restart is being searched for in the\ncontext of and its arguments.\n\n```clojure\n(restart-case (far/find-restart ::some-restart)\n  (::some-restart [] :test (constantly nil)\n    (println \"Impossible to reach\")))\n;; =\u003e nil\n```\n\nAs demonstrated above, `find-restart` may be called to find the first applicable\nrestart with a given name. You can call `invoke-restart` directly with its\nreturn value instead of with the restart name to prevent the need to look it up\nagain.\n\nThe function `compute-restarts` returns a list of visible restarts, each value\nof which includes a `:farolero.core/restart-name` key containing the restart's\nname.\n\nOne restart is always bound, named `:farolero.core/throw`. It immediately throws\nthe condition using `ex-info`.\n\nA dual to `restart-case` and mirror to `handler-bind` is `restart-bind`. It has\nthe same syntax as handler-bind, and when a restart is invoked, it is invoked as\na normal function and does not unwind the stack. This is generally not\nparticularly useful as if non-local transfer of control does not occur in the\nrestart, it will return to the code calling it, likely meaning that further\nhandlers will be invoked. The primary use of this macro is in the implementation\nof additional facilities built atop restarts, such as `restart-case`.\n\n### The Debugger\nWhen `error` or `cerror` is called and no handler is bound for the condition\nbeing signaled, the debugger is invoked using the function `invoke-debugger`.\n\n```clojure\n(restart-case (far/error ::ayy)\n  (::some-restart [])\n  (::some-other-restart []))\n;; =\u003e throws an ex-info \"Unhandled condition\"\n```\n\nBy default, the debugger will just throw the condition (wrapping it if it's not\nalready an exception). This enables library developers to use conditions without\nrequiring their users to learn farolero. For code that wants to use an\ninteractive debugger however, the following line should be included.\n\n```clojure\n(alter-var-root #'far/*debugger-hook* (constantly nil))\n```\n\nThis will deactivate the debugger that throws exceptions, and allow farolero to\nuse the \"system debugger\" that is built in. This can, for example, be done\neither at the top level or at runtime for an application, or in a namespace\nloaded only during development (like `user`) for a library.\n\n```clojure\n(restart-case (far/error ::ayy)\n  (::some-restart [])\n  (::some-other-restart []))\n;; Debugger level 1 entered on :user/ayy\n;; :user/ayy was signaled with arguments nil\n;; 0 [:user/some-restart] :user/some-restart\n;; 1 [:user/some-other-restart] :user/some-other-restart\n;; 2 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e 0\n;; =\u003e nil\n```\n\nWhen the system debugger is invoked, it reports the condition which triggered\nit, and lists the restarts available in the current context. If you enter a\nsimple number that's an index of one of the available restarts, then that\nrestart will be invoked interactively, prompting the user for input. If the\nrestart has no special handling for being invoked interactively, as the restarts\nabove, a default interactive handler will be used.\n\nInstead of using a number, arbitrary expressions may be evaluated at the\ndebugger before providing a restart to continue with. This may be used to get\nthe program into a state where the error may be continued from without issues.\n\nIf any more unhandled errors arise during the debugger's evaluation, then an\nadditional recursive layer of the debugger is invoked.\n\n```clojure\n(far/error ::ayy)\n;; Debugger level 1 entered on :user/ayy\n;; :user/ayy was signaled with arguments nil\n;; 0 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e (far/error \"oy\")\n;; Debugger level 2 entered on :farolero.core/simple-error\n;; oy\n;; 0 [:farolero.core/abort] Return to level 1 of the debugger\n;; 1 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e\n```\n\nWhen inside recursive layers of the debugger, the `:farolero.core/abort` restart\nis bound, allowing you to return to higher levels of the debugger and work from\nthere.\n\nThe debugger and interactive restarts use `*in*` and `*out*` for input and\noutput, but many interactive restarts also signal conditions to request the data\nthey need and allow it to be supplied by using a `:farolero.core/use-value`\nrestart.\n\nIn some contexts, it may be desirable to have alternative behavior when\nconditions are raised without an applicable handler, rather than invoking the\ndefault interactive debugger (e.g. writing a custom GUI debugger). The dynamic\nvariable `*debugger-hook*` can be bound to change the behavior of\n`invoke-debugger`. The default value for the hook is `throwing-debugger`, which\nis a function that will throw any conditions it is invoked with.\n\nWhen making custom debuggers, the user binds a function to the hook. The bound\nfunction must take two arguments, first a list of the condition and its\narguments, and the second is the currently bound debugger hook, which should be\nused to invoke the debugger again rather than calling `invoke-debugger`\ndirectly, or to bind `*debugger-hook*` again before calling other code, as\n`invoke-debugger` unbinds the hook before calling it, so that if an error is\nraised in it the system debugger will be invoked instead.\n\nIf the `*debugger-hook*` is bound to nil, it will invoke the system debugger,\nwhich by default is the debugger described above. The `*system-debugger*`\ndynamic variable contains the debugger to be called in this situation. This\nvariable should never be bound to nil.\n\nThe `break` function can be used to create breakpoints in your code. When\ncalled, it binds `*debugger-hook*` to nil before calling `invoke-debugger`,\nensuring the system debugger is used. This allows the primary debugger to be one\nwhich automatically handles errors, such as `throwing-debugger`, but when\n`break` is called, the system debugger will be invoked, allowing the user to\ninteractively debug the application before resuming execution.\n\nWhen binding restarts, additional information can be provided for use with the\ndebugger. A report function can be provided, as well as a function invoked to\ninteractively request any needed arguments to the restart function.\n\n```clojure\n(restart-case (far/error ::ayy)\n  (::some-restart [x]\n    :report (fn [restart] (str \"Value for some restart\"))\n    :interactive (constantly (list 5))\n    x))\n;; Debugger level 1 entered on :user/ayy\n;; :user/ayy was signaled with arguments nil\n;; 0 [:user/some-restart] Value for some restart\n;; 1 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e 0\n;; =\u003e 5\n```\n\n### Applications\nWith an understanding of *what* conditions and restarts are, and *how* to use\nthem, there remains the question of *when* they should be applied.\n\nThe basic rule of thumb is any time there's more than one way to handle a\nsituation, you bind some restarts and signal a condition. For a more concrete\nlook at the kinds of situations this may occur in, and how this can improve your\ncode, take a look at the [example projects](./doc/examples.md).\n\nFor the top level of an application though, you often will want to create\nhandlers which work through your whole application as default ways of handling\nerrors, and you may also want to disable them while in development.\n\nIn an example application, it may be structured like the following:\n\n``` clojure\n;;;; src/my_app/core.clj\n(ns my-app.core\n  (:require\n   [farolero.core :as far :refer [restart-case handler-bind]]\n   [my-app.impl :as impl])\n  (:gen-class))\n\n(defn -main\n  [\u0026 args]\n  (restart-case\n      (handler-bind [::far/error\n                     (fn [c \u0026 args]\n                       (impl/report-error c args)\n                       ;; If we have a way to ignore the error, do so\n                       (apply far/continue c args)\n                       ;; Otherwise, save a crash report and abort the application\n                       (impl/save-crash-report c args)\n                       (apply far/abort c args))]\n        (impl/start args))\n    (::far/abort []\n      :report \"Abort the application and exit.\"))\n  ;; Here is where you could do any extra shutdown stuff you need\n  (shutdown-agents))\n\n;;;; dev/user.clj\n(ns user\n  (:require\n   [farolero.core :as far]\n   [my-app.impl :as impl :refer [start]]))\n\n(defonce on-startup\n  (alter-var-root! #'far/*debugger-hook* (constantly nil)))\n```\n\nIn an application set up in this manner a default way to handle any error is\nbound at a top-level to the entry point for a distributable application which\nwill report errors as they occur, and if they can be safely ignored will do so.\nAll it requires from you as the application developer is to make sure that you\ncreate `:farolero.core/continue` restarts only in places where you can safely\ncontinue without breaking anything. If you want a way to continue but only\nconditionally, you can either set the `:test` function on the restart, or you\ncan use a different restart name.\n\nWhen you are working in a development environment though, it can be useful to\nsee errors as they come up and deal with them interactively, so instead it's\nrecommended to call out to a `start` function which has no default error\nhandling, and to configure a debugger (e.g. binding the system debugger as\nabove). This way, you can deal with errors as they arise.\n\n### Library Developers\nIf you're following along in a repl, execute the following code to re-bind the\ndefault debugger.\n\n``` clojure\n(alter-var-root #'far/*debugger-hook* (constantly far/throwing-debugger))\n```\n\nWhen writing libraries with farolero, it may be desirable to not require the\nuser to have experience with farolero, instead allowing them to use more\nfamiliar methods of error handling.\n\nIn these cases, farolero's default debugger will aid the library developer. When\nan error is signaled and not handled, the debugger is invoked. In general, the\nuser will be the one who decides which debugger will be used, but if they don't\nuse farolero directly, it will be left as the default, which will throw the\ncondition as an exception.\n\nIn order to aid in exception handling in your public api, errors should be\nsignaled as exceptions with no additional arguments.\n\n```clojure\n(far/error (RuntimeException. \"an error\"))\n;; =\u003e throws a RuntimeException\n```\n\nIf any additional arguments are signaled along with the condition, or if\nsomething other than an exception is signaled, then the value will be wrapped in\nan `ex-info`.\n\n```clojure\n(far/error (RuntimeException. \"an error\") ::some-arg)\n;; =\u003e throws an ex-info with a RuntimeException cause\n(far/error ::some-condition)\n;; =\u003e throws an ex-info\n```\n\nThe `ex-info` will have the keys `:condition`, containing the signaled value,\nand `:args` containing a seq of the rest of the arguments.\n\nIf you desire to provide dynamic variables for handlers and restarts to provide\nan interface similar to the library-less approach, it can be accomplished\nrelatively simply, while handing off handling of unwinding to farolero.\n\n```clojure\n;; A restart\n(def ^:dynamic *use-value*)\n(def ^:dynamic *some-handler* (constantly nil))\n\n(defn some-func \n  []\n  (restart-case\n      (binding [*use-value* (fn [v] (far/use-value v))]\n        (println \"Done some stuff!\")\n        (far/signal ::some-condition))\n    (::far/use-value [v]\n      v)))\n\n(defn library-entrypoint\n  []\n  (handler-bind [::some-condition *some-handler*]\n    (some-func)))\n\n;; in user code\n(binding [*some-handler* (fn [condition] (*use-value* :blah))]\n  (library-entrypoint))\n;; Done some stuff!\n;; =\u003e :blah\n```\n\nThis requires placing a handler-bind around all of the entrypoints of the\nlibrary. If the user decides to use farolero directly instead of this approach,\nthen having the handlers be bound to a function that returns nil will cause\nfarolero to look further up the stack for a handler, meaning the user can bind\ntheir own handlers if desired.\n\nAn additional thing that a library developer should consider when writing code\nwith farolero is that interactive functions, the functions used to get the\narguments for an interactive restart, should be configurable by the library user\nso that they can provide a custom debugger that will be able to interact with\nyour restarts, but then have a default way of fetching user input as well. The\nfunction `request-value` is provided to make this easy.\n\n``` clojure\n(restart-case (far/invoke-restart-interactively ::some-restart)\n  (::some-restart [a]\n    :interactive #(list (far/request-value ::interactive-some-restart))\n    a))\n;; user\u003e :foo\n;; =\u003e :foo\n```\n\nThis will first signal `::interactive-some-restart` to allow a handler to\nprovide a value with the `:farolero.core/use-value` restart, and then if they do\nnot, present a repl-like interface reading and writing with `*in*` and `*out*`.\nThis is the correct way to handle interactive functions to allow user\ncustomizability, without requiring the library user to define something special\nif they are willing to use the default experience.\n\nThe specific reason for this pattern, as opposed to the Common Lisp pattern of\nusing streams for debug io, is to prevent needlessly serializing and\ndeserializing data as it is sent up and down the stack.\n\n`request-value` will ensure that the condition signaled also derives from\n`:farolero.core/request-value`, allowing a handler to be bound to deal with\nevery instance of an interactive value request.\n\nIf some kind of interaction needs to be performed but no value returned, use\nthe function `request-interaction`.\n\n```clojure\n(restart-case (far/invoke-restart-interactively ::some-restart)\n  (::some-restart []\n    :interactive #(far/request-interaction ::interactive-some-restart)\n    5))\n;; Call farolero.core/continue when you are done\n;; user\u003e (+ 2 2)\n;; 4\n;; user\u003e (far/continue)\n;; =\u003e 5\n```\n\nThis will ensure that `::interactive-some-restart` derives from\n`:farolero.core/request-interaction`. The parent type for all interaction and\nvalue requests is `:farolero.core/interaction`.\n\nIf you wish to provide a custom default handler instead of the included repl (as\ne.g. `farolero.core/assert` does), then follow this pattern:\n\n```clojure\n;; At the top level somewhere\n(derive ::interactive-some-restart ::far/request-interaction)\n\n;; In your error handling\n(restart-case (far/invoke-restart-interactively ::some-restart)\n  (::some-restart []\n    :interactive\n    (fn []\n      (restart-case\n          (do\n            (far/signal ::interactive-some-restart)\n            ;; do some things that are the default\n            )\n        (::far/continue [])))\n    5))\n```\n\nIf the handler requires a value, then use the `:farolero.core/use-value`\nrestart, and derive your condition from `:farolero.core/request-value`.\n\n### Laziness and Dynamic Scope\nCondition handlers and restarts are bound only inside a particular dynamic\nscope. This can create some challenges with the facilities that Clojure provides\nfor deferring calculations, like `delay` and laziness.\n\n```clojure\n(handler-bind [::far/condition\n               (fn [\u0026 args]\n                 (apply prn args)\n                 (far/continue))]\n  (delay (far/cerror \"hello\")))\n;; =\u003e #\u003cDelay@28c6c817: :not-delivered\u003e\n@*1\n;; =\u003e Unhandled condition\n\n(handler-bind [::far/condition\n               (fn [\u0026 args]\n                 (apply prn args)\n                 (far/continue))]\n  (map far/cerror [\"hello\"]))\n;; =\u003e Unhandled condition\n```\n\nThese sorts of problems can be frustrating to deal with, and hard to find. The\nreason for them comes from the way that Clojure evaluates this code. In the case\nof `delay`, this is fairly clear what's happening. While the code is inside the\ndelay, it's only actually run when we dereference the returned value. This makes\nit clear that the code is run outside of the dynamic extent of the\n`handler-bind`.\n\nThe case with `map` is a little harder to see, especially for new users of\nClojure, and especially at the repl. What's happening is that `map` produces a\nlazy sequence, which does not evaluate the function that it calls on the\nsequence when you call `map`. Instead, the function passed to `map` is only\ncalled when the lazy sequence is consumed. This is somewhat confused by the fact\nthat the repl will consume the sequence implicitly as it prints the value.\n\nBecause this printing happens after the expression has already returned, it\nmeans that it's outside of the dynamic extent of the `handler-bind`.\n\nAll is not lost, however. We have multiple ways we can deal with this problem.\nFirst off, in the case of `map`, we could simply fully realize the sequence when\nwe create it, by using `mapv` or `doall`, and in some situations using `pr-str`\nand discarding the string will be helpful because it will perform the\nrealization deeply.\n\n```clojure\n(handler-bind [::far/condition\n               (fn [\u0026 args]\n                 (apply prn args)\n                 (far/continue))]\n  (doall (map far/cerror [\"hello\"])))\n;; :farolero.core/simple-error \"An error has occurred\"\n;; =\u003e (nil)\n```\n\nThis won't work if you need to keep the laziness of your sequence, due to side\neffects or memory constraints, and it won't help in the case of `delay` either.\nIn those situations, you can use `bound-fn`.\n\n```clojure\n(handler-bind [::far/condition\n               (fn [\u0026 args]\n                 (apply prn args)\n                 (far/continue))]\n  (map (bound-fn [s] (far/cerror s)) [\"hello\"]))\n;; :farolero.core/simple-error \"An error has occurred\"\n;; =\u003e (nil)\n```\n\n`bound-fn` will capture the dynamic context when it's evaluated, ensuring that\nthe body has the correct handlers and restarts bound when it's called. This\nhowever has a limitation on certain handlers and restarts, as you can only\nunwind to a point on the stack if that point is still on the stack.\n\n```clojure\n(let [f (far/block bad\n          (bound-fn [] (far/return-from bad)))]\n  (f))\n;; =\u003e Signals a :farolero.core/control-error\n```\n\nThis fails because by the time we call `f`, the block it attempts to return from\nis not on the stack anymore. In these cases a `:farolero.core/control-error` is\nsignaled, invoking the debugger and giving you information about the failure.\n\n### Multithreading\nHandlers and restarts are bound thread-locally, but with dynamic variable\nconveyance they may carry over to other threads in some contexts. To deal with\nthis, farolero allows the user to specify whether a particular handler or\nrestart is not thread-local when calling `handler-bind` or `restart-bind`.\n\n```clojure\n(handler-bind [::foo (fn [c] (println c))]\n  @(future (far/signal ::foo)))\n;; :user/foo\n;; =\u003e nil\n(handler-bind [::foo [(fn [c] (println c)) :thread-local true]]\n  @(future (far/signal ::foo)))\n;; =\u003e nil\n(far/restart-bind [::foo (fn [])]\n  @(future (far/find-restart ::foo)))\n;; =\u003e #:farolero.core{:restart-name ::foo}\n(far/restart-bind [::foo [(fn []) :thread-local true]]\n  @(future (far/find-restart ::foo)))\n;; =\u003e nil\n```\n\nIf a handler or restart is labeled as thread-local, then it is simply not\nvisible to other threads, and they will continue to search further up the stack.\n\n```clojure\n(handler-bind [::foo (fn [_] (println \"outer\"))]\n  (handler-bind [::foo [(fn [_] (println \"inner\")) :thread-local true]]\n    @(future (far/signal ::foo))))\n;; outer\n;; =\u003e nil\n(far/restart-bind [::foo (fn [] (println \"outer\"))]\n  (far/restart-bind [::foo [(fn [] (println \"inner\")) :thread-local true]]\n    @(future (far/invoke-restart ::foo))))\n;; outer\n;; =\u003e nil\n```\n\nIn contrast to the `*-bind` macros, `handler-case` and `restart-case` always\nbind thread-local handlers and restarts, because they always unwind the stack to\na particular point.\n\n```clojure\n(handler-case (far/signal ::foo)\n  (::foo [c]\n    (println c)))\n;; :user/foo\n;; =\u003e nil\n(handler-case @(future (far/signal ::foo))\n  (::foo [c]\n    (println c)))\n;; =\u003e nil\n```\n\nWhen using libraries which add forms of concurrency besides simple threads\n(core.async, promesa, manifold, etc.), care must be taken to ensure that code\nrun in the context of thread-local handlers and restarts is run on the same\nthread that bound them. This means that, for example, in a core.async `go`\nblock, you must not park inside the dynamic scope of thread-local restarts or\nhandlers if they are to be used.\n\nIn a case where you attempt to access a restart which is not bound in the\ncurrent thread, a `:farolero.core/control-error` will be signaled.\n\nThe system debugger included with farolero also supports multithreaded contexts.\nIf the debugger is invoked from a thread while it is already active, it will be\nqueued for later use. If the user wishes to switch which debugger is active\nwhile debugging, they may enter `:switch-debugger` at the repl, followed by the\nindex of the debugger they wish to switch to. If something other than a number\nis read, a control error is signaled with restarts bound to retry and to abort\nand go back to the debugger you started from.\n\n```clojure\n(far/error \"Error from thread 1\")\n;; Debugger level 1 entered on :farolero.core/simple-error\n;; Error from thread 1\n;; 0 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e (future (far/error \"Error from thread 2\"))\n;; #object[clojure.core$future_call$reify__8477 0x646c0a67 {:status :pending, :val nil}]\n;; user\u003e :switch-debugger\n;; Debuggers from other threads\n;; 0 [clojure-agent-send-off-pool-0] Error from thread 2\n;; Debugger to activate: 0\n;; Debugger level 1 entered on :farolero.core/simple-error\n;; Error from thread 2\n;; 0 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e 0\n;; Debugger level 1 entered on :farolero.core/simple-error\n;; Error from thread 1\n;; 0 [:farolero.core/throw] Throw the condition as an exception\n;; user\u003e 0\n;; Execution error (ExceptionInfo) at farolero.core/fn (core.cljc:315).\n;; Condition was thrown\n```\n\n### Other Control Flow\nIn addition to the core functions and macros required to make conditions and\nrestarts, farolero provides a few more control flow operators inspired by the\nCommon Lisp spec.\n\nThe `block` macro (and its paired `block*` function) provides a way to perform\nan early return from a named block.\n\n```clojure\n(far/block the-block\n  (far/return-from the-block :hello)\n  :goodbye)\n;; =\u003e :hello\n```\n\nPassing no second argument to `return-from` results in the `block` returning nil.\n\n```clojure\n(far/block the-block\n  (far/return-from the-block)\n  :goodbye)\n;; =\u003e nil\n```\n\nThis return works anywhere within the dynamic scope of the block, not just\nwithin its current stack frame.\n\n```clojure\n(defn some-func\n  [f]\n  (f :hello)\n  :goodbye)\n\n(far/block the-block\n  (some-func #(far/return-from the-block %))\n  :goodbye)\n;; =\u003e :hello\n```\n\nIf you use a keyword instead of a symbol, then `return-from` will unwind the\nstack until the first `block` which uses the same keyword. This is equivalent to\nCommon Lisp's `throw` and `catch`.\n\n```clojure\n(defn throwing-func\n  []\n  (far/return-from :the-block :goodbye))\n\n(far/block :the-block\n  (far/block :the-block\n    (throwing-func)) ;; =\u003e :goodbye\n  :hello)\n;; =\u003e :hello\n```\n\nThe `block*` function calls a closure in the context of such a block with the\ngiven keyword as the block name.\n\n```clojure\n(far/block* :the-block\n  #(do (far/return-from :the-block :hello)\n       :goodbye))\n;; =\u003e :hello\n```\n\nIf you want to uniquely specify a block name for use with `block*`, the\n`make-jump-target` function is provided.\n\n```clojure\n(let [the-block (far/make-jump-target)]\n  (far/block* the-block\n    #(do (far/return-from the-block :hello)\n         :goodbye)))\n;; =\u003e :hello\n```\n\nAny extra arguments passed to `block*` are passed as arguments to the closure.\n\n```clojure\n(far/block* :the-block\n  #(do (far/return-from :the-block %)\n       :goodbye)\n  :hello)\n;; =\u003e :hello\n```\n\nIf you attempt to `return-from` a block that isn't in the current thread's\ndynamic scope, then a `:farolero.core/control-error` is signaled.\n\n```clojure\n(far/return-from :error nil)\n;; =\u003e Entered the debugger on :farolero.core/control-error\n```\n\nAn additional facility is `tagbody`, which binds labels for its dynamic scope\nwhich can be jumped to with `go`. This is more or less an imperative `letfn`,\nbut can be used to implement more complex control flow than the other operators\nin Clojure.\n\n```clojure\n(let [x (volatile! 0)]\n  (far/tagbody\n    (println \"Entered tagbody!\")\n    loop\n    (when (\u003e @x 5)\n      (far/go exit))\n    (vswap! x inc)\n    (far/go loop)\n    exit\n    (println \"Exiting tagbody!\"))\n  @x)\n;; Entered tagbody!\n;; Exiting tagbody!\n;; =\u003e 6\n```\n\nThe `tagbody` clause always returns nil.\n\nJust like `block` and `return-from`, `go` may be used anywhere within the\ndynamic scope of the `tagbody`.\n\n```clojure\n(defn call-if-greater\n  [v f]\n  (when (\u003e v 5)\n    (f)))\n\n(let [x (volatile! 0)]\n  (far/tagbody\n    (println \"Entered tagbody!\")\n    loop\n    (call-if-greater @x #(far/go exit))\n    (vswap! x inc)\n    (far/go loop)\n    exit\n    (println \"Exiting tagbody!\"))\n  @x)\n;; Entered tagbody!\n;; Exiting tagbody!\n;; =\u003e 6\n```\n\nThis can be combined with `block` to add a return value.\n\n```clojure\n(let [x (volatile! 0)]\n  (far/block the-block\n    (far/tagbody\n      (println \"Entered tagbody!\")\n      loop\n      (when (\u003e @x 5)\n        (far/go exit))\n      (vswap! x inc)\n      (far/go loop)\n      exit\n      (println \"Exiting tagbody!\")\n      (far/return-from the-block @x))))\n;; Entered tagbody!\n;; Exiting tagbody!\n;; =\u003e 6\n```\n\nWhen using `restart-case`, `tagbody` can be used to provide a way to retry items from the restarts.\n\n```clojure\n(far/block exit\n  (far/tagbody\n    retry\n    (far/return-from exit\n      (far/restart-case (if (\u003c (rand) 0.5)\n                          (do (println \"iteration\")\n                              (far/invoke-restart ::far/continue))\n                          :eventual-result)\n        (::far/continue []\n          (far/go retry))))))\n;; iteration\n;; iteration\n;; iteration\n;; iteration\n;; =\u003e :eventual-result\n```\n\nThe above code will loop while `(\u003c (rand) 0.5)` returns true, eventually\nreturning `:eventual-result` when it returns false.\n\n### Implementation Caveat\nMany different operators in farolero build upon the `block` macro and its\nassociated functions. The `block` macro is implemented in terms of the JVM's\nexception mechanism, by throwing a value that extends `java.lang.Error`. This\nvalue specifies a particular `block` that it unwinds to. The purpose of the\n`java.lang.Error` class is to provide a way to throw a value that is explicitly\nintended not to be caught.\n\nUnfortunately you may sometimes see code that catches `java.lang.Throwable`. In\nnearly all cases, this code doesn't need to and shouldn't catch this much, and\nthe primary reason to do it is to allow the code to catch both all\n`java.lang.Exception`s, and `java.lang.AssertionError`.\n\nWhat this means however is that in cases where code catches all `Throwable`s\nfarolero will be unable to unwind the stack past that boundary, and if the value\nis logged, it may be confusing as farolero's `Signal` class does not include a\nstack trace or error message.\n\nThe reality of the situation is that while farolero can do nothing about this\n(except in cases where extension mechanisms are provided, as with\n[flow](https://github.com/fmnoise/flow)), many pieces of code that catch\n`Throwable` are frameworks of various sorts, and it's unlikely to desire\nunwinding past them, so this rarely is an issue, but it is one that you should\nkeep in mind when using farolero.\n\n### Extensions\nSome other error handling libraries will try to interact with exceptions by\ncatching `Throwable`, which will interfere with the farolero unwind mechanism.\nThankfully, some of those libraries also provide extension mechanisms to specify\nbehavior for particular exceptions, which gives farolero a way to keep the\nunwind mechanism functional. In cases like this, farolero adds an extension\nnamespace.\n\nWhen working with JVM Clojure, this will operate transparently to the user, as\nthe libraries will be detected at runtime and extensions loaded. Unfortunately,\nClojureScript doesn't provide a mechanism for checking for dependencies at\nruntime, and this means that you will have to require the extension namespace\nyourself to activate the integration.\n\nThe namespace names for extensions are of the form\n`farolero.extensions.lib-name`, like `farolero.extensions.flow` for integration\nwith [flow](https://github.com/fmnoise/flow).\n\nThe following libraries currently have extensions:\n\n- [flow](https://github.com/fmnoise/flow)\n\n## Known Issues\nYou may run into one of the issues below. I am aware of them and have plans to\nfix them. If you know how to fix them or have the time, pull requests are always\nwelcome!\n\n## License\n\nCopyright © 2023 Joshua Suskalo\n\nDistributed under the Eclipse Public License version 1.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figjoshua%2Ffarolero","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figjoshua%2Ffarolero","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figjoshua%2Ffarolero/lists"}