{"id":25926928,"url":"https://github.com/gersak/dreamcatcher","last_synced_at":"2025-12-12T01:20:38.734Z","repository":{"id":62434035,"uuid":"8624400","full_name":"gersak/dreamcatcher","owner":"gersak","description":"Clojure and Clojurescript library that strives to simulate state machine behavior and easy state management","archived":false,"fork":false,"pushed_at":"2023-01-10T09:40:10.000Z","size":207,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-28T14:55:43.900Z","etag":null,"topics":["clojure","clojurescript","dreamcatcher","statemachine"],"latest_commit_sha":null,"homepage":null,"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/gersak.png","metadata":{"files":{"readme":"README.asciidoc","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-03-07T09:51:09.000Z","updated_at":"2023-01-10T11:22:13.000Z","dependencies_parsed_at":"2023-02-08T18:00:24.555Z","dependency_job_id":null,"html_url":"https://github.com/gersak/dreamcatcher","commit_stats":null,"previous_names":["kovacnica/dreamcatcher"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gersak%2Fdreamcatcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gersak%2Fdreamcatcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gersak%2Fdreamcatcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gersak%2Fdreamcatcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gersak","download_url":"https://codeload.github.com/gersak/dreamcatcher/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241731744,"owners_count":20010781,"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","clojurescript","dreamcatcher","statemachine"],"created_at":"2025-03-03T20:04:49.989Z","updated_at":"2025-10-03T23:16:15.813Z","avatar_url":"https://github.com/gersak.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"= Dreamcatcher\nRobert Gersak\u003crobi@neyho.com\u003e\n:revnumber: v.1.0\n:revdate: 05.02.2019\n:revremark: First draft\n:sectnums:\n:toc: preamble\n:toc-title: ToC\n:description: Dreamcatcher introduction\n:source-highlighter: rouge\n/* :source-highligher: pygments */\n\n\n== What is dreamcatcher?\nDreamcatcher is naive implementation of basic state machine concepts.\nConcept itself can be alternative for currently available programing\ntools/techniques like polymorphism and abstraction as well as a tool that\nprovides high level overview of program structure or application architecture.\n\n== Elements of dreamcatcher\nThere are two entities that are explored in this naive implementation. \nModel and model instance. Model that is collection consisted of \nstate-\u003estate transition/validator mapping.\nModel itself should be stateless as possible. \n\nNext to state machine model there should be some instance of that model. \nSomething we can run, test, play around with.\n\n=== Model instance\nMachine instance is a collection of _(model, data, context, state)_ where:\n\n- _model_ is set of logic rules and transformations. \n- _data_ is some kind of data provided by runtime environment. \n- _context_ is data as well except this data is not supposed to be in relation with \nstate/data and should be in relation with model. \n- Current _state_ positions instance data in model so that\ninstance can move through model based on logic rules and transformations.\n\n\u003c\u003c\u003c\n\n=== Model\nDoesn't do anything by itself. It is a clean representation of transformations that should\nhappen if some conditions are met based on current position(state) in a model. Model has\nnothing to do with model instance per se.\n\n\nimage::doc/core/images/Dreamcatcher_basic.svg[Basic Model,width=\"400\",align=\"center\"]\n\n==== State\nState is *not* complected with instance data and certainly not mutable. State\nshould be something that is unique, something that can uniquely position \nmachine instance inside a model. This can be number, string, keyword as well\nas Object, map, record, or any other type of supported runtime data.\n\n\n==== Transition\nTransitions are functions of one argument. This functions or methods take machine instance \nas input value and transform it to new machine instance.\n//[math,file=\"formulas/transition_def.png\"]\n//--\n//\\begin{align}\n//  t(instance)= \n//\\end{align}\n//--\n\n\n==== Validator\nSimilar to transitions, validators are functions that take machine instance as input and produce \nboolean value stating if transition can happen or if transition can't happen.\n//[math,file=\"formulas/validator_def.png\"]\n//--\n//\\begin{align}\n//  v(instance) \u0026=true | false\n//\\end{align}\n//--\n\n\u003c\u003c\u003c\n\n\n== State transition mechanics\nDreamcatcher considers one *special* reference to state that can be used to set \nmodel transformations and logic. *ANY* state is special in terms that machine instance \ncan't ever be in ANY state. ANY state is considered only when applying transitions or \nprocessing validators. It is just a tool to simplify modeling since complexity groves \nwith each new state.\n\n\nimage::doc/core/images/Dreamcatcher_transition.svg[Transition Details, width=\"600\", height=\"400\", align=\"center\"]\n\nTherefore multiple input transitions to state can share common ANY to state transition or validator. \nVice versa multiple output transitions can share common state to ANY transition and validator.\n\n\n=== Order of execution:\n\n . Validates instance and if instance is valid for transition BEFORE to ANY then apply \n *outgoing* state transition to instance otherwise return BEFORE instance\n . Validates instance from previous step (1) with direct validator BEFORE to AFTER and \n then apply *direct* state transition BEFORE to AFTER. Otherwise return BEFORE instance\n . Validates instance from previous step (2) with incoming validator ANY to AFTER and \n then apply *incoming* state transition ANY to AFTER and return final result. Otherwise return BEFORE instance\n\n=== Instruments\nTo get more information about what is going on inside state machine, beside\npossibility to implement logging or some other tool into transitions, dreamcatcher \noffers optional *ANY* to *ANY* hook that will accept function or method of two arguments. \nFirst argument is _instance_before_transition_ and second argument is _instance_after_transition_. \nThis hook will activate only upon successful transition, so if one of phases of transition \ndidn't happen and result is input instance, then ANY to ANY won't be called.\n\n[NOTE]\nResult of ANY to ANY function is not affecting transition. Transition already happened, \nso this is place only for informing outside services about what happened.\n\n\u003c\u003c\u003c\n\n== Example STM\nWhat can we do with elements described so far. Let us try and create not that complex state \nmachine that will make beverages. This state machine should be capable to make following beverages:\n\n- Black Coffee\n- Latte\n- Espresso\n- Espressino\n- Cacao\n- Milk\n\nMachine should have milk, black coffee, coffee for espresso, cacao, water and sugar \navailable for making beverages. We won't go into detail about how \nmuch of each does machine currently have. These resources are simply\nalways available.\n\nRecipes:\n\n . Black Coffee = Water + Coffee\n . Latte = Water + Coffee + Milk\n . Espresso = Water + Espresso Coffee\n . Espressino = Water + Espresso Coffee + Milk\n . Cacao = Milk + Chocolate\n . Milk = Milk\n \nWe'll call this machine _Beverage Maker_ and it would look like something like this.\n\nimage::doc/core/images/beverage_maker.svg[Beverage Maker, width=\"400\", height=\"600\", align=\"center\"]\n\nPicture above doesn't provide any implementation details of what each transition should do. \nHow will water be heated, where can we find black/espresso coffee? What dosage is supposed \nto be used etc. Only abstract model is provided and in that abstract model one information \nis important for the task at hand. This information is what beverage do we want to brew/make.\n_make-beverage_ is going to be start state of machine instance. \nMachine instance will behave according to rules and transitions from model above. \nData for this instance will be a _Map_ data structure with key *\"beverage/type\"* \nholding a value of selected beverage.\n\nLet's say that we defined function:\n[source,clojure]\n----\n(defn is-beverage \n  \"Function expects set of beverages as input. Return value is\n  function that accepts machine instance and returns \\\"true\\\" if \n  machine instance data has :beverage/type that is contained in \n  input set. Otherwise false\"\n  [beverages]\n  {:pre [(set? beverages)\n         (not-empty beverages)]}\n  (fn [instance]\n    (let [{beverage :beverage/type} (dreamcatcher/data instance)]\n      (contains? beverages beverage))))\n----\n\n\u003c\u003c\u003c\n\nValidators:\n[source,clojure]\n----\n[:make-beverage :black         (is-beverage #{\"Black Coffee\"})\n :make-beverage :espresso      (is-beverage #{\"Espresso\" \"Espressino\"})\n :make-beverage :milk          (is-beverage #{\"Cacao\" \"Latte\" \"Milk\"})\n :milk          :chocolate     (is-beverage #{\"Cacao\"})\n :milk          :sugar         (complement (is-beverage #{\"Cacao\"}))\n :black         :milk          (is-beverage #{\"Latte\"})\n :black         :sugar         (is-beverage #{\"Black Coffee\"})\n :espresso      :milk          (is-beverage #{\"Espressino\"})\n :espresso      :sugar         (is-beverage #{\"Espresso\"})]\n----\nCode above is proposed way of structuring validators. First \"column\" is\nsource state, second one is destination state and third column represents\nvalidator function that returns true if \"beverage/type\" of instance is \ncontained in second argument. Actually, function _is-beverage_ returns function that does that.\n\nBasically, this is all the logic we need to move from state _make-beverage_ \nto state _beverage-made_. Implementation of transitions doesn't influence \ntraversing this graph except if transition is tempering with the value of\n\"beverage/type\" key. \n\n\u003c\u003c\u003c\n\n== STM Composition\nThere are no obstacles to extend _Beverage Maker_ and create for example _Vending Machine_.\nTo extend _Beverage Maker_ to _Vending Machine_ new states, transitions and validators are required. \n_Beverage Maker_ model will remain as is. It doesn't require change, since its function is \nto make beverage based only on \"beverage/type\".\n\nWhat does _Vending machine_ require?\n\n - Money input - for end users to insert money and change \"money/balance\"\n - Money return - either on user beverage selection or on explicit return money action machine\nshould return \"money/balance\" to end user\n - Beverage selection - End user should somehow select beverage, and if enough money was inserted\nvending machine should provide user with beverage, as well as change\n - Shutdown - state that marks end of model traversing\n\n\nimage::doc/core/images/vending_machine.svg[Vending Machine, width=\"500\", height=\"800\", align=\"center\"]\n\n\u003c\u003c\u003c\nFrom picture above, we can see that when user makes choice it will move machine instance to \ndifferent state. We can define which state by adding value to key \"vending-machine/selected\" in\nmachine instance data. After storing information in machine instance data, machine instance can\nresolve next step based on available transitions that are valid to complete.\n\nFor that purpose, let's define function _is-selected_ that will be used to check if user has selected\nspecific choice.\n\n\nIn addition, another function is created that will check if there is enough money inserted into\nvending machine to allow transition to _make-beverage_ state of _Beverage Maker_ model. \n\n[source, clojure]\n----\n(defn is-selected \n  \"Function returns function that accepts machine instance, extracts machine instance\n  data and compares value of \"vending-machine/selected\" key to input choice\"\n  [choice]\n  (fn [instance]\n    (let [{choice' :vending-machine/selected} (dreamcatcher/data instance)]\n      (= choice choice'))))\n\n(defn enough-money? \n  \"Function compares current money balance with beverage price. Returns true\n  if money balance is greater or equal to beverage price\"\n  [instance]\n  (let [{price :beverage/price\n         balance :money/balance\n         :or {balance 0}} (dreamcatcher/data instance)]\n    (\u003e= balance price)))\n\n(def not-enough-money? (complement enough-money?))\n----\n\nIn the end traversal of model transitions is limited by model validators for given machine instance.\n\nValidators:\n[source, clojure]\n----\n[d/any-state      :insert-money    (is-selected \"Insert Money\")\n d/any-state      :select-beverage (is-selected \"Choose Beverage\")\n :choose          :return-money    (is-selected \"Return Money\")\n d/any-state      :shutdown        (is-selected \"Shutdown\")\n :select-beverage :make-beverage   enough-money?\n :select-beverage :end             not-enough-money?]\n----\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgersak%2Fdreamcatcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgersak%2Fdreamcatcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgersak%2Fdreamcatcher/lists"}