{"id":16770106,"url":"https://github.com/oubiwann/maintaining-state-in-clojure","last_synced_at":"2025-10-14T09:39:43.897Z","repository":{"id":10351855,"uuid":"12489307","full_name":"oubiwann/maintaining-state-in-clojure","owner":"oubiwann","description":"Examples of stateful data in Clojure","archived":false,"fork":false,"pushed_at":"2018-02-11T18:51:49.000Z","size":29,"stargazers_count":21,"open_issues_count":6,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-23T09:32:29.718Z","etag":null,"topics":["channels","clojure","closures","code-exploration","code-play","data","light-weight-processes","lisp","objects","protocols","state"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oubiwann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-08-30T15:17:10.000Z","updated_at":"2024-06-05T12:49:43.000Z","dependencies_parsed_at":"2022-09-22T19:01:16.191Z","dependency_job_id":null,"html_url":"https://github.com/oubiwann/maintaining-state-in-clojure","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/oubiwann/maintaining-state-in-clojure","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oubiwann%2Fmaintaining-state-in-clojure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oubiwann%2Fmaintaining-state-in-clojure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oubiwann%2Fmaintaining-state-in-clojure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oubiwann%2Fmaintaining-state-in-clojure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oubiwann","download_url":"https://codeload.github.com/oubiwann/maintaining-state-in-clojure/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oubiwann%2Fmaintaining-state-in-clojure/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279018618,"owners_count":26086404,"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-10-14T02:00:06.444Z","response_time":60,"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":["channels","clojure","closures","code-exploration","code-play","data","light-weight-processes","lisp","objects","protocols","state"],"created_at":"2024-10-13T06:16:01.863Z","updated_at":"2025-10-14T09:39:43.871Z","avatar_url":"https://github.com/oubiwann.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Maintaining State in Clojure\n\n\n## Caveat\n\nThese examples are here to help those new to Clojure in establishing\nanalogies with other languages (or Lisp dialects). Please keep in mind\nthe travesties committed in the majority of object-oriented software over\nthe decades since the 80s: data and behaviour should not be conflated.\n\nTo put a finer point on this, if you are looking for proper design patterns\nfor working with state in Clojure applications, be sure to purchase any\nnumber of the Clojure books dedicated to real-world applications. Please do\nnot treat this project as an application reference.\n\nOne note on real-world applications: I highly recommend Stuart Sierra's\n[component library][component] for separating your apps into components\nand sharing only that state which each needs between components.\n\nThis is an old repo, but since it comes up in search results, I try to keep\nit updated.\n\n\n## Background\n\nLearning how to work with state in such a way as fits the language of one's\nchoice is critical for building production-ready applications.\n\nLearning how to _play_ with state in different ways is actually a fun topic\nof exploration. Different languages often have widely diverging features\nthat allow one to maintain state in all sorts of unique ways.\n\nThe examples in this repo explore some of this, from the perspective of\nClojure. They include:\n\n* Using closures\n* Using a data structure\n* Using Clojure protocols\n* Using `core.async` channels\n\nEnjoy!\n\n\n## Preparations\n\n\n### Getting the Code\n\nTo play with the examples in this repo, you'll need to clone it to the\nmachine that you're working on:\n\n```shell\n$ git clone https://github.com/oubiwann/maintaining-state-in-clojure.git\n$ cd maintaining-state-in-clojure\n```\n\n\n### Firing up the REPL\n\nIf you don't have `lein` installed, you'll need to [download it][lein-dl]. When\n`lein` is on your system, make sure you're in the cloned directory for this\nrepository and start up the REPL:\n\n```\n$ cd maintaining-state-in-clojure\n$ lein repl\n\nstate-examples.dev=\u003e\n```\n\nAt this point, you will have access to the following, which referencce the\nnamespaces of the different examples in this repository:\n\n* `channels`\n* `closures`\n* `data`\n* `protocols`\n\nWe will use each of these below.\n\n\n## Examples\n\nThe examples below are adapted from an example given by Peter Norvig in\nChapter 13 of his famous Lisp/AI book, [PAIP][paip]. The\n[Chapter 13 PAIP source code][paip-ch13] is available online at the\n[Peter Norvig site][norvig-site].\n\n\n### Using Closures\n\nOnce upon a time (before CLOS), if you wanted to maintain state in Lisp, you\nused closures. As a nod to this savory history, we start with a closure\nexample.\n\nThis example uses nested closures to:\n\n1. dispatch based upon a passed keyword, and\n\n1. return a dispatched function that has access to the top-level function's\n   variables as well as variables that are passed in to the nested functions.\n\nThis sort of construction provides some of the basic functionality of an\nobject system (mostly just state data).\n\nHere is a link to the [state-via-closures example code][closures].\nTake a look, then let's see how this works in action:\n\n\n```clj\nstate-examples.dev=\u003e (def acc (closures/new-account \"savings\" 1000 0.05))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (closures/get-name acc)\n\"savings\"\nstate-examples.dev=\u003e (closures/get-balance acc)\n1000\n```\n\nIf we call any functions that make any changes to state data, a new account\nobject gets returned. As such, in those cases we'll need to reasign the new\nobject to our account variable:\n\n```clj\nstate-examples.dev=\u003e (def acc (closures/deposit acc 150.50))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (closures/get-balance acc)\n1150.5\nstate-examples.dev=\u003e (def acc (closures/apply-interest acc))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (closures/get-balance acc)\n1208.025\nstate-examples.dev=\u003e (def acc (closures/withdraw acc 25.25))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (closures/get-balance acc)\n1182.775\nstate-examples.dev=\u003e (closures/withdraw acc 2000)\n\nException : Insufficient funds.  state-examples.closures/new-account/fn--28/fn--38 (closures.clj:28)\n```\n\n### Data Structures as a Counter Example\n\nMost of these examples are using fairly elaborate means of doing something\nquite simple: tracking data. What simpler way to do that than a data\nstructure? None, that's what way.\n\nThis example provides a convenience function which creates a simple map. This\nallows us to use it just like we did the previous example. The functions,\ninstead of extracting info from nested closures, simply operate on the\nprovided data structure.\n\nNote that this approach is not thread-safe.\n\nHere is a link to the [state-via-data-structures example code][data].\n\nFor this example, we've used an identical set of functions as the closures\nexample, with no fancy-pants. Just data. We'll start it off like we did\nbefore:\n\n```clj\nstate-examples.dev=\u003e (def acc (data/new-account \"savings\" 1000 0.05))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (data/get-name acc)\n\"savings\"\nstate-examples.dev=\u003e (data/get-balance acc)\n1000\n```\n\nLet's walk through the same steps:\n\n```clj\nstate-examples.dev=\u003e (def acc (data/deposit acc 150.50))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (data/get-balance acc)\n1150.5\nstate-examples.dev=\u003e (def acc (data/apply-interest acc))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (data/get-balance acc)\n1208.025\nstate-examples.dev=\u003e (def acc (data/withdraw acc 25.25))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (data/get-balance acc)\n1182.775\nstate-examples.dev=\u003e (data/withdraw acc 2000)\n\nException : Insufficient funds.  state-examples.data/withdraw (data.clj:27)\n```\n\n### Using Protocols and Records\n\nWe now take a look at Clojure's wrapping around Java interfaces and classes\nas a means of maintaining state.\n\nAgain, we've set things up so that the usage is almost identical to the\nprevious examples.\n\nHere is a link to the [state-via-protocols example code][protocols].\n\n```clj\nstate-examples.dev=\u003e (def acc (protocols/new-account \"savings\" 1000 0.05))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (protocols/get-name acc)\n\"savings\"\nstate-examples.dev=\u003e (protocols/get-balance acc)\n1000\n```\n\nAnd now for some operations on our data:\n\n\n```clj\nstate-examples.dev=\u003e (def acc (protocols/deposit acc 150.50))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (protocols/get-balance acc)\n1150.5\nstate-examples.dev=\u003e (def acc (protocols/apply-interest acc))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (protocols/get-balance acc)\n1208.025\nstate-examples.dev=\u003e (def acc (protocols/withdraw acc 25.25))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (protocols/get-balance acc)\n1182.775\nstate-examples.dev=\u003e (protocols/withdraw acc 2000)\n\nException : Insufficient funds.  state-examples.protocols.Account (protocols.clj:22)\n```\n\n\n### Using `core.async` Channels\n\nThis example is a completely different animal ... on the surface.\nGeneralizing to core concepts, this is very simiilar to using closures.\n\nHere is a link to the [state-via-channels example code][channels].\n\n```clj\nstate-examples.dev=\u003e (def acc (channels/new-account \"savings\" 1000 0.05))\n#'state-examples.dev/acc\nstate-examples.dev=\u003e (channels/get-name acc)\n\"savings\"\nstate-examples.dev=\u003e (channels/get-balance acc)\n1000\n```\n\nLet's walk through the same steps as the other examples:\n\n```clj\nstate-examples.dev=\u003e (channels/deposit acc 150.50)\n:ok\nstate-examples.dev=\u003e (channels/get-balance acc)\n1150.5\nstate-examples.dev=\u003e (channels/apply-interest acc)\n:ok\nstate-examples.dev=\u003e (channels/get-balance acc)\n1208.025\nstate-examples.dev=\u003e (channels/withdraw acc 25.25)\n:ok\nstate-examples.dev=\u003e (channels/get-balance acc)\n1182.775\nstate-examples.dev=\u003e (channels/withdraw acc 2000)\n:insufficient-funds\n```\n\n\n[lein-dl]: https://github.com/technomancy/leiningen#installation\n[closures]: src/state_examples/closures.clj\n[data]: src/state_examples/data.clj\n[protocols]: src/state_examples/protocols.clj\n[channels]: src/state_examples/channels.clj\n[paip]: http://www.amazon.com/dp/B003VWBY1I/\n[paip-ch13]: http://norvig.com/paip/clos.lisp\n[norvig-site]: http://norvig.com/\n[component]: https://github.com/stuartsierra/component\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foubiwann%2Fmaintaining-state-in-clojure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foubiwann%2Fmaintaining-state-in-clojure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foubiwann%2Fmaintaining-state-in-clojure/lists"}