{"id":13533452,"url":"https://github.com/oluwatimilehin/anchors","last_synced_at":"2025-04-01T21:32:19.655Z","repository":{"id":40649037,"uuid":"453519010","full_name":"oluwatimilehin/anchors","owner":"oluwatimilehin","description":"C++ library for incremental computing","archived":false,"fork":false,"pushed_at":"2022-10-08T14:35:53.000Z","size":17542,"stargazers_count":20,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-02T20:33:10.706Z","etag":null,"topics":["cpp","incremental-computation"],"latest_commit_sha":null,"homepage":"https://oluwatimilehin.github.io/anchors","language":"C++","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/oluwatimilehin.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":"2022-01-29T21:08:17.000Z","updated_at":"2024-09-06T12:53:30.000Z","dependencies_parsed_at":"2023-01-19T16:03:49.302Z","dependency_job_id":null,"html_url":"https://github.com/oluwatimilehin/anchors","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oluwatimilehin%2Fanchors","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oluwatimilehin%2Fanchors/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oluwatimilehin%2Fanchors/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oluwatimilehin%2Fanchors/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oluwatimilehin","download_url":"https://codeload.github.com/oluwatimilehin/anchors/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246713383,"owners_count":20821881,"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":["cpp","incremental-computation"],"created_at":"2024-08-01T07:01:19.990Z","updated_at":"2025-04-01T21:32:19.257Z","avatar_url":"https://github.com/oluwatimilehin.png","language":"C++","funding_links":[],"categories":["Basic Components"],"sub_categories":["Computation"],"readme":"# Anchors\r\n\r\n![ubuntu](https://github.com/oluwatimilehin/anchors/actions/workflows/ubuntu.yml/badge.svg)\r\n\r\nAnchors is a C++ library for [incremental computing](https://en.wikipedia.org/wiki/Incremental_computing) based\r\non [lord/anchors](https://github.com/lord/anchors) for rust\r\nand [janestreet/incremental](https://github.com/janestreet/incremental) for OCaml.\r\n\r\nQuoting [janestreet/incremental](https://github.com/janestreet/incremental), it allows you build large calculations \r\n(of the kind you might build into a spreadsheet) that can react efficiently to changing data. \r\n\r\nYou can view the [accompanying blog post](https://timilearning.com/posts/incremental-computing/) for more information. \r\n\r\n[API Documentation.](https://oluwatimilehin.github.io/anchors/)\r\n\r\n## Usage\r\n\r\nAn `Anchor` represents a node in the graph (think a spreadsheet cell) and you can either create an `Anchor` with a value\r\nor define an `updater` function to create an `Anchor` from one or more Anchors.\r\n\r\nAs a basic example, let's define an `Anchor` whose value is the sum of two other anchors.\r\n\r\n````cpp\r\n#include \u003canchors/anchorutil.h\u003e\r\n#include \u003canchors/engine.h\u003e\r\n\r\nusing namespace anchors;\r\n\r\nEngine d_engine; // First set up the anchors engine\r\n\r\nAnchorPtr\u003cint\u003e anchorA(Anchors::create(2));\r\nAnchorPtr\u003cint\u003e anchorB(Anchors::create(3));\r\n\r\nauto sum = [](int a, int b) { return a + b; }; // Updater function\r\n\r\nauto anchorC(Anchors::map2\u003cint\u003e(anchorA, anchorB, sum)); // Note that the function will not be called until you `get` the value of `anchorC`.\r\n\r\n````\r\n\r\nAnchors follows a demand-driven model and will only (re)compute the value of an `Anchor` when you observe the `Anchor` \r\nand call `get()`.\r\n\r\n````cpp\r\nd_engine.observe(anchorC);\r\nEXPECT_EQ(d_engine.get(anchorC), 5);\r\n````\r\n\r\nIf you update the value of an `Anchor` that an observed `Anchor` depends on, subsequent calls for the observed `Anchor`\r\nwill return the updated value.\r\n\r\n````cpp\r\nd_engine.set(anchorA, 10);\r\nEXPECT_EQ(d_engine.get(anchorC), 13);\r\n````\r\n\r\n### More Examples\r\n\r\n#### String Concatenation\r\n\r\n````cpp\r\nauto username(Anchors::create(std::string(\"John\")));\r\n\r\nauto concatenate = [](const std::string\u0026 text) { return \"Hello, \" + text; };\r\n\r\nauto greeting(Anchors::map\u003cstd::string\u003e(username, concatenate));\r\n\r\nd_engine.observe(greeting);\r\n\r\nEXPECT_EQ(\"Hello, John\", d_engine.get(greeting));\r\n\r\nd_engine.set(username, std::string(\"Samuel\"));\r\nEXPECT_EQ(\"Hello, Samuel\", d_engine.get(greeting));\r\n````\r\n\r\n#### Using an Input of a Different Type\r\n\r\n````cpp\r\n// Create the different anchors.\r\n\r\nauto myOrders = Anchors::create(std::vector\u003cint\u003e{150, 200, 300});\r\n\r\n// `maxOrder` and `minOrder` accept a vector and return an integer\r\nAnchorPtr\u003cint\u003e maxOrder = Anchors::map\u003cint, std::vector\u003cint\u003e\u003e(\r\n        myOrders, [](const std::vector\u003cint\u003e\u0026 v) {\r\n            return *std::max_element(v.begin(), v.end());\r\n        });\r\n\r\nAnchorPtr\u003cint\u003e minOrder = Anchors::map\u003cint, std::vector\u003cint\u003e\u003e(\r\n        myOrders, [](const std::vector\u003cint\u003e\u0026 v) {\r\n            return *std::min_element(v.begin(), v.end());\r\n        });\r\n\r\n// `orderRange` depends on two derived anchors. None of the values will be computed until you call `get`.\r\nAnchorPtr\u003cint\u003e orderRange = Anchors::map2\u003cint\u003e(\r\n        maxOrder, minOrder, [](int max, int min) { return max - min; });\r\n\r\n\r\n// Observe multiple anchors at a time. \r\n// Note that `orderRange` will still return the correct values below if you do not observe `maxOrder` and `minOrder`.\r\n// However, their values will be undefined unless you call `get` for the unobserved values after `orderRange` has been computed.\r\nstd::vector\u003cAnchorPtr\u003cint\u003e\u003e anchorsToObserve{\r\n        maxOrder, minOrder, orderRange};\r\n\r\nd_engine.observe(anchorsToObserve);\r\n\r\n// Verify the expected values\r\nEXPECT_EQ(d_engine.get(maxOrder), 300);\r\nEXPECT_EQ(d_engine.get(minOrder), 150);\r\nEXPECT_EQ(d_engine.get(orderRange), 150);\r\n\r\n// Update the input list and re-verify\r\nd_engine.set(myOrders, {300, 400, 800});\r\n\r\nEXPECT_EQ(d_engine.get(maxOrder), 800);\r\nEXPECT_EQ(d_engine.get(minOrder), 300);\r\nEXPECT_EQ(d_engine.get(orderRange), 500);\r\n````\r\n\r\n#### Verifying That It Avoids Needless Computations\r\n\r\n````cpp\r\n// Given an Anchor `result`, whose value is derived from (W + X) - Z, \r\n// Anchors will not recompute the sum if only the value of Z changes after we have first computed `result`.\r\n\r\nauto anchorW(Anchors::create(10));\r\nauto anchorX(Anchors::create(4));\r\n\r\nint  additionCounter = 0;\r\nauto anchorY(\r\n    Anchors::map2\u003cint\u003e(anchorW, anchorX, [\u0026additionCounter](int a, int b) {\r\n        additionCounter++;\r\n        return a + b;\r\n    }));\r\n\r\nauto anchorZ(Anchors::create(5));\r\n\r\nint  subtractionCounter = 0;\r\nauto result(\r\n    Anchors::map2\u003cint\u003e(anchorY, anchorZ, [\u0026subtractionCounter](int a, int b) {\r\n        subtractionCounter++;\r\n        return a - b;\r\n    }));\r\n\r\nd_engine.observe(result);\r\n\r\nEXPECT_EQ(d_engine.get(result), 9);\r\nEXPECT_EQ(additionCounter, 1);\r\nEXPECT_EQ(subtractionCounter, 1);\r\n\r\nd_engine.set(anchorZ, 7);\r\n\r\nEXPECT_EQ(d_engine.get(result), 7);\r\nEXPECT_EQ(additionCounter,1);  // It shouldn't recompute anchorY because its value did not change\r\nEXPECT_EQ(subtractionCounter, 2);\r\n````\r\n\r\n#### A Quadratic Formula Calculator\r\n\r\n````cpp\r\n    auto a(Anchors::create(2));\r\n    auto b(Anchors::create(-5));\r\n    auto c(Anchors::create(-3));\r\n\r\n    int bsquareCounter = 0;\r\n    int fourACCounter  = 0;\r\n\r\n    auto negativeB = Anchors::map\u003cdouble\u003e(b, [](double b) { return -b; });\r\n    auto bSquare   = Anchors::map\u003cdouble\u003e(b, [\u0026bsquareCounter](double b) {\r\n        bsquareCounter++;\r\n        return b * b;\r\n    });\r\n\r\n    auto fourAC =\r\n        Anchors::map2\u003cdouble\u003e(a, c, [\u0026fourACCounter](double x, double y) {\r\n            fourACCounter++;\r\n            return 4 * x * y;\r\n        });\r\n\r\n    auto squareRoot = Anchors::map2\u003cdouble\u003e(\r\n        bSquare, fourAC, [](double x, double y) { return std::sqrt(x - y); });\r\n\r\n    int  denominatorCounter = 0;\r\n    auto denominator = Anchors::map\u003cdouble\u003e(a, [\u0026denominatorCounter](double a) {\r\n        denominatorCounter++;\r\n        return 2 * a;\r\n    });\r\n\r\n    using FunctionType  = std::function\u003cdouble(double\u0026, double\u0026, double\u0026)\u003e;\r\n    FunctionType x1Func = [](double x, double y, double z) {\r\n        return (x + y) / z;\r\n    };\r\n\r\n    FunctionType x2Func = [](double x, double y, double z) {\r\n        return (x - y) / z;\r\n    };\r\n\r\n    auto x1 = Anchors::map3\u003cdouble\u003e(negativeB, squareRoot, denominator, x1Func);\r\n    auto x2 = Anchors::map3\u003cdouble\u003e(negativeB, squareRoot, denominator, x2Func);\r\n\r\n    d_engine.observe(x1);\r\n    d_engine.observe(x2);\r\n\r\n    {\r\n        EXPECT_EQ(3, d_engine.get(x1));\r\n        EXPECT_EQ(-0.5, d_engine.get(x2));\r\n\r\n        EXPECT_EQ(1, bsquareCounter);\r\n        EXPECT_EQ(1, fourACCounter);\r\n        EXPECT_EQ(1, denominatorCounter);\r\n    }\r\n\r\n    d_engine.set(c, -7);\r\n\r\n    {\r\n        EXPECT_EQ(3.5, d_engine.get(x1));\r\n        EXPECT_EQ(-1, d_engine.get(x2));\r\n\r\n        // Only the value of C changed, so only anchors\r\n        // that depend on C should be recalculated\r\n        EXPECT_EQ(1, bsquareCounter);\r\n        EXPECT_EQ(2, fourACCounter);\r\n        EXPECT_EQ(1, denominatorCounter);\r\n    }\r\n````\r\n\r\n### Note\r\n\r\n- When you `get` an observed node, it will bring any other \"stale\" observed nodes up to date. An observed node is stale\r\n  if any of its input has changed since it was last brought up to date.\r\n\r\n## Installation\r\nYou can use Anchors from a CMake project by extracting the [file](https://github.com/oluwatimilehin/anchors/releases/download/v0.1.0/anchors_ubuntu.7z.zip) and adding the following:\r\n\r\n````\r\n# CMakeLists.txt\r\nlist(APPEND CMAKE_PREFIX_PATH \u003cpath_to_library_folder)\r\nfind_package(Boost REQUIRED) # Anchors requires a Boost installation\r\nfind_package(Anchors REQUIRED)\r\n....\r\n\r\ntarget_link_libraries(${YOUR_TARGET} PRIVATE Anchors::anchors)\r\n````\r\n\r\n## Roadmap\r\n\r\nThis is still a work in progress, and some tasks I intend to work on in the near future are:\r\n\r\n- Implement [lord/anchors](https://lord.io/spreadsheets/) optimization - scroll to \"anchors, a hybrid solution\".\r\n- Implement a `setUpdater()` function that allows you change the updater function for an `Anchor`.\r\n- Cycle Detection.\r\n- Add support for\r\n  an [Incremental.bind](https://ocaml.janestreet.com/ocaml-core/latest/doc/incremental/Incremental__/Incremental_intf/#bind)\r\n  equivalent.\r\n- Support caching input parameters.\r\n- ~~Support for `map3`, `map4`, etc.~~\r\n- More tests.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foluwatimilehin%2Fanchors","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foluwatimilehin%2Fanchors","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foluwatimilehin%2Fanchors/lists"}