{"id":16318535,"url":"https://github.com/vic/pond","last_synced_at":"2025-03-20T22:30:53.077Z","repository":{"id":57535642,"uuid":"127128526","full_name":"vic/pond","owner":"vic","description":"State aware Elixir functions without spawning processes","archived":false,"fork":false,"pushed_at":"2018-04-02T16:30:36.000Z","size":115,"stargazers_count":27,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-10-11T22:23:52.596Z","etag":null,"topics":["callbag","elixir","finite-state-machine","gen-server","generators","observables","pub-sub","reactive","reactive-streams","state-management"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/pond","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vic.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":"2018-03-28T11:12:48.000Z","updated_at":"2023-02-25T18:21:40.000Z","dependencies_parsed_at":"2022-08-28T17:41:59.071Z","dependency_job_id":null,"html_url":"https://github.com/vic/pond","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fpond","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fpond/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fpond/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vic%2Fpond/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vic","download_url":"https://codeload.github.com/vic/pond/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244085006,"owners_count":20395523,"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":["callbag","elixir","finite-state-machine","gen-server","generators","observables","pub-sub","reactive","reactive-streams","state-management"],"created_at":"2024-10-10T22:23:50.449Z","updated_at":"2025-03-20T22:30:52.815Z","avatar_url":"https://github.com/vic.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pond\n\nPond is an Elixir library for creating state handling functions without spawning processes.\n\n[![Travis](https://img.shields.io/travis/vic/pond.svg)](https://travis-ci.org/vic/pond)\n[![Hex.pm](https://img.shields.io/hexpm/v/pond.svg?style=flat-square)](https://hexdocs.pm/pond)\n\n![Monet](https://upload.wikimedia.org/wikipedia/commons/4/43/Claude_Monet_-_Reflections_of_Clouds_on_the_Water-Lily_Pond.jpg)\n\nPond functions are same-process, referentially transparent functions, that let you implement\nFinite State Machines, Generators, (push/pull) Reactive Streams.\n\nPond functions don't require you to spawn a new process ala GenServer, GenStage, etc.\nHowever a pond function can easily be part of them when needed just like any other function.\n\n#### Wait, arent processes *the* nice thing about the BEAM?\n\nSpawning a new process just to keep state is not always a good idea.\n\nDont get me wrong, one of the *best* power features of the BEAM is that it's very cheap\nto create tons of processes and supervise them.\n\nHowever abusing spawn, just because you want to keep state, well, that's certainly \nnot the smartest thing. If you created zillions of tiny processes all data between them \nwould actually be duplicated on each message pass, since processes prefer to share nothing, \nmessages get copied between them when sent.\n\nUse spawn, `GenServer` and friends when you want to do async/concurrent jobs, or provide \nservices that can handle many clients at the same time and orchestrate communication\nbetween them.\n\nSome useful resources:\n\n[To spawn, or not to spawn?](http://theerlangelist.com/article/spawn_or_not)\n\n[sasa1977/fsm](https://github.com/sasa1977/fsm)\n\n[python generators thread on EF](https://elixirforum.com/t/python-generators-equivalent/2806/10)\n\n## `import Pond`\n\nA *pond* is created by combining an initial state and a hanler function.\n\n`pond/2` returns a function that can be invoked without explicitly\ngiving a state to it. If you are curious about how it's done, \n[Pond's core is just a simple closure](https://github.com/vic/pond/tree/master/lib/pond.ex)\n\n##### Hello\n\nThe most basic example would be a function that when called just\nreturns it's initial state.\n\n```elixir\niex\u003e f = pond(:hello, fn \n...\u003e   _, state -\u003e state\n...\u003e end)\n...\u003e f.()\n:hello\n```\n\nThe previous example however, is not really interesting as it's not\ndoing much with the state, except returning it at first invocation.\n\n##### Hello World\n\nLet's create another function that can alter it's own internal state.\n\n```elixir\niex\u003e f = pond(:hello, fn \n...\u003e   pond, state = :hello -\u003e\n...\u003e     {state, pond.(:world)}\n...\u003e   pond, state -\u003e\n...\u003e     {state, pond.(state)}\n...\u003e end)\n...\u003e\n...\u003e assert {:hello, f} = f.()\n...\u003e assert {:world, f} = f.()\n...\u003e \n...\u003e elem(f.(), 0)\n:world\n```\n\nA couple of things we have to mention about the previous example:\n\nSince Elixir is a functional language, you can see that calling `f.()`\nwill return a tuple with the current state and the next function to\nbe called (a *pond* with updated state).\n\nUpdating the state is done inside the handler function \nby calling the current pond with a new state.\nIn our example, when `state = :hello`, the next function is built\nby changing the state to `:world`, in `pond.(:world)`.\n\nThe last line of our example shows that once we are in the `:world`\nstate, it wont change anymore.\n\nIf you look closely, our handler function is actually just a \nsingle-function finite state machine.\n\nAs you can see, our functions are pure, it's just that we \nare getting an *updated function* to call the next time. Exactly\nthe same as when you `Map.put` something and get a *new* map. The nice\nthing about this is, the state is managed internally by the pond\nitself and it's abstracted away for the user.\n\n### Elixir Generators\n\nLet's create a function that cycles a list of ints but on every cycle\nincrements the number of decimal positions.\n\n```elixir\ndef growing(ints) do\n  pond({ints, 1}, fn\n    pond, {[n], m}  -\u003e\n      { n * m, pond.({ints, m * 10}) }\n    pond, {[n | rest], m}  -\u003e\n      { n * m, pond.({rest, m}) }\n  end)\nend\n```\n\nThe result of calling `growing/1` is a *Generator* function that\nwill produce values each time it's called.\n\n```elixir\niex\u003e f = growing([1, 2, 3])\n...\u003e\n...\u003e assert {1, f} = f.()\n...\u003e assert {2, f} = f.()\n...\u003e assert {3, f} = f.()\n...\u003e\n...\u003e assert {10, f} = f.()\n...\u003e assert {20, f} = f.()\n...\u003e assert {30, f} = f.()\n...\u003e\n...\u003e assert {100, f} = f.()\n...\u003e f.() |\u003e elem(0)\n200\n```\n\n### Piping Functions\n\nSo, basically a *pond* is a function that is already capturing it's\nstate and is just waiting to be called with some other arguments from\nthe user. \n\nUp to now, if you notice our previous examples, all of them yield a \nfunction with zero arity `f.()`. However, you can create a *pond* that\ntakes any number of arguments.\n\nOur next example, `reduce`, yields a function that will take a single argument.\nEither the `:halt` atom to extract the current state or any other value to\nproduce the next state from calling `reducer.(acc, value)`.\n\n\n```elixir\ndef reduce(reducer, acc) do\n  pond(acc, fn\n    _, acc, :halt -\u003e\n      acc\n    pond, acc, value -\u003e\n      pond.(reducer.(acc, value))\n  end)\nend\n```\n\nThe `Pond.Next` module provides `next`. A convenience that simply takes a function \nas first argument and invokes it with all remaining arguments.\n\nFor example, `next/2` is:\n\n```elixir\ndef next(fun, arg), do: fun.(arg)\n```\n\nThis allows us to nicely pipe stateful functions as they are being produced from previous\nsteps.\n\n\n```elixir\niex\u003e import Pond.Next\n...\u003e (\u0026Kernel.+/2)\n...\u003e |\u003e reduce(0)\n...\u003e |\u003e next(10)\n...\u003e |\u003e next(3)\n...\u003e |\u003e next(200)\n...\u003e |\u003e next(:halt)\n213\n```\n\n### Piping with State Accumulators\n\nIn our last example, calling the `reduce` pond will return another\nfunction, except when called with `:halt`.\nThat's why we could pipe every function using `Pond.Next`.\n\nHowever other functions can return not only the next function but also\nthe current state, like for example our previous `growing` generator.\nIt will return tuples like `{value, next_fun}`. \n\nFor example, let's pipe only two calls to our `growing` generator and accumulate its\nvalues into a list.\n\n```elixir\n\niex\u003e alias Pond.Acc\n...\u003e f = growing([1, 2, 3])\n...\u003e\n...\u003e f\n...\u003e |\u003e Acc.into(Acc.list())\n...\u003e |\u003e next()\n...\u003e |\u003e next()\n...\u003e |\u003e Acc.value()\n[1, 2]\n```\n\nBefore calling `next`, we combine our generator with an state accumulator, \nin this case `Acc.list()`.\nCalling `Acc.value()` at the end will extract the current value from the state accumulator.\n\nThe `Pond.Acc.into/2` function creates a tuple `{acc_fun, next_fun}`, that\nimplements the `Pond.Applicative` protocol. Any data structure implementing\n`Pond.Applicative` is able to be piped naturally using `Pond.Next` functions.\n\n### Elixir Callbags\n\n[Callbag](https://github.com/callbag/callbag) is a specification for creating\nfast pull/push streams on JavaScript land.\n\nCallbags are simple functions that following a communication protocol\nbetween them can implement the so-called, *reactive programming* paradigm.\n\nCallbags are also being ported to other [languages](https://github.com/kitten/wonka),\nsince callbags have no core-library, and let you achieve the same *reactivity*\nwithout requiring full libraries like Rx and friends.\n\nOk, enought about JS, let's get back to Elixir.\n\nFirst, let's define `foo`, a *source*, in Callbag parlance, a function\nthat generates data (like GenStage's *producer*).\n\nThe `foo` *pond* starts with an initial `:idle` state. Awaiting to be called\nwith `(0, sink)`. This, in Callbag, is known as the handshake part of the\nprotocol, the source must then greet (`0`) back the sink.\n\nIn our *pond*, upon being greeted by a sink, we update the state `source.(sink)` to\nsave a reference to the sink that is greeting us, and then just greet back `sink.(0, source)`.\n\nOnce the handshake is complete, the sink can demand (`1`) data from us when it feels like. \nWe say `foo` is a *pullable source* stream.\n\nSometimes, a pullable stream can take `(1, data)`, where data can be things like \nthe amount of data desired by the sink (like GenStage's demand). \nIn our example, we just ignore this.\n\nFinally, after being asked for data, we send (`1`) some `:hello`, `:world` thingies back \nto the sink, and tell it we are done `(2, nil)` without error, and that there wont \nany more data coming from us.\n\n```elixir\ndef foo() do\n  pond(:idle, fn\n    source, :idle, 0, sink -\u003e\n      source = source.(sink)\n      sink.(0, source)\n\n    _source, sink, 1, _data -\u003e\n      sink\n      |\u003e next(1, :hello)\n      |\u003e next(1, :world)\n      |\u003e next(2, nil)\n  end)\nend\n```\n\nNow let's implement `bar`, a _sink_.\n\nJust like in our previous code, `bar` also starts with an `:idle` state.\nExpecting a greeting from a source, once received, we update the sink\ninternal status `sink.([])` with an empty list where we will accumulate\nmessages from the source.\n\nWhen the source greets us back, our state already is `[]`, so we receive\n`bound`, that is, the sink *subscribed* to the source, each callbag with\nit's state ready to exchange data. In our example, we simply return this\nas our test bellow is the one that starts the demand for data.\n\nOnce we are receiving data from the source, we simply collect it and update\nthe sink state `sink.([data | acc])`.\n\nOnce the source tell us that it is done, we simply reverse our accumulator\nand return that.\n\n\n```elixir\ndef bar() do\n  pond(:idle, fn\n    sink, :idle, 0, source -\u003e\n      sink = sink.([])\n      source.(0, sink)\n    _sink, [], 0, bound -\u003e\n      bound\n    sink, acc, 1, data -\u003e\n      sink.([data | acc])\n    _sink, acc, 2, nil -\u003e\n      acc |\u003e Enum.reverse\n  end)\nend\n```\n\nAnd now, let's wire `foo` and `bar` to work together.\n\n```elixir\niex\u003e source = foo()\n...\u003e sink = bar()\n...\u003e bound = sink.(0, source) # bar meets foo\n...\u003e bound.(1, nil) # demand data\n[:hello, :world]\n```\n\nThis way you could use `Pond` to create Elixir reactive streams.\nJust implement functions that follow the Callbag spec. And by\nusing Pond they dont necessarily need to spawn a new process \nfor each combinator.\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:pond, \"~\u003e 0.2\"}\n  ]\nend\n```\n\nDocumentation can be found at [https://hexdocs.pm/pond](https://hexdocs.pm/pond).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Fpond","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvic%2Fpond","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvic%2Fpond/lists"}