{"id":24204536,"url":"https://github.com/avramrobert/halfling","last_synced_at":"2025-10-20T12:32:00.880Z","repository":{"id":62432866,"uuid":"80163176","full_name":"AvramRobert/halfling","owner":"AvramRobert","description":null,"archived":false,"fork":false,"pushed_at":"2021-10-05T22:24:59.000Z","size":135,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-13T23:17:02.336Z","etag":null,"topics":["async-programming","asynchronous","clojure","concurrency","functional","lazy"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AvramRobert.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-26T22:47:08.000Z","updated_at":"2021-10-05T22:25:01.000Z","dependencies_parsed_at":"2022-11-01T21:01:02.841Z","dependency_job_id":null,"html_url":"https://github.com/AvramRobert/halfling","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AvramRobert%2Fhalfling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AvramRobert%2Fhalfling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AvramRobert%2Fhalfling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AvramRobert%2Fhalfling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AvramRobert","download_url":"https://codeload.github.com/AvramRobert/halfling/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241663540,"owners_count":19999337,"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":["async-programming","asynchronous","clojure","concurrency","functional","lazy"],"created_at":"2025-01-13T23:17:08.736Z","updated_at":"2025-10-20T12:31:55.858Z","avatar_url":"https://github.com/AvramRobert.png","language":"Clojure","readme":"# halfling\n![](resources/intro-image.jpg)\n\u003cbr/\u003e\nA simplistic Clojure library for creating, manipulating and composing asynchronous actions, that\nis built atop Clojure's existing support for futures.\n\nTwo of the main things that futures in Clojure lack are composability and a certain\ndegree of laziness. This library attempts to provide these characteristics, plus some additional\ntools for working with them.\n\n## Clojars\n[![Clojars Project](https://img.shields.io/clojars/v/halfling.svg)](https://clojars.org/halfling)\n## Usage     \nThe main abstraction in halfling is something called a `Task`. \u003cbr /\u003e\n`Task` is essentially a fancy wrapper around Clojure's `future`.\n\n### Tasks\nLet's create some tasks: \u003cbr /\u003e\n```Clojure\n\u003e (require '[halfling.task :as t])\n=\u003e nil\n\n\u003e (def adding (t/task (+ 1 1)))\n=\u003e #'user/adding\n\n\u003e (def no-please (t/task \n                 (Thread/sleep Integer/MAX_VALUE)\n                 42))\n=\u003e #'user/no-please\n\n\u003e (t/executed? adding)\n=\u003e false\n\n\u003e (t/executed? no-please)\n=\u003e false\n\n```\nRight, so by now nothing actually happened. Tasks are lazy by default and\nevery operation you perform on them (aside from execution) is also computed lazily. \nIn order to make a task do something, you have to explicitly run it. This can be done either\nsynchronously or asynchronously.\n\nThe invariant is that running a task will always return another task, which contains the result of that execution.\nThis can be subsequently manipulated and composed with other tasks.\n\n### Synchronous execution\n```Clojure\n\u003e (t/run adding)\n=\u003e #Task{:executed? true, :status :success, :value 2}\n```\nThis type of execution will naturally block the current thread until the tasks finishes.\n\n### Asynchronous execution\n```Clojure\n\u003e (t/run-async adding)\n=\u003e #Task{:executed? false, :status :pending, :value nil}\n```\nRunning a task asynchronously will not block the current thread and will return immediately.\nThe task itself captures a promise which will eventually be filled with the result of that execution.\n\n**Note**: `wait` can be used to block explicitly.\n\n### Task values\nIn some cases, you may want to retrieve the actual inner value of a task.\n\nThis can be achieved either with `get!`, `deref` or `@` and these can return one of two things:\n\n* If a task succeeded, it will return the concrete value of that execution:\n```clojure\n\u003e (t/get! (t/run adding))\n=\u003e 2\n\n\u003e @(t/run adding)\n=\u003e 2\n```\n\n* If a task failed, it will contain a map version of the `Exception` that occurred:\n```clojure\n\u003e (-\u003e (t/task (throw (Exception. \"Something went wrong!\")))\n      (t/run)\n      (t/get!))\n\n=\u003e { :cause \"Something went wrong!\", \n     :via [...],\n     :trace [...] }\n```\n\nThere's a separate `get-or-else` function, which will return either the value in\ncase of a success, or a provided `else` alternative in case of failure:\n```clojure\n\u003e (-\u003e (task 1)\n      (t/run)\n      (t/get-or-else -1))\n=\u003e 1\n\n\u003e (-\u003e (t/task (throw (Exception. \"Nope\")))\n      (t/run)\n      (t/get-or-else -1))\n=\u003e -1\n```\n\n**Note:** All of these will **block** an asynchronously executing task.\n\n### Task status\nThere are a number of functions that check different types of a task's status:\n\n* `done?` - checks if a **running task** has **finished running**\n* `executed?` - checks if a task **has been run** and has **finished running**\n* `fulfilled?` - checks if a **running task** has **finished running** and was **successful**\n* `broken?` - checks if a **running task** has **finished running** and **failed**\n\nIn addition, you can create finished successful or failed tasks with:\n\n* `success` -  given any value, returns a finished successful task containing that value\n* `failure` - given a string message, returns a finished failed task with that message wrapped in a Throwable as an error\n* `failure-t` - given a proper `Exception` or `Throwable` object, returns a finished failed task with that error\n\n### Composing tasks\nTasks can be composed by using the `then` primitive. This takes a\ntask and some sort of callback function, and returns a new task:\n```Clojure\n\u003e (def crucial-maths (-\u003e (t/task (+ 1 1))\n                         (t/then inc)\n                         (t/then dec)))\n=\u003e #'user/crucial-maths\n\n\u003e @(t/run crucial-maths)\n=\u003e 2\n```\nAdditionally, the callback function can either return a simple value or another task:\n```Clojure\n\u003e (def crucial-maths (-\u003e (t/task (+ 1 1))\n                         (t/then #(t/task (inc %)))\n                         (t/then dec)))\n=\u003e #'user/crucial-maths\n\n\u003e @(t/run crucial-maths)\n=\u003e 2\n```\nBy the magic of referential transparency, this leads to the same outcome.\n\n### Composition after execution\nTasks maintain composability after execution. Every time they get run, they return new tasks\ncontaining the future results. Because tasks are lazy, once you've run a task and\nafterwards composed new things into it, you'll have to run it again in order to force the new compositions.\n\nExample:\n```Clojure\n\u003e (def crucial-math (-\u003e (t/task (+ 1 1))\n                        (t/then #(t/task (inc %)))\n                        (t/run) ;; \u003c- (inc (+ 1 1))\n                        (t/then dec))) ;; \u003c- unexecuted\n=\u003e #'user/crucial-math\n```\nIn this case `run` (and also `run-async`) will only execute those tasks that came before its invocation.\nIf additional compositions are made after or while it's executing, these shall remain un-executed until another\ncall to either `run` or `run-async` is made:\n```Clojure\n\u003e @(t/run crucial-math)\n=\u003e 2\n```\nThe task will then pick up where it's left off and execute the remaining changes.\n\n### Fire-and-forget effects\nIf you're not interested in the return value of some previous task,\nyou can chain fire-and-forget-like task effects by using the `then-do` macro.\n`then-do` sequentially composes effects into one task:\n```clojure\n\u003e @(-\u003e (t/task (println \"Launching missiles!\"))\n       (t/then-do (println \"Missiles launched!\"))\n       (t/then-do (println \"Death is imminent!\"))\n       (t/run))\n\nLaunching missiles!\nMissiles launched!\nDeath is imminent!\n=\u003e nil\n```\nThis is equivalent to:\n```clojure\n\u003e @(-\u003e (t/task (println \"Launching missles!\")\n       (t/task (fn [_] (println \"Missiles launched!\")))\n       (t/task (fn [_] (println \"Death is imminent!\")))\n       (t/run))\n```\n\n### Task recovery\nA potentially failed task may be recovered with either `recover` or `recover-as`.\n\nBoth of these may well return either simple values, or tasks alltogether.\n\n`recover` allows you to recover a task, based on the error that occured, whilst `recover-as` simply\nignores the error and lets you reset the task to any given value after failure.\n\n```clojure\n\u003e @(-\u003e (t/task (throw (Exception. \"Failed)))\n       (t/recover #(.getMessage %))\n       (t/run))\n\n=\u003e \"Failed\"\n```\n\n```clojure\n\u003e @(-\u003e (t/task (throw (Exception. \"Failed\")))\n       (t/recover-as -1)\n       (t/run))\n\n=\u003e -1\n```\n\n### Task comprehension\nWhilst threading tasks from one to the other looks\npretty, it isn't particuarly suited for working with\nmutliple interdependent tasks.\n\nFor this there is `do-tasks`:\n```Clojure\n\u003e (def crucial-maths \n      (t/do-tasks [a (t/task (+ 1 1))\n                   b1 (t/task (inc a))\n                   b2 (dec a)]\n                  (+ a (- b1 b2))))\n=\u003e #'user/crucial-maths\n\n\u003e @(t/run crucial-maths)\n=\u003e 4\n```\nWith this, you can use binding-forms to treat task\nvalues as if they were realized, and use them in that local context.\n`do-tasks` accepts both simple values and other tasks. It automatically \"promotes\"\nsimple values to tasks in order to work with them.\n\nAs of `1.2.1`, `do-tasks` also supports syntax for `recover` and `recover-as`.\n\nThese can be placed wherever within the `do-tasks` binding block:\n\n```clojure\n\u003e (def crucial-maths\n      (t/do-tasks [a (t/task (+ 1 1))\n                   b1 (t/task (inc a))\n                   b2 (dec a)\n                   :recover #(.getMessage %)]\n                  (+ a (- b1 b2))))\n=\u003e #'user/crucial-maths\n```\n\n\u003cb\u003eNote:\u003c/b\u003e `do-tasks` essentially desugars to nested `then`-calls,\nwhich means that the binding-forms are \u003ci\u003eserialised\u003c/i\u003e.\n\n### Parallelism\nHalfing supports parallel execution with the functions:\n\n * `mapply` - given any number of tasks and a function of arity equal to that number,\n   will call that function with all the values of those tasks if they are successful:\n\n```clojure\n\u003e (def task1 (t/task 1))\n=\u003e #'halfling.task/task1\n\n\u003e (def task2 (t/task 2))\n=\u003e #'halfling.task/task2\n\n\u003e (def task3 (t/task 3))\n=\u003e #'halfling.task/task3\n\n\u003e @(t/run (t/mapply (fn [a b c] (+ a b c)) task1 task2 task3)) ; task1, task2, task3 executed in parallel\n=\u003e 6\n```\n\n * `zip` - takes any number of tasks and returns a task, which, in case of success, aggregates their values\n   in a vector:\n\n```clojure\n\u003e @(t/run (t/zip task1 task2 task3)) ; task1, task2, task3 executed in parallel\n=\u003e [1 2 3]\n```\n\n * `sequenced` - takes a collection of tasks and returns a task, which, in case of success, aggregates the values of\n   those tasks in the same type of collection:\n\n```clojure\n\u003e @(t/run (t/sequenced #{task1 task2 task3})) ; task1, task2, task3 executed in sequence\n=\u003e #{1 2 3}\n```\n\nFailed tasks will contain the error of the **first** execution that failed.\nSee `halfing.task` for more information.\n\n#### Library functions\nAs of version `1.0.0` halfling has a separate namespace called `lib`, which contains\ndifferent types of library functions that use the `task` API in their implementation.\n\nCurrent functions:\n * `p-map` - just like `map` but returns a task, which applies the function in parallel.\n Similarly to Clojure's `pmap`, should only be used when the computation performed outweighs\n the distribution overhead.\n\n An example usage:\n```clojure\n\u003e (require '[halfling.lib :refer [p-map]])\n=\u003e nil\n\n\u003e (defn letters [start-char]\n   (iterate (comp char inc int) start-char))\n=\u003e #'user/letters\n\n\u003e (def alph (vec (concat\n                   (take 26 (letters \\A))\n                   (take 26 (letters \\a)))))\n=\u003e #'user/alph\n\n\u003e (defn rand-str [n]\n   (-\u003e\u003e (range 0 n)\n        (map (fn [_] (rand-nth alph)))\n        (apply str)))\n=\u003e #'user/rand-str\n\n\u003e (defn strings [n length]\n   (-\u003e\u003e (range 0 n)\n        (map (fn [_] (rand-str n)))))\n=\u003e #'user/strings\n\n\u003e (def work (-\u003e\u003e (strings 4000 1000)\n                 (p-map clojure.string/lower-case)))\n=\u003e #'user/work\n\n\u003e (time (do (t/run work) ()))\n\"Elapsed time: 1.258364 msecs\"\n```\n## License\n\nCopyright © 2017-2021 Robert Marius Avram\n\nDistributed under the MIT License.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favramrobert%2Fhalfling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favramrobert%2Fhalfling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favramrobert%2Fhalfling/lists"}