{"id":17230153,"url":"https://github.com/jonasseglare/epicea-antivalue","last_synced_at":"2025-03-25T19:42:46.772Z","repository":{"id":57713255,"uuid":"80339865","full_name":"jonasseglare/epicea-antivalue","owner":"jonasseglare","description":"Antivalues: How to deal with odd outcomes from computations","archived":false,"fork":false,"pushed_at":"2017-10-10T05:33:09.000Z","size":82,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-30T17:35:26.757Z","etag":null,"topics":["clojure","control-flow","error","error-handling","errors","exceptions","macros","transformations"],"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/jonasseglare.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}},"created_at":"2017-01-29T10:21:31.000Z","updated_at":"2018-01-25T12:27:49.000Z","dependencies_parsed_at":"2022-09-06T02:10:58.502Z","dependency_job_id":null,"html_url":"https://github.com/jonasseglare/epicea-antivalue","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonasseglare%2Fepicea-antivalue","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonasseglare%2Fepicea-antivalue/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonasseglare%2Fepicea-antivalue/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonasseglare%2Fepicea-antivalue/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonasseglare","download_url":"https://codeload.github.com/jonasseglare/epicea-antivalue/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245534307,"owners_count":20631284,"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":["clojure","control-flow","error","error-handling","errors","exceptions","macros","transformations"],"created_at":"2024-10-15T04:52:38.482Z","updated_at":"2025-03-25T19:42:46.750Z","avatar_url":"https://github.com/jonasseglare.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# epicea-antivalue\n*How to deal with odd outcomes from computations*\n\nAntivalues let us break control flow locally and return an alternative result of a computation. They work a bit like exceptions, but there is only one type of antivalue, no type-based dispatch, and they cannot escape from functions or loops. Antivalues can be converted to regular values using ```anti``` and regular values can be converted to antivalues using ```anti```. Whenever an antivalue occurs, it interupts all expressions currently being evaluated, and propagates up the nested forms until it reaches a form that will handle it, such as ```either```, ```anti``` or ```export```. \n\nEven if they behave a bit like exceptions, this library generates standard Clojure code without exceptions from code that uses antivalues. That way, it can easily be made to work on different host platforms and we could hope that it will play well with other macro libraries, such as core.async (TODO: test that...). Unlike exceptions, antivalues can be associated to symbol using the ```let``` form, which makes it easy to identify the reason why some computation failed, instead of using the type-based dispatch mechanism of exceptions.\n\n## Introductory example\nHere is a small function to illustrate what the library does.\n```clojure\n(defn bmi [data]\n  (top\n   (let [mass (expect number? (:mass data))\n         height (expect number? (:height data))]\n     (either (/ mass (* height height))\n             [:missing-mass (anti mass)]\n             [:missing-height (anti height)]\n             nil))))\n```\nIt uses ```expect``` to validate the ```mass``` and ```height``` values in the map, and ```either``` to decide what to return. There is also a ```top``` form that surrounds everything and rewrites the code into regular clojure code. This small examples illustrates three useful features of this library:\n  1. ```expect``` lets us validate data, and produces antivalues with invalid data.\n  2. Antivalues can be bound to symbols using ```let```, so that we can identify the reason why something failed.\n  3. ```either``` lets us choose the first expression that has a regular value, and that expression is then returned as a result from calling the function.\n\nSo if we call it with approprate arguments,\n```clojure\n(bmi {:mass 80 :height 1.94})\n```\nwe get\n```clojure\n21.25624402168137\n```\nbut with wrong arguments,\n```clojure\n(bmi {:mass 80 :height :kattskit})\n```\nwe get\n```clojure\n[:missing-height :kattskit]\n```\n\n## Usage\nWe will be using the namespace ```epicea.antivalue.core```.\n\nA regular Clojure value is just a value. The value of ```9``` is ```9```. To produce an antivalue, we use ```anti``` on the value:\n```clojure\n(anti 9)\n```\nThis results in the *antivalue* of 9.\n\nHere is an expression that evaluates to 9:\n```clojure\n(+ 4 5)\n```\nIf we make one of the arguments an antivalue, the expression will evaluate to that antivalue, that is\n```clojure\n(+ (anti 4) 5)\n```\nevaluates to ```(anti 4)```.\n\n```anti``` must always be wrapped inside ```either```, so that we always end up with a value (which is not an antivalue):\n```clojure\n(either (+ (anti 4) 5)\n        :failure)\n```\nThe form above will evaluate to the value ```:failure```. The top-most ```either``` form must always have at least one branch that will never produce an antivalue, so this will *not* compile:\n```clojure\n(either (+ (anti 4) 5))\n```\nInstead, you will get an error at macro expansion time.\n\nGiven an antivalue, we can turn it into a value again using anti:\n```clojure\n(either (anti (anti 4)) nil)\n```\nevaluates to ```4```, and \n```clojure\n(either (anti (+ (anti 4) 5)) nil)\n```\nevaluates to ```4```, too.\n\n```either``` can have more than two branches and each branch is visited in order until we encounter a branch with a value that is not an antivalue, for instance\n```clojure\n(either (anti 4) (anti 5) :a :b :c (anti 6) :d)\n```\nevaluates to ```:a``` which is the first value.\n\nExpressions that produce antivalues can exist inside let-bindings, e.g.\n```clojure        \n(defn my-add [a b]\n  (either\n   (let [ax (if (number? a) a (anti a))\n         bx (if (number? b) b (anti b))]\n      (either (+ ax bx)\n              [:bad-input :a (anti ax)]\n              [:bad-input :b (anti bx)]\n              nil))))\n```\nThat is practical to identify the reason why we can't procede with a computation. Note that there are two ```either```. The outer ```either``` is only needed for the code transformations.\n\nThe ```expect``` macro tests if a function applied to a value is true and returns the value in that case, otherwise it produces an antivalue of that value. So the code here is equivalent to the above code.\n```clojure\n(defn my-add [a b]\n  (either\n   (let [ax (expect number? a)\n         bx (expect number? b)]\n     (either (+ ax bx)\n              [:bad-input :a (anti ax)]\n              [:bad-input :b (anti bx)]\n              nil))))\n```\nIn case we would actually need to work with an antivalue like a regular value, the ```export``` function will convert it to such a value. The following call\n```clojure\n(export (anti 3))\n```\nevaluates to\n```clojure\n#epicea.antivalue.core.Antivalue{:data 3}\n```\nAnd if we want to take any value and convert it to an antivalue if possible, there is ```import```. For instance, this call will take the raw representation of the antivalue previous exported and convert it to an ordinary value:\n```clojure\n(export (anti (import #epicea.antivalue.core.Antivalue{:data 3})))\n```\nso that we get ```3``` as result. The mechanism of ```import``` and ```export``` could be used to make antivalues cross function boundaries, but should probably be used with care. It might be better to explicitly use ```either``` to produce alternative return values if a computation fails.\n\nSee the [unit tests](test/epicea/antivalue/core_test.clj) for several examples of how the library can be used.\n\n## Difference w.r.t exceptions\n\n  * Unlike exceptions, antivalues are local. An antivalue produced inside a function cannot leak outside that function. This includes lambda functions.\n  * Also, antivalues currently don't work with loops.\n  * Whenever an antivalue occurs inside a let bounding form, instead of interupting the entire form as would have been the case with an exception, the antivalue is kept inside the bound symbol and only released once the bound symbol is evaluated.\n  * There is only one type of antivalues. There is no type-based dispatch.\n\n## Rationale\n\nExceptions cause confusion when they escape from functions, because the result of the function can either be a return value or an exception. If functions only produce return values but no exceptions, the code becomes simpler. But locally, exceptions can still be convenient because they *can* let us express the control flow more concisely and to the point. \n\nThis library emulates a very simple form of exceptions that only work locally and makes it easy to deal with all the different forms of return values from calling functions, so that we can write code which is both robust, expressive and easy to reason about.\n\n## Contribute\nI appreciate contributions to this library. You can contribute by submitting an issue or a pull request. In particular, I would need help with\n\n  * Fixing bugs\n  * Adding more unit tests\n  * Implementing support for the ```loop```-form. I would expect this to be tricky, because the call to ```recur``` must be in tail position in the generated code.\n  * Adapting it for ClojureScript\n\nAny contributor will be listed here, unless they don't want to.\n\n## License\n\nCopyright © 2017 Jonas Östlund\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonasseglare%2Fepicea-antivalue","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonasseglare%2Fepicea-antivalue","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonasseglare%2Fepicea-antivalue/lists"}