├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── benchmark ├── CMakeLists.txt ├── exceptions.cpp ├── function.cpp └── generator.cpp ├── doc ├── img │ ├── logo-250.png │ └── logo-inkscape.svg ├── quick-intro.md ├── refman-clause-modifiers.md ├── refman-command.md ├── refman-debug_print_metastack.md ├── refman-flat_handler.md ├── refman-fresh_label.md ├── refman-handle.md ├── refman-handle_ref.md ├── refman-handle_with.md ├── refman-handle_with_ref.md ├── refman-handler.md ├── refman-handler_ref.md ├── refman-invoke_command.md ├── refman-no_manage.md ├── refman-no_resume.md ├── refman-plain.md ├── refman-resumption.md ├── refman-resumption_data.md ├── refman-static_invoke_command.md ├── refman-wrap.md ├── refman-wrap_with.md └── refman.md ├── examples ├── CMakeLists.txt ├── actors.cpp ├── async-await.cpp ├── composition-actors.cpp ├── dep-injection.cpp ├── exceptions.cpp ├── fuel.cpp ├── generators.cpp ├── react.cpp ├── rollback-state.cpp ├── shift0-reset.cpp ├── state.cpp └── threads.cpp ├── include └── cpp-effects │ ├── clause-modifiers.h │ └── cpp-effects.h └── test ├── CMakeLists.txt ├── command-lifetime.cpp ├── cut-out-the-middleman.cpp ├── global-from-handle.cpp ├── handler-lifetime.cpp ├── handler-noresume.cpp ├── handlers-with-labels.cpp ├── plain-handler.cpp ├── swap-handler.cpp └── traits.cpp /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: boost 17 | run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get install -yq libboost-context-dev 18 | - name: configure 19 | run: cmake . 20 | - name: compiler-version 21 | run: /usr/bin/c++ --version 22 | - name: make 23 | run: make 24 | - name: examples 25 | run: bin/threads && bin/actors && bin/async-await && bin/generators && bin/rollback-state && bin/state && bin/shift0-reset && bin/composition-actors 26 | - name: tests 27 | run: bin/test/traits && bin/test/command-lifetime && bin/test/handler-lifetime && bin/test/cut-out-the-middleman && bin/test/swap-handler && bin/test/global-from-handle && bin/test/handlers-with-labels && bin/test/plain-handler && bin/test/handler-noresume 28 | - name: micro-benchmarks 29 | run: bin/benchmark/bench-exceptions && bin/benchmark/bench-function && bin/benchmark/bench-generator 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Backups 35 | *~ 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | 3 | project (CppEffects) 4 | 5 | set (CMAKE_BUILD_TYPE Release) 6 | set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin) 7 | 8 | include_directories ("include") 9 | 10 | message (STATUS compiler: ${CMAKE_C_COMPILER}) 11 | set (CMAKE_CXX_STANDARD 17) 12 | add_compile_options (-Wall -Wextra -pedantic -Werror -O3) 13 | 14 | # For local boost: 15 | # set (BOOST_ROOT ) 16 | FIND_PACKAGE (Boost 1.70 COMPONENTS context REQUIRED) 17 | 18 | if (Boost_FOUND) 19 | link_libraries (Boost::context) 20 | add_subdirectory (examples) 21 | add_subdirectory (test) 22 | add_subdirectory (benchmark) 23 | else() 24 | message (STATUS "Boost not found!") 25 | endif() 26 | 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | from gcc:12.1.0 2 | 3 | run apt-get -y update 4 | run apt-get -y install cmake 5 | run apt-get -y install libboost-context-dev 6 | 7 | run apt-get -y install time # needed for benchmarks 8 | 9 | workdir /home 10 | 11 | cmd echo "*** Welcome to cpp-effects container (gcc 12.1.0) ***"; bash 12 | 13 | # sudo docker build -t cppeff . 14 | # docker run -it --rm -v $(pwd):/home cppeff 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Maciej Piróg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp-effects 2 | A library for programming with effect handlers in C++ 3 | 4 | ![The C++Eff logo](doc/img/logo-250.png) 5 | 6 | **Effect handlers** allow for programming with user-defined computational effects, with applications including custom lightweight concurrency (threads, async-await, actors, generators), error handling, dependency injection, etc. Effect handlers originate from the realm of functional programming, and the main goal of this **highly experimental** library is to explore how they fit in the more object-oriented setting of C++. 7 | 8 | The library relies on modern C++ features (move semantics, variadic templates, compile-time evaluation) to achieve elegant programmer-level interface, memory management of handlers, and relative type-safety. Internally, it uses the [boost.context](https://www.boost.org/doc/libs/1_74_0/libs/context/doc/html/index.html) library for call-stack manipulation, and so it implements one-shot handlers only. 9 | 10 | ## Documentation 11 | 12 | - [Reference](doc/refman.md) - A detailed explanation of the library's API and a short discussion about the overall design of the library. 13 | 14 | - [High-level effect handlers in C++](https://dl.acm.org/doi/pdf/10.1145/3563445) - An accompanying research paper (OOPSLA 2022). 15 | 16 | ## A quick example: lightweight cooperative threads 17 | 18 | As a sneak preview, we can use effect handlers to define our own tiny library for cooperative lightweight threads. The programmer's interface will consist of two functions, `yield` and `fork`, together with a class that implements a scheduler: 19 | 20 | ```cpp 21 | void yield(); // Used by a thread to give up control 22 | void fork(std::function proc); // Start a new thread 23 | 24 | class Scheduler { 25 | public: 26 | static void Start(std::function f); 27 | }; 28 | ``` 29 | 30 | The static member function `Start` initiates the scheduler with `f` as the body of the first thread. It returns when all threads finish their jobs. 31 | 32 | To implement this interface, we first define two **commands**, which are data structures used for transferring control from the client code to the handler. We implement `yield` and `fork` to invoke these commands. 33 | 34 | ```cpp 35 | #include "cpp-effects/cpp-effects.h" 36 | namespace eff = cpp_effects; 37 | 38 | struct Yield : eff::command<> { }; 39 | 40 | struct Fork : eff::command<> { 41 | std::function proc; 42 | }; 43 | 44 | void yield() 45 | { 46 | eff::invoke_command(Yield{}); 47 | } 48 | 49 | void fork(std::function proc) 50 | { 51 | eff::invoke_command(Fork{{}, proc}); 52 | } 53 | ``` 54 | 55 | We define the scheduler, which is a **handler** that can interpret the two commands by pushing the resumptions (i.e., captured continuations) to the queue. 56 | 57 | ```cpp 58 | using Res = eff::resumption; 59 | 60 | class Scheduler : public eff::flat_handler { 61 | public: 62 | static void Start(std::function f) 63 | { 64 | queue.push_back(eff::wrap(f)); 65 | 66 | while (!queue.empty()) { 67 | auto resumption = std::move(queue.front()); 68 | queue.pop_front(); 69 | std::move(resumption).resume(); 70 | } 71 | } 72 | private: 73 | static std::list queue; 74 | void handle_command(Yield, Res r) override 75 | { 76 | queue.push_back(std::move(r)); 77 | } 78 | void handle_command(Fork f, Res r) override 79 | { 80 | queue.push_back(std::move(r)); 81 | queue.push_back(eff::wrap(f.proc)); 82 | } 83 | }; 84 | 85 | std::list Scheduler::queue; 86 | 87 | ``` 88 | 89 | And that's all it takes! We can now test our library by starting a few threads: 90 | 91 | ```cpp 92 | void worker(int k) 93 | { 94 | for (int i = 0; i < 10; ++i) { 95 | std::cout << k; 96 | yield(); 97 | } 98 | } 99 | 100 | void starter() 101 | { 102 | for (int i = 0; i < 5; ++i) { 103 | fork(std::bind(worker, i)); 104 | } 105 | } 106 | 107 | int main() 108 | { 109 | Scheduler::Start(starter); 110 | 111 | // Output: 112 | // 01021032104321043210432104321043210432104321432434 113 | } 114 | ``` 115 | 116 | ## Technical summary 117 | 118 | - **Language:** C++17 119 | - **Handlers**: deep, one-shot, stateful 120 | 121 | ## Using in your project 122 | 123 | The library is header-only, so to use it just include the headers and link with boost.context. On most systems, boost is available via a package manager, e.g., 124 | 125 | - macOS: `brew install boost` 126 | 127 | - Ubuntu: `apt-get install libboost-context-dev` 128 | 129 | You can link with boost using cmake as follows. In your `CMakeLists.txt`, use the following: 130 | 131 | ```cmake 132 | FIND_PACKAGE (Boost 1.70 COMPONENTS context REQUIRED) 133 | 134 | if (Boost_FOUND) 135 | link_libraries (Boost::context) 136 | add_executable (my_program my_program.cpp) 137 | else() 138 | message (STATUS "Boost not found!") 139 | endif() 140 | 141 | ``` 142 | 143 | ## Building examples (natively) 144 | 145 | 146 | This repository contains some examples, tests, and benchmarks. The easiest way to build these is to use `cmake`. You will need `cmake` and `boost` in any non-ancient versions. For example, the following should do the trick on macOS: 147 | 148 | ```bash 149 | $ brew install cmake 150 | $ brew install boost 151 | $ cmake . 152 | $ make 153 | ``` 154 | 155 | You can verify that the build was successful by running an example. The following will run the `threads` example - you can see the interleaving of threads in the output: 156 | 157 | ```bash 158 | $ bin/threads 159 | 01021032104321043210432104321043210432104321432434 160 | ``` 161 | 162 | ## Building examples (using Docker) 163 | 164 | You can also compile and run the examples in a Docker container. Just type in the following to build and then run the container shell: 165 | 166 | ```bash 167 | $ sudo docker build -t cppeff . 168 | $ docker run -it --rm -v $(pwd):/home cppeff 169 | ``` 170 | 171 | In the container shell type `cmake .` and then `make`. 172 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin/benchmark) 2 | 3 | add_executable (bench-exceptions exceptions.cpp) 4 | add_executable (bench-function function.cpp) 5 | add_executable (bench-generator generator.cpp) 6 | -------------------------------------------------------------------------------- /benchmark/exceptions.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Benchmark: Compare native exceptions with exceptions via effect handlers 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpp-effects/cpp-effects.h" 12 | #include "cpp-effects/clause-modifiers.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | volatile int64_t sum = 0; 17 | volatile int64_t esum = 0; 18 | 19 | volatile int64_t INC = 1; 20 | 21 | volatile int64_t i; 22 | volatile int64_t mod; 23 | 24 | // ------ 25 | // Native 26 | // ------ 27 | 28 | void testNative(int max, int mod_) 29 | { 30 | mod = mod_; 31 | for (i = 0; i < max; i += INC) 32 | { 33 | try { 34 | if (i % mod == 0) { throw 0; } 35 | sum++; 36 | } catch (int) { 37 | esum++; 38 | } 39 | } 40 | } 41 | 42 | // -------- 43 | // Handlers 44 | // -------- 45 | 46 | struct Error : eff::command<> { }; 47 | 48 | class Catch : public eff::handler { 49 | void handle_return() final override { } 50 | void handle_command(Error, eff::resumption) final override { esum++; } 51 | }; 52 | 53 | void testHandlers(int max, int mod_) 54 | { 55 | mod = mod_; 56 | for (i = 0; i < max; i += INC) 57 | { 58 | eff::handle([](){ 59 | if (i % mod == 0) { eff::invoke_command(Error{}); } 60 | sum++; 61 | }); 62 | } 63 | } 64 | 65 | // ----------------- 66 | // NoResume handlers 67 | // ----------------- 68 | 69 | class CatchNR : public eff::handler> { 70 | void handle_return() final override { } 71 | void handle_command(Error) final override { esum++; } 72 | }; 73 | 74 | void testHandlersNR(int max, int mod_) 75 | { 76 | mod = mod_; 77 | for (i = 0; i < max; i += INC) 78 | { 79 | eff::handle([](){ 80 | if (i % mod == 0) { eff::invoke_command(Error{}); } 81 | sum++; 82 | }); 83 | } 84 | } 85 | 86 | // --------------- 87 | // Static Handlers 88 | // --------------- 89 | 90 | void testSHandlers(int max, int mod_) 91 | { 92 | mod = mod_; 93 | for (i = 0; i < max; i += INC) 94 | { 95 | eff::handle([](){ 96 | if (i % mod == 0) { eff::static_invoke_command(Error{}); } 97 | sum++; 98 | }); 99 | } 100 | } 101 | 102 | // ------------------------ 103 | // Static NoResume handlers 104 | // ------------------------ 105 | 106 | void testSHandlersNR(int max, int mod_) 107 | { 108 | mod = mod_; 109 | for (i = 0; i < max; i += INC) 110 | { 111 | eff::handle([](){ 112 | if (i % mod == 0) { eff::static_invoke_command(Error{}); } 113 | sum++; 114 | }); 115 | } 116 | } 117 | 118 | // ---- 119 | // Main 120 | // ---- 121 | 122 | int main() 123 | { 124 | int MAX = 100000; 125 | 126 | std::cout << "--- exception handler + throw exception ---" << std::endl; 127 | 128 | std::cout << "native: " << std::flush; 129 | 130 | auto begin = std::chrono::high_resolution_clock::now(); 131 | testNative(100000, 1); 132 | auto end = std::chrono::high_resolution_clock::now(); 133 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << "\t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" << std::endl; 134 | 135 | sum = 0; 136 | esum = 0; 137 | 138 | std::cout << "handlers: " << std::flush; 139 | 140 | auto begin2 = std::chrono::high_resolution_clock::now(); 141 | testHandlers(100000, 1); 142 | auto end2 = std::chrono::high_resolution_clock::now(); 143 | std::cout << std::chrono::duration_cast(end2-begin2).count() << "ns" << "\t(" << (int)(std::chrono::duration_cast(end2-begin2).count() / MAX) << "ns per iteration)" << std::endl; 144 | 145 | sum = 0; 146 | esum = 0; 147 | 148 | std::cout << "handlers-n-r: " << std::flush; 149 | 150 | auto begin3 = std::chrono::high_resolution_clock::now(); 151 | testHandlersNR(100000, 1); 152 | auto end3 = std::chrono::high_resolution_clock::now(); 153 | std::cout << std::chrono::duration_cast(end3-begin3).count() << "ns" << "\t(" << (int)(std::chrono::duration_cast(end3-begin3).count() / MAX) << "ns per iteration)" << std::endl; 154 | 155 | sum = 0; 156 | esum = 0; 157 | 158 | 159 | std::cout << "s-handlers: " << std::flush; 160 | 161 | auto begins2 = std::chrono::high_resolution_clock::now(); 162 | testSHandlers(100000, 1); 163 | auto ends2 = std::chrono::high_resolution_clock::now(); 164 | std::cout << std::chrono::duration_cast(ends2-begins2).count() << "ns" << "\t(" << (int)(std::chrono::duration_cast(ends2-begins2).count() / MAX) << "ns per iteration)" << std::endl; 165 | 166 | 167 | sum = 0; 168 | esum = 0; 169 | 170 | std::cout << "s-handlers-n-r: " << std::flush; 171 | 172 | auto begins3 = std::chrono::high_resolution_clock::now(); 173 | testSHandlersNR(100000, 1); 174 | auto ends3 = std::chrono::high_resolution_clock::now(); 175 | std::cout << std::chrono::duration_cast(ends3-begins3).count() << "ns" << "\t(" << (int)(std::chrono::duration_cast(ends3-begins3).count() / MAX) << "ns per iteration)" << std::endl; 176 | 177 | } 178 | -------------------------------------------------------------------------------- /benchmark/function.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Benchmark: Compare function call with invoking a command that behaves like a function 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpp-effects/cpp-effects.h" 12 | #include "cpp-effects/clause-modifiers.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | volatile int a = 19; 17 | volatile int b = 585; 18 | 19 | volatile int MAX = 7000000; 20 | 21 | volatile int64_t SUM = 0; 22 | 23 | // ---- 24 | // Loop 25 | // ---- 26 | 27 | void testLoop(int max) 28 | { 29 | for (int i = 0; i < max; i++) { 30 | SUM += (a * i + b) % 101; 31 | } 32 | } 33 | 34 | // ------ 35 | // Native 36 | // ------ 37 | 38 | __attribute__((noinline)) 39 | int foo(int x) 40 | { 41 | return (a * x + b) % 101; 42 | } 43 | 44 | __attribute__((noinline)) 45 | void testNative(int max) 46 | { 47 | for (int i = 0; i < max; i++) { 48 | SUM += foo(i); 49 | } 50 | } 51 | 52 | // ------------- 53 | // Native inline 54 | // ------------- 55 | 56 | int fooInline(int x) 57 | { 58 | return (a * x + b) % 101; 59 | } 60 | 61 | __attribute__((noinline)) 62 | void testInline(int max) 63 | { 64 | for (int i = 0; i < max; i++) { 65 | SUM += fooInline(i); 66 | } 67 | } 68 | 69 | // ------ 70 | // Lambda 71 | // ------ 72 | 73 | std::function lamFoo = [](int x) -> int { 74 | return (a * x + b) % 101; 75 | }; 76 | 77 | __attribute__((noinline)) 78 | void testLambda(int max) 79 | { 80 | for (int i = 0; i < max; i++) { 81 | SUM += lamFoo(i); 82 | } 83 | } 84 | 85 | // ---------------------- 86 | // dynamic_cast + virtual 87 | // ---------------------- 88 | 89 | class Base { 90 | public: 91 | virtual int foo(int x) { return x; } 92 | }; 93 | 94 | class Base2 { 95 | public: 96 | virtual int goo(int x) { return x; } 97 | }; 98 | 99 | Base2* dCastPtr = nullptr; 100 | 101 | class Derived : public Base, public Base2 { 102 | public: 103 | int foo(int x) override 104 | { 105 | return (a * x + b) % 101; 106 | } 107 | }; 108 | 109 | __attribute__((noinline)) 110 | void testDCast(int max) 111 | { 112 | for (int i = 0; i < max; i++) { 113 | SUM += dynamic_cast(dCastPtr)->foo(i); 114 | } 115 | } 116 | 117 | // -------- 118 | // Handlers 119 | // -------- 120 | 121 | struct Foo : eff::command { int x; }; 122 | 123 | class Han : public eff::handler { 124 | void handle_return() override { } 125 | void handle_command(Foo c, eff::resumption r) override 126 | { 127 | std::move(r).tail_resume((a * c.x + b) % 101); 128 | } 129 | }; 130 | 131 | __attribute__((noinline)) 132 | void testHandlers(int max) 133 | { 134 | eff::handle([=](){ 135 | for (int i = 0; i < max; i++) { 136 | SUM += eff::invoke_command(Foo{{}, i}); 137 | } 138 | }); 139 | } 140 | 141 | // -------------- 142 | // Plain handlers 143 | // -------------- 144 | 145 | class PHan : public eff::handler> { 146 | void handle_return() override { } 147 | int handle_command(Foo c) override 148 | { 149 | return (a * c.x + b) % 101; 150 | } 151 | }; 152 | 153 | __attribute__((noinline)) 154 | void testPlainHandlers(int max) 155 | { 156 | eff::handle([=](){ 157 | for (int i = 0; i < max; i++) { 158 | SUM += eff::invoke_command(Foo{{}, i}); 159 | } 160 | }); 161 | } 162 | 163 | 164 | // --------------- 165 | // Static handlers 166 | // --------------- 167 | 168 | __attribute__((noinline)) 169 | void testStaticHandlers(int max) 170 | { 171 | eff::handle([=](){ 172 | for (int i = 0; i < max; i++) { 173 | SUM += eff::static_invoke_command(Foo{{}, i}); 174 | } 175 | }); 176 | } 177 | 178 | // --------------------- 179 | // Static plain handlers 180 | // --------------------- 181 | 182 | __attribute__((noinline)) 183 | void testStaticPlainHandlers(int max) 184 | { 185 | eff::handle([=](){ 186 | for (int i = 0; i < max; i++) { 187 | SUM += eff::static_invoke_command(Foo{{}, i}); 188 | } 189 | }); 190 | } 191 | 192 | // -------------------- 193 | // Known plain handlers 194 | // -------------------- 195 | 196 | __attribute__((noinline)) 197 | void testKnownPlainHandlers(int max) 198 | { 199 | eff::handle(10, [=](){ 200 | for (int i = 0; i < max; i++) { 201 | auto it = eff::find_handler(10); 202 | SUM += eff::static_invoke_command(it, Foo{{}, i}); 203 | } 204 | }); 205 | } 206 | 207 | // ---- 208 | // Main 209 | // ---- 210 | 211 | int main(int argc, char**) 212 | { 213 | 214 | std::cout << "--- plain handler ---" << std::endl; 215 | 216 | std::cout << "loop: " << std::flush; 217 | 218 | auto beginloop = std::chrono::high_resolution_clock::now(); 219 | testLoop(MAX); 220 | auto endloop = std::chrono::high_resolution_clock::now(); 221 | std::cout << std::chrono::duration_cast(endloop-beginloop).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(endloop-beginloop).count() / MAX) << "ns per iteration)" <(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" <(endi-begini).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(endi-begini).count() / MAX) << "ns per iteration)" < lamX = [](int x){ return x; }; 241 | if (argc == 7) { lamFoo = lamX; } 242 | 243 | auto beginl = std::chrono::high_resolution_clock::now(); 244 | testLambda(MAX); 245 | auto endl = std::chrono::high_resolution_clock::now(); 246 | std::cout << std::chrono::duration_cast(endl-beginl).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(endl-beginl).count() / MAX) << "ns per iteration)" << std::endl; 247 | 248 | std::cout << "dynamic_cast: " << std::flush; 249 | 250 | if (argc % 123 == 7) { 251 | dCastPtr = new Base2(); 252 | } else { 253 | dCastPtr = new Derived(); 254 | } 255 | 256 | auto begindc = std::chrono::high_resolution_clock::now(); 257 | testDCast(MAX); 258 | auto enddc = std::chrono::high_resolution_clock::now(); 259 | std::cout << std::chrono::duration_cast(enddc-begindc).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(enddc-begindc).count() / MAX) << "ns per iteration)" << std::endl; 260 | 261 | std::cout << "handlers: " << std::flush; 262 | 263 | auto begin2 = std::chrono::high_resolution_clock::now(); 264 | testHandlers(MAX); 265 | auto end2 = std::chrono::high_resolution_clock::now(); 266 | std::cout << std::chrono::duration_cast(end2-begin2).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end2-begin2).count() / MAX) << "ns per iteration)" <(end3-begin3).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end3-begin3).count() / MAX) << "ns per iteration)" <(ends2-begins2).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(ends2-begins2).count() / MAX) << "ns per iteration)" << std::endl; 281 | 282 | std::cout << "s-plain-handlers: " << std::flush; 283 | 284 | auto begins3 = std::chrono::high_resolution_clock::now(); 285 | testStaticPlainHandlers(MAX); 286 | auto ends3 = std::chrono::high_resolution_clock::now(); 287 | std::cout << std::chrono::duration_cast(ends3-begins3).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(ends3-begins3).count() / MAX) << "ns per iteration)" << std::endl; 288 | 289 | std::cout << "known-handlers: " << std::flush; 290 | 291 | auto beginks3 = std::chrono::high_resolution_clock::now(); 292 | testKnownPlainHandlers(MAX); 293 | auto endks3 = std::chrono::high_resolution_clock::now(); 294 | std::cout << std::chrono::duration_cast(endks3-beginks3).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(endks3-beginks3).count() / MAX) << "ns per iteration)" << std::endl; 295 | } 296 | -------------------------------------------------------------------------------- /benchmark/generator.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Benchmark: comapre static and dynamic InvokeCmd 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cpp-effects/cpp-effects.h" 13 | #include "cpp-effects/clause-modifiers.h" 14 | 15 | namespace eff = cpp_effects; 16 | 17 | namespace DynamicGenerator { 18 | 19 | // -------------- 20 | // Internal stuff 21 | // -------------- 22 | 23 | template 24 | struct Yield : eff::command<> { 25 | T value; 26 | }; 27 | 28 | template 29 | void yield(int64_t label, T x) 30 | { 31 | eff::invoke_command(label, Yield{{}, x}); 32 | } 33 | 34 | template 35 | struct GenState; 36 | 37 | template 38 | using Result = std::optional>; 39 | 40 | template 41 | struct GenState { 42 | T value; 43 | eff::resumption()> resumption; 44 | }; 45 | 46 | template 47 | class GeneratorHandler : public eff::handler, void, Yield> { 48 | Result handle_command(Yield y, eff::resumption()> r) final override 49 | { 50 | return GenState{y.value, std::move(r)}; 51 | } 52 | Result handle_return() final override 53 | { 54 | return {}; 55 | } 56 | }; 57 | 58 | // ---------------------- 59 | // Programmer's interface 60 | // ---------------------- 61 | 62 | // When a generator is created, its body is executed until the first 63 | // Yield. It is only because in this example we want the programmer's 64 | // interface to be as simple as possible, so that the "there is a 65 | // value" and "you can resume me" states of a generator always hve the 66 | // same value, available via operator bool. 67 | 68 | template 69 | class Generator { 70 | public: 71 | Generator(std::function)> f) 72 | { 73 | auto label = eff::fresh_label(); 74 | result = eff::handle>(label, [f, label](){ 75 | f([label](T x) { yield(label, x); }); 76 | }); 77 | } 78 | Generator() { } // Create a dummy generator that generates nothing 79 | Generator(const Generator&) = delete; 80 | Generator(Generator&& other) 81 | { 82 | result = result.res; 83 | other.result = {}; 84 | } 85 | Generator& operator=(const Generator&) = delete; 86 | Generator& operator=(Generator&& other) 87 | { 88 | if (this != &other) { 89 | if (result) { delete result.value().resumption; } 90 | result = other.result; 91 | other.result = {}; 92 | } 93 | return *this; 94 | } 95 | T Value() const 96 | { 97 | if (!result) { throw std::out_of_range("Generator::Value"); } 98 | return result.value().value; 99 | } 100 | bool Next() 101 | { 102 | if (!result) { throw std::out_of_range("Generator::Value"); } 103 | result = std::move(result.value().resumption).resume(); 104 | return result.has_value(); 105 | } 106 | operator bool() const 107 | { 108 | return result.has_value(); 109 | } 110 | private: 111 | Result result = {}; 112 | }; 113 | 114 | } 115 | 116 | namespace StaticGenerator { 117 | 118 | // -------------- 119 | // Internal stuff 120 | // -------------- 121 | 122 | template 123 | class GeneratorHandler; 124 | 125 | template 126 | struct Yield : eff::command<> { 127 | T value; 128 | }; 129 | 130 | template 131 | void yield(int64_t label, T x) 132 | { 133 | eff::static_invoke_command>(label, Yield{{}, x}); // <--- StaticInvokeCmd 134 | } 135 | 136 | template 137 | struct GenState; 138 | 139 | template 140 | using Result = std::optional>; 141 | 142 | template 143 | struct GenState { 144 | T value; 145 | eff::resumption_data>* resumption; 146 | }; 147 | 148 | template 149 | class GeneratorHandler : public eff::handler, void, Yield> { 150 | Result handle_command(Yield y, eff::resumption()> r) final override 151 | { 152 | return GenState{y.value, r.release()}; 153 | } 154 | Result handle_return() final override 155 | { 156 | return {}; 157 | } 158 | }; 159 | 160 | // ---------------------- 161 | // Programmer's interface 162 | // ---------------------- 163 | 164 | // When a generator is created, its body is executed until the first 165 | // Yield. It is only because in this example we want the programmer's 166 | // interface to be as simple as possible, so that the "there is a 167 | // value" and "you can resume me" states of a generator always hve the 168 | // same value, available via operator bool. 169 | 170 | template 171 | class Generator { 172 | public: 173 | Generator(std::function)> f) 174 | { 175 | auto label = eff::fresh_label(); 176 | result = eff::handle>(label, [f, label](){ 177 | f([label](T x) { yield(label, x); }); 178 | }); 179 | } 180 | Generator() { } // Create a dummy generator that generates nothing 181 | Generator(const Generator&) = delete; 182 | Generator(Generator&& other) 183 | { 184 | result = result.res; 185 | other.result = {}; 186 | } 187 | Generator& operator=(const Generator&) = delete; 188 | Generator& operator=(Generator&& other) 189 | { 190 | if (this != &other) { 191 | if (result) { delete result.value().resumption; } 192 | result = other.result; 193 | other.result = {}; 194 | } 195 | return *this; 196 | } 197 | T Value() const 198 | { 199 | if (!result) { throw std::out_of_range("Generator::Value"); } 200 | return result.value().value; 201 | } 202 | bool Next() 203 | { 204 | if (!result) { throw std::out_of_range("Generator::Value"); } 205 | result = eff::resumption()>(result.value().resumption).resume(); 206 | return result.has_value(); 207 | } 208 | operator bool() const 209 | { 210 | return result.has_value(); 211 | } 212 | private: 213 | Result result = {}; 214 | }; 215 | 216 | } 217 | 218 | namespace OptDynamicGenerator { 219 | 220 | // -------------- 221 | // Internal stuff 222 | // -------------- 223 | 224 | template 225 | class GeneratorHandler; 226 | 227 | template 228 | struct Yield : eff::command<> { 229 | T value; 230 | }; 231 | 232 | template 233 | void yield(int64_t label, T x) 234 | { 235 | eff::invoke_command(label, Yield{{}, x}); 236 | } 237 | 238 | template 239 | struct GenState; 240 | 241 | template 242 | using Result = std::optional>; 243 | 244 | template 245 | class Generator; 246 | 247 | template 248 | struct GenState { 249 | T value; 250 | eff::resumption_data* resumption; 251 | }; 252 | 253 | template 254 | class GeneratorHandler : public eff::handler> { 255 | void handle_command(Yield y, eff::resumption r) final override 256 | { 257 | gen->result = GenState{y.value, r.release()}; 258 | } 259 | void handle_return() final override 260 | { 261 | } 262 | public: 263 | Generator* gen; 264 | GeneratorHandler(Generator* gen) : gen(gen) { } 265 | }; 266 | 267 | // ---------------------- 268 | // Programmer's interface 269 | // ---------------------- 270 | 271 | // When a generator is created, its body is executed until the first 272 | // Yield. It is only because in this example we want the programmer's 273 | // interface to be as simple as possible, so that the "there is a 274 | // value" and "you can resume me" states of a generator always hve the 275 | // same value, available via operator bool. 276 | 277 | template 278 | class Generator { 279 | public: 280 | Generator(std::function)> f) 281 | { 282 | auto label = eff::fresh_label(); 283 | eff::handle>(label, [f, label](){ 284 | f([label](T x) { yield(label, x); }); 285 | }, this); 286 | } 287 | Generator() { } // Create a dummy generator that generates nothing 288 | Generator(const Generator&) = delete; 289 | Generator(Generator&& other) 290 | { 291 | result = result.res; 292 | other.result = {}; 293 | } 294 | Generator& operator=(const Generator&) = delete; 295 | Generator& operator=(Generator&& other) 296 | { 297 | if (this != &other) { 298 | if (result) { delete result.value().resumption; } 299 | result = other.result; 300 | other.result = {}; 301 | } 302 | return *this; 303 | } 304 | T Value() const 305 | { 306 | if (!result) { throw std::out_of_range("Generator::Value"); } 307 | return result.value().value; 308 | } 309 | bool Next() 310 | { 311 | if (!result) { throw std::out_of_range("Generator::Value"); } 312 | eff::resumption(result.value().resumption).resume(); 313 | return result.has_value(); 314 | } 315 | operator bool() const 316 | { 317 | return result.has_value(); 318 | } 319 | Result result = {}; 320 | }; 321 | 322 | } 323 | 324 | namespace OptStaticGenerator { 325 | 326 | 327 | // -------------- 328 | // Internal stuff 329 | // -------------- 330 | 331 | template 332 | class GeneratorHandler; 333 | 334 | template 335 | struct Yield : eff::command<> { 336 | T value; 337 | }; 338 | 339 | template 340 | void yield(int64_t label, T x) 341 | { 342 | eff::static_invoke_command>(label, Yield{{}, x}); 343 | } 344 | 345 | template 346 | struct GenState; 347 | 348 | template 349 | using Result = std::optional>; 350 | 351 | template 352 | class Generator; 353 | 354 | template 355 | struct GenState { 356 | T value; 357 | eff::resumption resumption; 358 | }; 359 | 360 | template 361 | class GeneratorHandler : public eff::handler> { 362 | void handle_command(Yield y, eff::resumption r) final override 363 | { 364 | gen->result = GenState{y.value, std::move(r)}; 365 | } 366 | void handle_return() final override 367 | { 368 | } 369 | public: 370 | Generator* gen; 371 | GeneratorHandler(Generator* gen) : gen(gen) { } 372 | }; 373 | 374 | // ---------------------- 375 | // Programmer's interface 376 | // ---------------------- 377 | 378 | // When a generator is created, its body is executed until the first 379 | // Yield. It is only because in this example we want the programmer's 380 | // interface to be as simple as possible, so that the "there is a 381 | // value" and "you can resume me" states of a generator always hve the 382 | // same value, available via operator bool. 383 | 384 | template 385 | class Generator { 386 | public: 387 | Generator(std::function)> f) 388 | { 389 | auto label = eff::fresh_label(); 390 | eff::handle>(label, [f, label](){ 391 | f([label](T x) { yield(label, x); }); 392 | }, this); 393 | } 394 | Generator() { } // Create a dummy generator that generates nothing 395 | Generator(const Generator&) = delete; 396 | Generator(Generator&& other) 397 | { 398 | if (this != &other) { 399 | result = result.res; 400 | other.result = {}; 401 | } 402 | } 403 | Generator& operator=(const Generator&) = delete; 404 | Generator& operator=(Generator&& other) 405 | { 406 | if (this != &other) { 407 | //if (result) { delete result.value().resumption; } 408 | result = other.result; 409 | other.result = {}; 410 | } 411 | return *this; 412 | } 413 | T Value() const 414 | { 415 | //if (!result) { throw std::out_of_range("Generator::Value"); } 416 | return (*result).value; 417 | } 418 | bool Next() 419 | { 420 | //if (!result) { throw std::out_of_range("Generator::Value"); } 421 | std::move((*result).resumption).resume(); 422 | return result.has_value(); 423 | } 424 | operator bool() const 425 | { 426 | return result.has_value(); 427 | } 428 | Result result = {}; 429 | }; 430 | 431 | } 432 | 433 | namespace OptOptStaticGenerator { 434 | 435 | // -------------- 436 | // Internal stuff 437 | // -------------- 438 | 439 | template 440 | class GeneratorHandler; 441 | 442 | template 443 | struct CmdYield : eff::command<> { 444 | const T value; 445 | }; 446 | 447 | template 448 | struct Yield { 449 | const int64_t label; 450 | void operator()(T x) const 451 | { 452 | eff::static_invoke_command>(label, CmdYield{{}, x}); 453 | } 454 | }; 455 | 456 | template 457 | struct GenState; 458 | 459 | template 460 | using Result = std::optional>; 461 | 462 | template 463 | class Generator; 464 | 465 | template 466 | struct GenState { 467 | T value; 468 | eff::resumption resumption; 469 | }; 470 | 471 | template 472 | class GeneratorHandler : public eff::handler> { 473 | void handle_command(CmdYield y, eff::resumption r) final override 474 | { 475 | gen->result = GenState{y.value, std::move(r)}; 476 | } 477 | void handle_return() final override 478 | { 479 | } 480 | public: 481 | Generator* gen; 482 | GeneratorHandler(Generator* gen) : gen(gen) { } 483 | }; 484 | 485 | // ---------------------- 486 | // Programmer's interface 487 | // ---------------------- 488 | 489 | // When a generator is created, its body is executed until the first 490 | // Yield. It is only because in this example we want the programmer's 491 | // interface to be as simple as possible, so that the "there is a 492 | // value" and "you can resume me" states of a generator always hve the 493 | // same value, available via operator bool. 494 | 495 | template 496 | class Generator { 497 | public: 498 | Generator(std::function)> f) 499 | { 500 | auto label = eff::fresh_label(); 501 | eff::handle>(label, [f, label](){ f(Yield{label}); }, this); 502 | } 503 | Generator() { } // Create a dummy generator that generates nothing 504 | Generator(const Generator&) = delete; 505 | Generator(Generator&& other) 506 | { 507 | if (this != &other) { 508 | result = result.res; 509 | other.result = {}; 510 | } 511 | } 512 | Generator& operator=(const Generator&) = delete; 513 | Generator& operator=(Generator&& other) 514 | { 515 | if (this != &other) { 516 | //if (result) { delete result.value().resumption; } 517 | result = other.result; 518 | other.result = {}; 519 | } 520 | return *this; 521 | } 522 | T Value() const 523 | { 524 | //if (!result) { throw std::out_of_range("Generator::Value"); } 525 | return (*result).value; 526 | } 527 | bool Next() 528 | { 529 | //if (!result) { throw std::out_of_range("Generator::Value"); } 530 | std::move((*result).resumption).resume(); 531 | return result.has_value(); 532 | } 533 | operator bool() const 534 | { 535 | return result.has_value(); 536 | } 537 | Result result = {}; 538 | }; 539 | 540 | } 541 | 542 | namespace KnownStaticGenerator { 543 | 544 | // -------------- 545 | // Internal stuff 546 | // -------------- 547 | 548 | template 549 | class GeneratorHandler; 550 | 551 | template 552 | struct CmdYield : eff::command<> { 553 | const T value; 554 | }; 555 | 556 | template 557 | struct Yield { 558 | eff::handler_ref it; 559 | void operator()(const T& x) const 560 | { 561 | eff::static_invoke_command>(it, CmdYield{{}, x}); 562 | } 563 | }; 564 | 565 | template 566 | struct GenState; 567 | 568 | template 569 | using Result = std::optional>; 570 | 571 | template 572 | class Generator; 573 | 574 | template 575 | struct GenState { 576 | T value; 577 | eff::resumption resumption; 578 | }; 579 | 580 | template 581 | class GeneratorHandler : public eff::handler> { 582 | void handle_command(CmdYield y, eff::resumption r) final override 583 | { 584 | gen->result = GenState{y.value, std::move(r)}; 585 | } 586 | void handle_return() final override 587 | { 588 | } 589 | public: 590 | Generator* gen; 591 | GeneratorHandler(Generator* gen) : gen(gen) { } 592 | }; 593 | 594 | // ---------------------- 595 | // Programmer's interface 596 | // ---------------------- 597 | 598 | // When a generator is created, its body is executed until the first 599 | // Yield. It is only because in this example we want the programmer's 600 | // interface to be as simple as possible, so that the "there is a 601 | // value" and "you can resume me" states of a generator always hve the 602 | // same value, available via operator bool. 603 | 604 | template 605 | class Generator { 606 | public: 607 | Generator(std::function)> f) 608 | { 609 | auto label = eff::fresh_label(); 610 | eff::handle>(label, [&](){ 611 | auto it = eff::find_handler(label); 612 | f(Yield{it}); 613 | }, this); 614 | } 615 | Generator() { } // Create a dummy generator that generates nothing 616 | Generator(const Generator&) = delete; 617 | Generator(Generator&& other) 618 | { 619 | if (this != &other) { 620 | result = result.res; 621 | other.result = {}; 622 | } 623 | } 624 | Generator& operator=(const Generator&) = delete; 625 | Generator& operator=(Generator&& other) 626 | { 627 | if (this != &other) { 628 | //if (result) { delete result.value().resumption; } 629 | result = other.result; 630 | other.result = {}; 631 | } 632 | return *this; 633 | } 634 | T Value() const 635 | { 636 | //if (!result) { throw std::out_of_range("Generator::Value"); } 637 | return (*result).value; 638 | } 639 | bool Next() 640 | { 641 | //if (!result) { throw std::out_of_range("Generator::Value"); } 642 | std::move((*result).resumption).resume(); 643 | return result.has_value(); 644 | } 645 | operator bool() const 646 | { 647 | return result.has_value(); 648 | } 649 | Result result = {}; 650 | }; 651 | 652 | } 653 | 654 | namespace KnownStaticGeneratorNoManage { 655 | 656 | // -------------- 657 | // Internal stuff 658 | // -------------- 659 | 660 | template 661 | class GeneratorHandler; 662 | 663 | template 664 | struct CmdYield : eff::command<> { 665 | const T value; 666 | }; 667 | 668 | template 669 | struct Yield { 670 | eff::handler_ref it; 671 | void operator()(const T& x) const 672 | { 673 | eff::static_invoke_command>(it, CmdYield{{}, x}); 674 | } 675 | }; 676 | 677 | template 678 | struct GenState; 679 | 680 | template 681 | using Result = std::optional>; 682 | 683 | template 684 | class Generator; 685 | 686 | template 687 | struct GenState { 688 | T value; 689 | eff::resumption resumption; 690 | }; 691 | 692 | template 693 | class GeneratorHandler : public eff::handler>> { 694 | void handle_command(CmdYield y, eff::resumption r) final override 695 | { 696 | gen->result = GenState{y.value, std::move(r)}; 697 | } 698 | void handle_return() final override 699 | { 700 | } 701 | public: 702 | Generator* gen; 703 | GeneratorHandler(Generator* gen) : gen(gen) { } 704 | }; 705 | 706 | // ---------------------- 707 | // Programmer's interface 708 | // ---------------------- 709 | 710 | // When a generator is created, its body is executed until the first 711 | // Yield. It is only because in this example we want the programmer's 712 | // interface to be as simple as possible, so that the "there is a 713 | // value" and "you can resume me" states of a generator always hve the 714 | // same value, available via operator bool. 715 | 716 | template 717 | class Generator { 718 | public: 719 | Generator(std::function)> f) 720 | { 721 | auto label = eff::fresh_label(); 722 | eff::handle>(label, [&](){ 723 | auto it = eff::find_handler(label); 724 | f(Yield{it}); 725 | }, this); 726 | } 727 | Generator() { } // Create a dummy generator that generates nothing 728 | Generator(const Generator&) = delete; 729 | Generator(Generator&& other) 730 | { 731 | if (this != &other) { 732 | result = result.res; 733 | other.result = {}; 734 | } 735 | } 736 | Generator& operator=(const Generator&) = delete; 737 | Generator& operator=(Generator&& other) 738 | { 739 | if (this != &other) { 740 | //if (result) { delete result.value().resumption; } 741 | result = other.result; 742 | other.result = {}; 743 | } 744 | return *this; 745 | } 746 | T Value() const 747 | { 748 | //if (!result) { throw std::out_of_range("Generator::Value"); } 749 | return (*result).value; 750 | } 751 | bool Next() 752 | { 753 | //if (!result) { throw std::out_of_range("Generator::Value"); } 754 | std::move((*result).resumption).resume(); 755 | return result.has_value(); 756 | } 757 | operator bool() const 758 | { 759 | return result.has_value(); 760 | } 761 | Result result = {}; 762 | }; 763 | 764 | } 765 | 766 | 767 | // ------------------ 768 | // Particular example 769 | // ------------------ 770 | 771 | int main() 772 | { 773 | 774 | std::cout << "--- generators: static vs dynamic invoke ---" << std::endl; 775 | 776 | volatile int64_t SUM = 0; 777 | const int MAX = 10000000; 778 | 779 | { 780 | 781 | std::cout << "loop: " << std::flush; 782 | 783 | auto begin = std::chrono::high_resolution_clock::now(); 784 | 785 | for (int i = 0; i <= MAX; i++) { 786 | SUM = SUM + i; 787 | } 788 | 789 | auto end = std::chrono::high_resolution_clock::now(); 790 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < dnaturals([](auto yield) { 801 | int i = 0; 802 | while (true) { yield(i++); } 803 | }); 804 | 805 | auto begin = std::chrono::high_resolution_clock::now(); 806 | 807 | for (int i = 0; i <= MAX; i++) { 808 | SUM = SUM + dnaturals.Value(); 809 | dnaturals.Next(); 810 | } 811 | 812 | auto end = std::chrono::high_resolution_clock::now(); 813 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < snaturals([](auto yield) { 824 | int i = 0; 825 | while (true) { yield(i++); } 826 | }); 827 | 828 | auto begin = std::chrono::high_resolution_clock::now(); 829 | 830 | for (int i = 0; i <= MAX; i++) { 831 | SUM = SUM + snaturals.Value(); 832 | snaturals.Next(); 833 | } 834 | 835 | auto end = std::chrono::high_resolution_clock::now(); 836 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < snaturals([](auto yield) { 847 | int i = 0; 848 | while (true) { yield(i++); } 849 | }); 850 | 851 | auto begin = std::chrono::high_resolution_clock::now(); 852 | 853 | for (int i = 0; i <= MAX; i++) { 854 | SUM = SUM + snaturals.Value(); 855 | snaturals.Next(); 856 | } 857 | 858 | auto end = std::chrono::high_resolution_clock::now(); 859 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < snaturals([](auto yield) { 870 | int i = 0; 871 | while (true) { yield(i++); } 872 | }); 873 | 874 | auto begin = std::chrono::high_resolution_clock::now(); 875 | 876 | for (int i = 0; i <= MAX; i++) { 877 | SUM = SUM + snaturals.Value(); 878 | snaturals.Next(); 879 | } 880 | 881 | auto end = std::chrono::high_resolution_clock::now(); 882 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < snaturals([](auto yield) { 893 | int i = 0; 894 | while (true) { yield(i++); } 895 | }); 896 | 897 | auto begin = std::chrono::high_resolution_clock::now(); 898 | 899 | for (int i = 0; i <= MAX; i++) { 900 | SUM = SUM + snaturals.Value(); 901 | snaturals.Next(); 902 | } 903 | 904 | auto end = std::chrono::high_resolution_clock::now(); 905 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < snaturals([](auto yield) { 916 | int i = 0; 917 | while (true) { yield(i++); } 918 | }); 919 | 920 | auto begin = std::chrono::high_resolution_clock::now(); 921 | 922 | for (int i = 0; i <= MAX; i++) { 923 | SUM = SUM + snaturals.Value(); 924 | snaturals.Next(); 925 | } 926 | 927 | auto end = std::chrono::high_resolution_clock::now(); 928 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < snaturals([](auto yield) { 939 | int i = 0; 940 | while (true) { yield(i++); } 941 | }); 942 | 943 | auto begin = std::chrono::high_resolution_clock::now(); 944 | 945 | for (int i = 0; i <= MAX; i++) { 946 | SUM = SUM + snaturals.Value(); 947 | snaturals.Next(); 948 | } 949 | 950 | auto end = std::chrono::high_resolution_clock::now(); 951 | std::cout << std::chrono::duration_cast(end-begin).count() << "ns" << " \t(" << (int)(std::chrono::duration_cast(end-begin).count() / MAX) << "ns per iteration)" < 2 | 20 | 22 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 59 | 63 | 68 | 73 | 78 | 83 | 88 | 93 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /doc/quick-intro.md: -------------------------------------------------------------------------------- 1 | # Quick introduction (and comparison with effectful functional programming) 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | This document gives a quick overview of the API of the library. It assumes that the reader has some basic understanding of algebraic effects and effect handlers. 6 | 7 | Effect handlers first appeared in the area of functional programming (e.g., Eff, Koka, OCaml). There are, however, some differences between how handlers are usually formulated in FP and what our library provides, trying to combine effects with object-oriented programming - and C++ programming in particular. 8 | 9 | ## Namespace 10 | 11 | The library lives in the namespace `cpp_effects`. We often abbreviate it to `eff`, that is: 12 | 13 | ```cpp 14 | namespace eff = cpp_effects; 15 | ``` 16 | 17 | ## Commands 18 | 19 | In our library, *commands* (sometimes called *operations* in the handler literature) are defined as classes derived from the `command` template. The (optional) type argument of the template is the return type of the command. For example, we can formulate the usual interface for mutable state of type `int` as follows: 20 | 21 | ```cpp 22 | struct Put : eff::command<> { int newState; }; 23 | struct Get : eff::command { }; 24 | ``` 25 | 26 | Data members of these classes are arguments of the commands. 27 | 28 | :point_right: The `command` class is actually a wrapper for two types: 29 | 30 | ```cpp 31 | template 32 | struct command; 33 | 34 | template 35 | struct command { 36 | using out_type = Out; 37 | template using resumption_type = Answer(Out); 38 | }; 39 | 40 | template <> 41 | struct command<> { 42 | using out_type = void; 43 | template using resumption_type = Answer(); 44 | }; 45 | ``` 46 | 47 | To *perform* (or: *invoke*) a command, we need to use an object of such a derived class. The out type of the command becomes the result of the invocation: 48 | 49 | ```cpp 50 | template 51 | typename Cmd::out_type invoke_command(const Cmd& cmd); 52 | ``` 53 | 54 | For example: 55 | 56 | ```cpp 57 | void put(int s) { 58 | eff::invoke_command(Put{{}, s}); 59 | } 60 | 61 | int get() { 62 | return eff::invoke_command(Get{}); 63 | } 64 | ``` 65 | 66 | :point_right: Note the `{}` in `Put{{}, s}`, which is necessary in C++ to initialise (even if the initialisation is rather trivial) the `command` superclass. 67 | 68 | :dromedary_camel: The equivalent code in OCaml would be as follows (compare [ocaml-multicore/effects-examples/state.ml](https://github.com/ocaml-multicore/effects-examples/blob/master/state.ml)): 69 | 70 | ```ocaml 71 | (* definition of operations *) 72 | effect Put : int -> unit 73 | effect Get : int 74 | 75 | (* functional interface *) 76 | let put v = perform (Put v) 77 | let get () = perform Get 78 | ``` 79 | 80 | ## Handlers 81 | 82 | In general, a *handler* is a piece of code that knows what to do with a set of commands. One can subsequently *handle* a computation by delimiting it using the handler. If in this computation an operation is invoked, the handler assumes control, receiving the command together with the computation captured as a *resumption* (aka *continuation*). 83 | 84 | In our library, we take advantage of the basic principle of object-oriented programming: packing together data and code. And so, a handler is a class that contains member functions that know how to handle particular commands, but it can also encapsulate as much data and auxiliary definitions as the programmer sees fit. Then, we can handle a computation using a particular *object* of that class. This object's life is managed by the library, providing a persistent "context of execution" of the handled computation. For example, we can define a handler for mutable state using a data member in the handler: 85 | 86 | ```cpp 87 | template // the type of the handled computation 88 | class State : public eff::handler { 89 | public: 90 | State(int initialState) : state(initialState) { } 91 | private: 92 | int state; 93 | T handle_command(Get, eff::resumption r) override 94 | { 95 | return std::move(r).tail_resume(state); 96 | } 97 | T handle_command(Put p, eff::resumption r) override 98 | { 99 | state = p.newState; 100 | return std::move(r).tail_resume(); 101 | } 102 | T handle_return(T v) override 103 | { 104 | return v; 105 | } 106 | }; 107 | ``` 108 | 109 | FP usually makes effect handlers syntactically quite similar to exception handlers. We take a different approach here: a handler can be defined by deriving from the `handler` class template. The user defines "command" and "return" clauses by overriding appropriate virtual functions in `handler`, one for each command listed in the template parameter list. 110 | 111 | We can handle a computation as follows: 112 | 113 | ```cpp 114 | int body() 115 | { 116 | put(get() * 2); 117 | return get(); 118 | } 119 | 120 | void foo() { 121 | // ... 122 | eff::handle>(body, 10); // returns 20 123 | } 124 | ``` 125 | 126 | The `handle` function will take care of creating the handler object for us, forwarding its subsequent arguments (`10`) to the constructor of `State`. 127 | 128 | ## Resumptions 129 | 130 | A resumptions represents a suspended computation that "hangs" on a command. In FP, they can be functions, in which the type of the argument is the return type of the command. They can also be a separate type (as in, again, Eff or OCaml). In this library, they are a separate type: 131 | 132 | ```cpp 133 | template 134 | class resumption; 135 | 136 | template 137 | class resumption { ... }; 138 | 139 | template 140 | class resumption { ... }; 141 | ``` 142 | 143 | We can *resume* (or *continue* as it is called in OCaml) a resumption using the `resume` member function: 144 | 145 | ```cpp 146 | template 147 | static Answer resumption::resume(Out cmdResult) &&; 148 | 149 | // or 150 | template 151 | static Answer resumption::resume() &&; 152 | ``` 153 | 154 | Resumptions in our library are one-shot, which means that you can resume each one at most once. This is not only a technical matter, but more importantly it is in accordance with RAII: objects are destructed during unwinding of the stack, and so in principle you don't want to unwind the same stack twice. This is somewhat enforced by the fact that `resumption` is movable, but not copyable. The handler is given the "ownership" of the resumption (the `resumption` class is actually a form of a smart pointer), but if we want to resume, we need to give up the ownership of the resumption. After this, the resumption becomes invalid, and so the user should not use it again. 155 | 156 | ## Other features of the library 157 | 158 | ### Invoking and handling with a label and reference 159 | 160 | One can give a **label** to a handler and when invoking a command, which allows to pair a command with a handler more directly (otherwise, the pairing is done via runtime type information). This is a form of dynamic "instances" of effects. For example: 161 | 162 | ```cpp 163 | void foo() 164 | { 165 | int64_t lbl = eff::fresh_label(); 166 | eff::handle(lbl, []() { 167 | eff::invoke_command(lbl, SomeCmd{}); 168 | }); 169 | } 170 | ``` 171 | 172 | If one knows the exact type of the handler when invoking a command, they can supply it, which will make the invoke more efficient: 173 | 174 | ```cpp 175 | eff::static_invoke_command(lbl, SomeCmd{}); 176 | ``` 177 | 178 | A more efficient, albeit more dangerous, is to keep a **reference** to the handler, which will be valid as long as the handler is on the stack (even if it was moved around on the stack, or moved to a resumption and back on the stack). 179 | 180 | ```cpp 181 | void foo() 182 | { 183 | eff::handle_ref([](auto href) { 184 | eff::invoke_command(href, SomeCmd{}); // <---+ 185 | eff::handle([]() { // | 186 | eff::invoke_command(href, SomeCmd{}); // <---+ 187 | }); // | 188 | }); // These two refer to 189 | } // the same handler 190 | ``` 191 | 192 | However, if the handler is not on the stack, the program might behave unpredictably with no warning. 193 | 194 | ### Other forms of handlers and clauses 195 | 196 | Our library offers additional forms of handlers and clauses, which lead to better readability and better performance. For example, `flat_handler` implements handlers in which the return clause is identity, which is useful for polymorphic handlers in which the answer type can be `void`. Moreover, the respective clauses for `Put` and `Get` in the example above are self- and tail-resumptive, which means that they resume the same continuation that they obtain as an argument ("self-") and that they return the result of resuming ("tail-"). Such clauses can be simplified using the `plain` modifier as follows: 197 | 198 | ```cpp 199 | #include "cpp-effects/clause-modifiers.h" 200 | 201 | // ... 202 | 203 | template 204 | class HStateful : public eff::flat_handler, eff::plain> { 205 | public: 206 | HStateful(int initialState) : state(initialState) { } 207 | private: 208 | int state; 209 | void handle_command(Put p) final override // no explicit resumption 210 | { 211 | state = p.newState; 212 | } 213 | int handle_command(Get) final override // ...same here 214 | { 215 | return state; 216 | } 217 | }; 218 | ``` 219 | -------------------------------------------------------------------------------- /doc/refman-clause-modifiers.md: -------------------------------------------------------------------------------- 1 | # cpp-effects/clause-modifiers.h 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | This file contains modifiers that force specific shapes of command clauses in handlers, which is useful for readability (e.g., we can read from the type that we will not use the resumption) and performance (e.g., we don't allocate resumptions that are never used). 6 | 7 | Each modifier consists of a template, which marks a command in the type of the handler, and a specialisation of the [`CmdClause`](refman-cpp-effects.md#class-cmdclause) class that modifies the type of the clause in the definition of the handler. For example, we can use the `NoResume` modifier to specify that a particular handler will not need the resumption: 8 | 9 | ```cpp 10 | struct MyCmd : Command { }; 11 | struct OtherCmd : Command<> { }; 12 | 13 | class MyHandler : public Handler , OtherCmd> { 14 | ... 15 | char CommandClause(OtherCmd, Resumption) { ... } // regular clause 16 | char CommandClause(MyCmd) { ... } // no resumption 17 | }; 18 | ``` 19 | 20 | Because we used the `NoResume` modifier, the type of `CommandClause` for `MyCmd` is now different. 21 | 22 | 23 | ## `NoResume` modifier 24 | 25 | Specialisation for command clauses that do not use the resumption. This is useful for handlers that behave like exception handlers or terminate the "current thread". 26 | 27 | ```cpp 28 | template 29 | struct NoResume { }; 30 | 31 | template 32 | class CmdClause> { 33 | protected: 34 | virtual Answer CommandClause(Cmd) = 0; 35 | }; 36 | ``` 37 | 38 | The command clause does not receive the current resumption, but it is allowed to resume any other resumption inside its body. 39 | 40 |
41 | Example 42 | 43 | ```cpp 44 | struct Error : Command<> { }; 45 | 46 | class Cancel : public Handler> { 47 | void CommandClause(Error) override // no "resumption" argument 48 | { 49 | std::cout << "Error!" << std::endl; 50 | } 51 | void ReturnClause() override {} 52 | }; 53 | 54 | int main() 55 | { 56 | std::cout << "Welcome!" << std::endl; 57 | OneShot::Handle([](){ 58 | std::cout << "So far so good..." << std::endl; 59 | OneShot::InvokeCmd(Error{}); 60 | std::cout << "I made it!" << std::endl; 61 | }); 62 | std::cout << "Bye!" << std::endl; 63 | } 64 | ``` 65 | 66 | Output: 67 | 68 | ``` 69 | Welcome! 70 | So far so good... 71 | Error! 72 | Bye! 73 | ``` 74 | 75 |
76 | 77 | 78 | ## `Plain` modifier 79 | 80 | Specialisation for plain clauses, which interpret commands as functions (i.e., they are self- and tail-resumptive). No context switching, no explicit resumption. 81 | 82 | ```cpp 83 | template 84 | struct Plain { }; 85 | 86 | template 87 | class CmdClause> { 88 | protected: 89 | virtual typename Cmd::OutType CommandClause(Cmd) = 0; 90 | }; 91 | ``` 92 | 93 | Note that the return type of the command clause is now the return type of the command. 94 | 95 |
96 | Example 97 | 98 | ```cpp 99 | struct Add : Command { 100 | int x, y; 101 | }; 102 | 103 | class Calculator : public Handler > { 104 | int CommandClause(Add c) override // - no "resumption" argument 105 | // - return type that of the command, not handler 106 | { 107 | return c.x + c.y; 108 | } 109 | void ReturnClause() override { } 110 | }; 111 | 112 | int main() 113 | { 114 | OneShot::Handle([](){ 115 | std::cout << "2 + 5 = " << OneShot::InvokeCmd({{}, 2, 5}) << std::endl; 116 | }); 117 | } 118 | ``` 119 | 120 | Output: 121 | 122 | ``` 123 | 2 + 5 = 7 124 | ``` 125 | 126 |
127 | 128 | 129 | ## `NoManage` modifier 130 | 131 | -------------------------------------------------------------------------------- /doc/refman-command.md: -------------------------------------------------------------------------------- 1 | # class `command` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | The base class for commands. 6 | 7 | ```cpp 8 | template 9 | struct command; 10 | 11 | template 12 | struct command { 13 | using out_type = Out; 14 | template using resumption_type = Answer(Out); 15 | }; 16 | 17 | template <> 18 | struct command<> { 19 | using out_type = void; 20 | template using resumption_type = Answer(); 21 | }; 22 | ``` 23 | 24 | A class derived from `command` can be used as a command if it is at least copy-constructible. 25 | 26 | **Type parameters:** 27 | 28 | - `typename... Outs` - The return type of invoking the (derived) command (i.e., the return type of `invoke_command`). Should be at least move-constructible and move-assignable. Leave empty if the command should not return any value (in such case, the return type of `invoke_command` is `void`). 29 | 30 | ### :large_orange_diamond: out_type 31 | 32 | ```cpp 33 | using out_type = Out; // in command 34 | using out_type = void; // in command<> 35 | ``` 36 | 37 | The type returned by `invoke_command` called with the (drived) command. 38 | 39 | ### :large_orange_diamond: resumption_type 40 | 41 | ```cpp 42 | template using resumption_type = Answer(Out); // in command 43 | template using resumption_type = Answer(); // in command<> 44 | ``` 45 | 46 | The type parameter of the [resumption](refman-resumption.md) that captures a computation suspended by invoking the (derived) command. The type of the resumption depends also on the overall answer type of the handler, hence `resumption_type` is a template. 47 | 48 | Compare the type of [`handle_command`](refman-handler.md) for a command `Cmd`: 49 | 50 | ```cpp 51 | Answer handle_command(Cmd, resumption>) 52 | ``` 53 | 54 | For example, for `Cmd : command`, the type of the corresponding `handle_command` in a class `MyHandler : handler` is 55 | 56 | ```cpp 57 | bool MyHandler::handle_command(Cmd, resumption) 58 | ``` 59 | -------------------------------------------------------------------------------- /doc/refman-debug_print_metastack.md: -------------------------------------------------------------------------------- 1 | # function `debug_print_metastack` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | void debug_print_metastack(); 7 | ``` 8 | 9 | Prints out the current stack of handlers. Useful for "printf" debugging. The format of each frame is: 10 | 11 | ``` 12 | label:type_name[cmd]...[cmd] 13 | ``` 14 | 15 | where 16 | 17 | - `label` is the label of the handler (automatically generated or given by the user when calling `handle` or `wrap`) 18 | 19 | - `type_name` is the `typeid()::name` of the handler class, 20 | 21 | - `[cmd]` is the list of `typeid()::name`s of the commads handled by the handler. 22 | 23 | The stack grows bottom-to-top, while the bottom frame is always a dummy "global" handler with label `0`. 24 | 25 | ### Example 26 | 27 | ```cpp 28 | struct Error : command<> { }; 29 | 30 | class ErrorHandler : public flat_handler> { 31 | void handle_command(Error) override { } 32 | }; 33 | 34 | template 35 | struct Put : command<> { 36 | S newState; 37 | }; 38 | 39 | template 40 | struct Get : command { }; 41 | 42 | template 43 | class Stateful : public flat_handler>, plain>> { 44 | public: 45 | Stateful(S initialState) : state(initialState) { } 46 | private: 47 | S state; 48 | void handle_command(Put p) final override 49 | { 50 | state = p.newState; 51 | } 52 | S handle_command(Get) final override 53 | { 54 | return state; 55 | } 56 | }; 57 | 58 | int main() 59 | { 60 | handle(100, []() { 61 | handle(200, []() { 62 | handle>([]() { 63 | debug_print_metastack(); 64 | }, 0); 65 | }); 66 | }); 67 | } 68 | ``` 69 | 70 | Example output: 71 | 72 | ``` 73 | -2:8StatefulIviE[N10cpp_effects5plainI3PutIiEEE][N10cpp_effects5plainI3GetIiEEE] 74 | 200:12ErrorHandler[5Error] 75 | 100:12ErrorHandler[5Error] 76 | 0:N10cpp_effects9metaframeE 77 | ``` 78 | -------------------------------------------------------------------------------- /doc/refman-flat_handler.md: -------------------------------------------------------------------------------- 1 | # class `flat_handler` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | Base class for [handlers](refman-handler.md) in which the return clause is identity. It is useful for handlers that are generic in the answer type, and spares the programmer writing a separate specialisation for when the answer type is `void`. 6 | 7 | ```cpp 8 | template 9 | class flat_handler : public handler { 10 | Answer handle_return(Answer a) final override { return a; } 11 | }; 12 | 13 | template 14 | class flat_handler : public handler { 15 | void handle_return() final override { } 16 | }; 17 | ``` 18 | 19 | - `typename Answer` - The overall answer type of a derived handler and the type of the handled computation. Should be at least move-constructible and move-assignable. 20 | 21 | - `typename... Cmds` - The commands that are handled by this handler. 22 | 23 | 24 | The `handle_return` functions are overridden as: 25 | 26 | ```cpp 27 | template 28 | Answer flat_handler::handle_return(Answer a) 29 | { 30 | return a; 31 | } 32 | 33 | template 34 | void flat_handler::handle_return() { } 35 | ``` 36 | 37 |
38 | Example 39 | 40 | Consider the following tick handler that is generic in the return type: 41 | 42 | ```cpp 43 | struct Tick : command { }; 44 | 45 | template 46 | class Counter : public flat_handler > { 47 | int counter = 0; 48 | int handle_command(Tick) final override 49 | { 50 | return ++counter; 51 | } 52 | }; 53 | ``` 54 | 55 | With the `Handler` class we would have to provide a separate specialisation for `void`: 56 | 57 | ```cpp 58 | struct Tick : command { }; 59 | 60 | template 61 | class Counter : public handler> { 62 | int counter = 0; 63 | int handle_command(Tick) final override 64 | { 65 | return ++counter; 66 | } 67 | T handle_return(T a) 68 | { 69 | return a; 70 | } 71 | }; 72 | 73 | template <> 74 | class Counter : public handler> { 75 | int counter = 0; 76 | int handle_command(Tick) final override 77 | { 78 | return ++counter; 79 | } 80 | void handle_return() { } 81 | }; 82 | ``` 83 | 84 |
85 | -------------------------------------------------------------------------------- /doc/refman-fresh_label.md: -------------------------------------------------------------------------------- 1 | # function `fresh_label` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | int64_t fresh_label(); 7 | ``` 8 | 9 | When a command is invoked, the handler is chosen based on the type of the command using the usual "innermost" rule. However, one can use labels to directly match commands with handlers. The function `fresh_label` generates a unique label, which can be used later with the overloads with `label` arguments. 10 | 11 | - **Return value** `int64_t` - The generated label. 12 | 13 | -------------------------------------------------------------------------------- /doc/refman-handle.md: -------------------------------------------------------------------------------- 1 | # function `handle` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | template 7 | typename H::answer_type handle( 8 | int64_t label, std::function body, Args&&... args); 9 | 10 | template 11 | typename H::answer_type handle(std::function body, Args&&... args); 12 | ``` 13 | 14 | Create a new [handler](refman-handler.md) of type `H` and use it to handle the computation `body`. 15 | 16 | - `typename H` - The type of the handler that is used to handle `body`. 17 | 18 | - `typename... Args` - Arguments supplied to the constructor of `H`. 19 | 20 | - `int64_t label` - Explicit label of the handler. If no label is given, this handler is used based on the types of the commandss of `H` (the innermost handler that handles the invoked command is used). 21 | 22 | - `std::function body` - The handled computation. 23 | 24 | - **Return value** `H::answer_type` - The final answer of the handler, returned by one of the overloads of `H::handle_command` or `H::handle_return`. 25 | 26 | Note: `handle(b)` is equivalent to `handle_with(b, std::make_shared())`. 27 | -------------------------------------------------------------------------------- /doc/refman-handle_ref.md: -------------------------------------------------------------------------------- 1 | # function `handle_ref` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | :warning: This feature is experimental! 6 | 7 | ```cpp 8 | template 9 | typename H::answer_type handle_ref( 10 | int64_t label, std::function body, Args&&... args); 11 | 12 | template 13 | typename H::answer_type handle_ref( 14 | std::function body, Args&&... args); 15 | ``` 16 | 17 | Similar to [`handle`](refman-handle.md), but reveals the [handler reference](refman-handler_ref.md) to the installed handler as an argument to the body. 18 | -------------------------------------------------------------------------------- /doc/refman-handle_with.md: -------------------------------------------------------------------------------- 1 | # function `handle_with` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | template 7 | typename H::answer_type handle_with( 8 | int64_t label, std::function body, std::shared_ptr handler); 9 | 10 | template 11 | typename H::answer_type handle_with( 12 | std::function body, std::shared_ptr handler); 13 | ``` 14 | 15 | Handle the computation `body` using the given handler of type `H`. 16 | 17 | - `typename H` - The type of the handler that is used to handle `body`. 18 | 19 | - `int64_t label` - Explicit label given to `handler`. If no label is given, this handler is used based on the types of the commands of `H` (the innermost handler that handles the invoked command is used). 20 | 21 | - `std::function body` - The handled computation. 22 | 23 | - `std::shared_ptr handler` - The handler used to handle `body`. 24 | 25 | - **Return value** `H::answer_type` - The final answer of the handler, returned by one of the overloads of `H::handle_command` or `H::handle_return`. 26 | -------------------------------------------------------------------------------- /doc/refman-handle_with_ref.md: -------------------------------------------------------------------------------- 1 | # function `handle_with_ref` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | :warning: This feature is experimental! 6 | 7 | ```cpp 8 | template 9 | typename H::answer_type handle_ref( 10 | int64_t label, std::function body, Args&&... args); 11 | 12 | template 13 | typename H::answer_type handle_ref( 14 | std::function body, Args&&... args); 15 | ``` 16 | 17 | Similar to [`handle_with`](refman-handle_with.md), but reveals the [handler reference](refman-handler_ref.md) to the installed handler as an argument to the body. 18 | -------------------------------------------------------------------------------- /doc/refman-handler.md: -------------------------------------------------------------------------------- 1 | # class `handler` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | Base class for handlers. 6 | 7 | ```cpp 8 | template 9 | class handler 10 | { 11 | public: 12 | using answer_type = Answer; 13 | using body_type = Body; 14 | virtual void debug_print() const; 15 | protected: 16 | // For each Cmd in Cmds... 17 | virtual Answer handle_command( 18 | Cmd, resumption>) = 0; 19 | 20 | virtual Answer handle_return(Body b) = 0; 21 | }; 22 | 23 | // Specialisation for Body = void: 24 | 25 | template 26 | class handler 27 | { 28 | public: 29 | using answer_type = Answer; 30 | using body_type = void; 31 | virtual void debug_print() const; 32 | protected: 33 | // For each Cmd in Cmds... 34 | virtual Answer handle_command( 35 | Cmd, resumption>) = 0; 36 | 37 | virtual Answer handle_return() = 0; 38 | }; 39 | 40 | ``` 41 | 42 | - `typename Answer` - The overall answer type of a derived handler. Should be at least move-constructible and move-assignable. 43 | 44 | - `typename Body` - The type of the handled computation. Should be at least move-constructible and move-assignable. 45 | 46 | - `typename... Cmds` - The [commands](refman-command.md) that are handled by this handler. 47 | 48 | To define a handler for a set of commands, one needs to derive from `handler` and specify the command clauses and the return clause. 49 | 50 | ### :large_orange_diamond: handler::answer_type 51 | 52 | ```cpp 53 | using answer_type = Answer; 54 | ``` 55 | 56 | Reveals the type of the overall answer of a handler. 57 | 58 | 59 | ### :large_orange_diamond: handler::body_type 60 | 61 | ```cpp 62 | using body_type = Body; 63 | ``` 64 | 65 | Reveals the type of the handled computation. 66 | 67 | 68 | ### :large_orange_diamond: handler::handle_command 69 | 70 | ```cpp 71 | virtual Answer command_clause(Cmd, resumption>) = 0; 72 | ``` 73 | 74 | A handler handles a particular [command](refman-command.md) `Cmd` if it inherits from `handler<..., Cmd, ...>` and overrides the overload of the member function `handle_command(Cmd, ...)` with the interpretation of the command. 75 | 76 | - `Cmd c` - The handled command. The right clause is chosen via the overloading resolution mechanism out of the overloads of `handle_command` in the handler based on the type `Cmd`. 77 | 78 | - `resumption> r` - The captured [resumption](refman-resumption.md). When the user invokes a command, control goes back to the handler and the computation delimited by the handler is captured as the resumption `r`. Since resumptions are one-shot, when we resume `r`, we have to give up the ownership of `r`, which makes `r` invalid. 79 | 80 | - **return value** `Answer` - The overall result of handling the computation. 81 | 82 | :information_source: Technically, `handle_command` is a virtual member function of `cpp_effects_internals::command_clause`, from which `handler` inherits for each `Cmd`. 83 | 84 | ### :large_orange_diamond: handler::handle_return 85 | 86 | ```cpp 87 | virtual Answer handle_return(Body b); // if Body != void 88 | virtual Answer handle_return(); // if Body == void 89 | ``` 90 | 91 | If a handled computation or resumed resumption gives a result without invoking a command, `handle_return` specifies the overall answer of the handler. 92 | 93 | - `Body b` - the value that is the result of the handled computation. 94 | 95 | - **return value** `Answer` - The overall value returned from handling of a computation. 96 | -------------------------------------------------------------------------------- /doc/refman-handler_ref.md: -------------------------------------------------------------------------------- 1 | # type `handler_ref` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | Abstract reference to an active handler. 6 | 7 | ```cpp 8 | using handler_ref = ...; 9 | ``` 10 | 11 | A `handler_ref` is an abstract reference to an active 12 | [handler](refman-handler.md), which can be used to tie the handler to 13 | a [command](refman-command.md), without the need to look up the 14 | handler every time we use [`invoke_command`](refman-invoke_command.md) 15 | or [`static_invoke_command`](refman-static_invoke_command.md). 16 | 17 | :bangbang: Handler references are valid if the referenced handler is 18 | active. Invoking a command with a reference to a handler of a 19 | computation that has already ended or is captured in a resumption will 20 | cause undefined behaviour. 21 | 22 | A handler reference should not be confused with: 23 | 24 | - A reference to an object of a class that inherits from 25 | [`handler`](refman-handler.md), although it is not far from the 26 | truth: `handler_ref` can be thought of as a form of an iterator that 27 | points to an object, not a reference to the object itself. 28 | 29 | - A label, since labels are not necessarily unique, and invoking a 30 | command with a label still involves looking up a handler with the 31 | given label on the stack. 32 | 33 |
34 | Example 35 | 36 | ```cpp 37 | using namespace cpp_effects; 38 | 39 | struct Ask : command { }; 40 | struct AddOneMoreHandler : command<> { }; 41 | 42 | template 43 | class Reader : public flat_handler { 44 | public: 45 | Reader(int val) : val(val) { } 46 | private: 47 | int val; 48 | T handle_command(Ask, resumption r) override 49 | { 50 | return std::move(r).Resume(val); 51 | } 52 | T handle_command(AddOneMoreHandler, resumption r) override 53 | { 54 | return handle>(300, [r = r.release()]() { 55 | resumption res(r); 56 | std::move(res).resume(); 57 | }, 300); 58 | } 59 | }; 60 | 61 | int main() 62 | { 63 | handle_ref>(100, [](auto href) { 64 | debug_print_metastack(); 65 | std::cout << invoke_command(href, Ask{}) << std::endl; 66 | invoke_command(AddOneMoreHandler{}); 67 | handle>([=]() { 68 | debug_print_metastack(); 69 | std::cout << invoke_command(href, Ask{}) << std::endl; 70 | return 0; 71 | }, 200); 72 | }, 100); 73 | } 74 | ``` 75 | 76 | Example output: 77 | 78 | ``` 79 | 100:6ReaderIvE[3Ask][17AddOneMoreHandler] 80 | 0:N10CppEffects9MetaframeE 81 | 100 82 | -2:6ReaderIvE[3Ask][17AddOneMoreHandler] 83 | 100:6ReaderIvE[3Ask][17AddOneMoreHandler] 84 | 300:6ReaderIvE[3Ask][17AddOneMoreHandler] 85 | 0:N10CppEffects9MetaframeE 86 | 100 87 | ``` 88 | 89 |
90 | -------------------------------------------------------------------------------- /doc/refman-invoke_command.md: -------------------------------------------------------------------------------- 1 | # function `invoke_command` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | template 7 | typename Cmd::out_type invoke_command(int64_t goto_handler, const Cmd& cmd); 8 | 9 | template 10 | typename Cmd::out_type invoke_command(const Cmd& cmd); 11 | 12 | template 13 | typename Cmd::out_type invoke_command(handler_ref it, const Cmd& cmd); 14 | ``` 15 | 16 | Used in a handled computation to invoke a particular [command](refman-command.md). The current computation (up to and including the appropriate handler) is suspended, captured in a [resumption](refman-resumption.md), and the control goes to the handler. 17 | 18 | - `typename Cmd` - The type of the invoked command. 19 | 20 | - `int64_t label` - The label of the handler to which the control should go. If there is no handler with label `label` in the context or it does not handle the command `Cmd`, the program ends with exit code `-1`. 21 | 22 | - `handler_ref href` - A reference to the handler to which the control should go. See the documentation for [`handler_ref`](refman-handler_ref.md). 23 | 24 | - `const Cmd& cmd` - The invoked command. 25 | 26 | - **Return value** `Cmd::out_type` - the value with which the suspended computation is resumed. 27 | 28 | -------------------------------------------------------------------------------- /doc/refman-no_manage.md: -------------------------------------------------------------------------------- 1 | # class `no_manage` (clause modifier) 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | Specialisation for handlers that either: 6 | 7 | - Don't expose the resumption (i.e., all resumes happen within command 8 | clauses), 9 | 10 | - Don't access the handler object after resume, 11 | 12 | which amounts to almost all practical use-cases of handlers. A 13 | `no_manage` clause does not participate in the reference-counting 14 | memory management of handlers, saving a tiny bit of performance. 15 | 16 | ```cpp 17 | template 18 | struct no_manage { }; 19 | ``` 20 | 21 | Usage: 22 | 23 | ```cpp 24 | struct MyCmd : command { }; 25 | 26 | class MyHandler : flat_handler> { 27 | virtual bool handle_command(MyCmd, resumption r) override 28 | { 29 | std::move(r).resume(3); 30 | return 3; 31 | } 32 | } 33 | ``` 34 | 35 | The `handle_command`function is the same as in a usual handler, but it is not memory-managed. 36 | 37 | :information_source: [`plain`](refman-plain.md) and [`no_resume`](refman-no_resume.md) clauses are automatically `no_manage`. 38 | -------------------------------------------------------------------------------- /doc/refman-no_resume.md: -------------------------------------------------------------------------------- 1 | # class `no_resume` (clause modifier) 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | The `no_resume` modifier indicates that a particular implementation of `handle_command` in a handler will not need the resumption. 6 | 7 | ```cpp 8 | template 9 | struct no_resume { }; 10 | ``` 11 | 12 | Usage: 13 | 14 | ```cpp 15 | struct Error : command<> { }; 16 | 17 | class Cancel : public flat_handler> { 18 | void handle_command(Error) override // no "resumption" argument 19 | { 20 | std::cout << "Error!" << std::endl; 21 | } 22 | }; 23 | 24 | int main() 25 | { 26 | std::cout << "Welcome!" << std::endl; 27 | handle([](){ 28 | std::cout << "So far so good..." << std::endl; 29 | invoke_command(Error{}); 30 | std::cout << "I made it!" << std::endl; 31 | }); 32 | std::cout << "Bye!" << std::endl; 33 | } 34 | ``` 35 | 36 | Output: 37 | 38 | ``` 39 | Welcome! 40 | So far so good... 41 | Error! 42 | Bye! 43 | ``` 44 | -------------------------------------------------------------------------------- /doc/refman-plain.md: -------------------------------------------------------------------------------- 1 | # class `plain` (clause modifier) 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | A clause modifier used for `handle_command` functions that interpret commands as functions (i.e., they are self- and tail-resumptive). No context switching, no explicit resumption. 6 | 7 | ```cpp 8 | template 9 | struct plain { }; 10 | ``` 11 | 12 | Usage: 13 | 14 | ```cpp 15 | struct Add : command { 16 | int x, y; 17 | }; 18 | 19 | class Calculator : public flat_handler> { 20 | int handle_command(Add c) override // - no "resumption" argument 21 | // - return type that of the command, not handler 22 | { 23 | return c.x + c.y; 24 | } 25 | }; 26 | 27 | int main() 28 | { 29 | handle([](){ 30 | std::cout << "2 + 5 = " << invoke_command({{}, 2, 5}) << std::endl; 31 | }); 32 | } 33 | ``` 34 | 35 | Output: 36 | 37 | ``` 38 | 2 + 5 = 7 39 | ``` 40 | -------------------------------------------------------------------------------- /doc/refman-resumption.md: -------------------------------------------------------------------------------- 1 | # class `resumption` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | A resumption represents a suspended computation. A resumption is given to the user either as an argument of `handler<...>::handle_command`, or can be lifted from a function using a constructor or `wrap`. 6 | 7 | ```cpp 8 | template 9 | class resumption; 10 | 11 | template 12 | class resumption { 13 | public: 14 | resumption(); 15 | resumption(resumption_data* data); 16 | resumption(std::function); 17 | resumption(const resumption&) = delete; 18 | resumption(resumption&& other); 19 | 20 | resumption& operator=(const resumption&) = delete; 21 | resumption& operator=(resumption&& other); 22 | 23 | ~resumption(); 24 | 25 | explicit operator bool() const; 26 | bool operator!() const; 27 | resumption_data* release(); 28 | Answer resume(Out cmdResult) &&; 29 | Answer tail_resume(Out cmdResult) &&; 30 | }; 31 | 32 | template 33 | class resumption { 34 | public: 35 | resumption(); 36 | resumption(resumption_data* data); 37 | resumption(std::function); 38 | resumption(const resumption&) = delete; 39 | resumption(resumption&& other); 40 | 41 | resumption& operator=(const resumption&) = delete; 42 | resumption& operator=(resumption&& other); 43 | 44 | ~resumption(); 45 | 46 | explicit operator bool() const; 47 | bool operator!() const; 48 | resumption_data* release(); 49 | Answer resume() &&; 50 | Answer tail_resume() &&; 51 | }; 52 | 53 | ``` 54 | 55 | Objects of the `resumption` class are movable but not copyable. This is because they represent suspended **one-shot** continuations. 56 | 57 | The `resumption` class is actually a form of a smart pointer, so moving it around is cheap. 58 | 59 | **Type parameters:** 60 | 61 | - `typename T` - A function type that corresponds to the type of the suspended computation. 62 | 63 | **Specialisations:** 64 | 65 | - `resumption` - A computations that, when resumed, will return a value of type `Answer`. 66 | 67 | - `resumption` - A computation that needs a value of type `Out` to be resumed (`Out` is usually the output type of the operation on which the computation is suspended), and will return a value of type `Answer`. 68 | 69 | ### :large_orange_diamond: resumption::resumption 70 | 71 | ```cpp 72 | /* 1 */ resumption::resumption() 73 | 74 | /* 2 */ resumption::resumption(resumption_data* data) 75 | 76 | /* 3 */ resumption::resumption(std::function func) 77 | 78 | /* 4 */ resumption::resumption(std::function func) 79 | ``` 80 | 81 | Constructors. 82 | 83 | - `1` - Create a trivial **invalid** resumption. 84 | 85 | - `2` - Create a resumption from data previously released with `release`. 86 | 87 | - `3` - Lift a function to a resumption. 88 | 89 | - `4` - As above, specialisation for `T Answer()`. 90 | 91 | Arguments: 92 | 93 | - `resumption_data* data` - Data previously released with `release`. 94 | 95 | - `std::function func` - The lifted function. 96 | 97 | - `std::function func` - The lifted function (specialisation for `T == Answer()`). 98 | 99 | 100 | ### :large_orange_diamond: resumption::operator bool 101 | 102 | Check if the resumption is valid. The resumption becomes invalid if moved elsewhere (in particular, when resumed). 103 | 104 | ```cpp 105 | explicit operator bool() const; 106 | ``` 107 | 108 | - **return value** `bool` - Indicates if the resumption is valid. 109 | 110 | ### :large_orange_diamond: resumption::operator! 111 | 112 | Check if the resumption is invalid. The resumption becomes invalid if moved elsewhere (in particular, when resumed). 113 | 114 | ```cpp 115 | bool operator!() const; 116 | ``` 117 | 118 | - **return value** `bool` - Indicates if the resumption is invalid. 119 | 120 | ### :large_orange_diamond: resumption::release 121 | 122 | ```cpp 123 | resumption_data* release(); 124 | ``` 125 | 126 | releases the pointer to the suspended computation. 127 | 128 | - **return value** [`resumption_data*`](refman-resumption_data.md) - The released pointer. 129 | 130 | **Warning:** :warning: Never use `delete` on the released pointer! If you want to get rid of it safely, wrap it back in a dummy `resumption` value, and let its destructor do the job. For example: 131 | 132 | ```cpp 133 | void foo(resumption r) 134 | { 135 | auto ptr = r.release(); 136 | // ... 137 | resumption{ptr}; 138 | } 139 | ``` 140 | 141 | ### :large_orange_diamond: resumption::resume 142 | 143 | ```cpp 144 | Answer resumption::resume(Out cmdResult) && 145 | 146 | Answer resumption::resume() && 147 | ``` 148 | 149 | resume the suspended computation captured in the resumption. 150 | 151 | - `Out cmdResult` - The value that is returned by the 152 | [command](refman-command.md) on which the resumption "hangs". 153 | 154 | - **Return value** `Answer` - The result of the resumed computation. 155 | 156 | ### :large_orange_diamond: resumption::tail_resume 157 | 158 | ```cpp 159 | Answer resumption::tail_resume(Out cmdResult) && 160 | 161 | Answer resumption::tail_resume() && 162 | ``` 163 | 164 | Use to resume the suspended computation captured in the resumption in a tail position in the command clause. This is to be used **only inside a command clause** as the returned expression. Semantically, for an rvalue reference `r`, the expressions `return r.resume(...);` and `return r.tail_resume(...);` are semantically equivalent, but the latter does not build up the call stack. 165 | 166 | - `Out cmdResult` - The value that is returned by the [command](refman-command.md) on which the resumption "hangs". 167 | 168 | - **Return value** `Answer` - The result of the resumed computation. 169 | 170 | Tail-resumes are useful in command clauses such as: 171 | 172 | ```cpp 173 | int handle_command(SomeCommand, resumption r) override 174 | { 175 | // do sth 176 | return std::move(r).tail_resume(); 177 | } 178 | ``` 179 | 180 | In this example, we will build up the call stack until the entire handler returns a final answer (resulting in a "stack leak"). The library has a separate trampolining mechanism built in to avoid this: 181 | 182 | ```cpp 183 | // do sth 184 | return std::move(r).tail_resume(); 185 | ``` 186 | 187 | **NOTE:** `tail_resume` can be used only if `Answer` is trivially constructible. Consider the following command clause: 188 | 189 | ```cpp 190 | class H : Handler { 191 | // ... 192 | Answer handle_command(Op, resumption r) override 193 | { 194 | return std::move(r).tail_resume(); 195 | } 196 | } 197 | ``` 198 | 199 | What happens behind the scenes is that `tail_resume` returns a trivial value of type `Answer`, while the real resuming happens in a trampoline hidden in the [`handle`](refman-handle.md) function. 200 | -------------------------------------------------------------------------------- /doc/refman-resumption_data.md: -------------------------------------------------------------------------------- 1 | # classes `resumption_data` and `resumption_base` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | Classes that represents "bare" captured continuations that are not memory-managed by the library. 6 | 7 | ```cpp 8 | class resumption_base { 9 | public: 10 | virtual ~resumption_base() { } 11 | }; 12 | 13 | template 14 | class resumption_data : public resumption_base { }; 15 | ``` 16 | 17 | - `typename Out` - In a proper resumption, the output type of the command that suspended the computation. In a plain resumption, the input type of the lifted function. Can be `void`. 18 | 19 | - `typename Answer` - The return type of the suspended computation. 20 | -------------------------------------------------------------------------------- /doc/refman-static_invoke_command.md: -------------------------------------------------------------------------------- 1 | # function `static_invoke_command` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | template 7 | typename Cmd::out_type static_invoke_command(int64_t goto_handler, const Cmd& cmd); 8 | 9 | template 10 | typename Cmd::out_type static_invoke_command(const Cmd& cmd); 11 | 12 | template 13 | typename Cmd::out_type static_invoke_command(handler_ref it, const Cmd& cmd); 14 | ``` 15 | 16 | Used in a handled computation to invoke a particular [command](refman-command.md) (similar to [`invoke_command`](refman-invoke_command.md)), but the handler with the given label is statically cast to `H`. The current computation (up to and including the appropriate handler) is suspended, captured in a resumption, and the control goes to the handler. 17 | 18 | - `typename H` - The type of the handler used to handled the command. 19 | 20 | - `typename Cmd` - The type of the invoked command. 21 | 22 | - `int64_t label` - The label of the handler to which the control should go. If there is no handler with label `label`, the program exits with exit code `-1`. If the inner-most handler with label `label` is not of type (derived from) `H`, it results in undefined behaviour. If `label` is note supplied, the inner-most handler is used. 23 | 24 | - `handler_ref href` - A reference to the handler to which the control should go. See the documentation for [`handler_ref`](refman-handler_ref.md). 25 | 26 | - `const Cmd& cmd` - The invoked command. 27 | 28 | The point of `static_invoke_commnad` is that we often know upfront which handler will be used for a particular command. This way, instead of dynamically checking if a given handler is able to handle the invoke command, we can statically cast it to an appropriate handler `H`, trading dynamic type safety for performance. 29 | 30 | - **Return value** `Cmd::out_type` - the value with which the suspended computation is resumed. 31 | 32 | -------------------------------------------------------------------------------- /doc/refman-wrap.md: -------------------------------------------------------------------------------- 1 | # function `wrap` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | ```cpp 6 | template 7 | resumption wrap( 8 | int64_t label, std::function body, Args&&... args); 9 | 10 | template 11 | resumption wrap( 12 | int64_t label, std::function body, Args&&... args); 13 | 14 | template 15 | resumption wrap( 16 | std::function body, Args&&... args); 17 | 18 | template 19 | resumption wrap( 20 | std::function body, Args&&... args); 21 | ``` 22 | 23 | Wraps a computation in a handler, but doesn't execute it. Instead, the computation together with a handler are returned as a suspended computation (= [resumption](refman-resumption.md)). 24 | 25 | - `typename H` - The type of the handler that is used to handle `body`. 26 | 27 | - `typename... Args` - Arguments supplied to the constructor of `H`. 28 | 29 | - `int64_t label` - Explicit label of the handler. If no label is given, this handler is used based on the types of the commandss of `H` (the innermost handler that handles the invoked command is used). 30 | 31 | - `std::function body`, `std::function body`- The wrapped function. 32 | 33 | - **Return value** `resumption` - The resumption that corresponds to `body` wrapped in a handler `H`. 34 | 35 | Semantically, for a function `std::function foo`, the expression 36 | 37 | ```cpp 38 | wrap(foo) 39 | ``` 40 | 41 | is equivalent to 42 | 43 | ```cpp 44 | resumption([=](){ return handle(foo); }) 45 | ``` 46 | 47 | If the function has an argument, it becomes the `Out` type of the resumption. That is, for a function `std::function foo`, the expression 48 | 49 | ```cpp 50 | wrap(foo) 51 | ``` 52 | 53 | is equivalent to 54 | 55 | ```cpp 56 | resumption([=](A a){ return handle(std::bind(foo, a)); }) 57 | ``` 58 | -------------------------------------------------------------------------------- /doc/refman-wrap_with.md: -------------------------------------------------------------------------------- 1 | # function `wrap_with` 2 | 3 | [<< Back to reference manual](refman.md) 4 | 5 | 6 | ```cpp 7 | template 8 | resumption wrap_with( 9 | int64_t label, std::function body, std::shared_ptr handler); 10 | 11 | template 12 | resumption wrap_with( 13 | int64_t label, std::function body, std::shared_ptr handler); 14 | 15 | template 16 | resumption wrap_with( 17 | std::function body, std::shared_ptr handler); 18 | 19 | template 20 | resumption wrap_with( 21 | std::function body, std::shared_ptr handler); 22 | ``` 23 | 24 | Similar to [`wrap`](refman-wrap.md) but with a particular handler object. 25 | -------------------------------------------------------------------------------- /doc/refman.md: -------------------------------------------------------------------------------- 1 | # cpp-effects: Reference Manual 2 | 3 | ## Introduction 4 | 5 | :rocket: [Quick intro](quick-intro.md) (and comparison with effectful functional programming) - A brief overview of the API of the library. It assumes that the reader has some basic understanding of algebraic effects and effect handlers. 6 | 7 | 8 | ## Structure of the library 9 | 10 | **Namespace:** `cpp_effects` 11 | 12 | :memo: [`cpp-effects/cpp-effects.h`](../include/cpp-effects/cpp-effects.h) - The core of the library: 13 | 14 | - class [`command`](refman-command.md) - Classes derived from `command` define commands. 15 | 16 | - class [`flat_handler`](refman-flat_handler.md) - Version of `handler` for generic handlers with identity return clause. 17 | 18 | - class [`handler`](refman-handler.md) - Classes derived from `handler` define handlers. 19 | 20 | - type [`handler_ref`](refman-handler_ref.md) - Abstract reference to an active handler. 21 | 22 | - class [`resumption`](refman-resumption.md) - Suspended computation, given to the user in command clauses of a handler. 23 | 24 | - classes [`resumption_data`](refman-resumption_data.md) and [`resumption_base`](refman-resumption_data.md) - "Bare" captured continuations that are not memory-managed by the library. 25 | 26 | - namespace `cpp_effects_internal` - Details of the implementation, exposed for experimentation. 27 | 28 | - functions: 29 | 30 | * [`debug_print_metastack`](refman-debug_print_metastack.md) - Prints out the current stack of handlers. Useful for "printf" debugging. 31 | 32 | * [`fresh_label`](refman-fresh_label.md) - Generates a unique label that identifies a handler. 33 | 34 | * [`handle`](refman-handle.md) - Creates a new handler object and uses it to handle a computation. 35 | 36 | * [`handle_ref`](refman-handle_ref.md) - Similar to `handle`, but reveals a reference to the handler. 37 | 38 | * [`handle_with`](refman-handle_with.md) - Handles a computation using a given handler object. 39 | 40 | * [`handle_with_ref`](refman-handle_with_ref.md) - Handles a computation using a particular handler object and reveals a reference to the handler. 41 | 42 | * [`wrap`](refman-wrap.md) - Lifts a function to a resumption handled by a new handler object. 43 | 44 | * [`wrap_with`](refman-wrap_with.md) - Lifts a function to a resumption handled by a given handler object. 45 | 46 | * [`invoke_command`](refman-invoke_command.md) - Used in a handled computation to invoke a particular command, suspend the computation, and transfer control to the handler. 47 | 48 | * [`static_invoke_command`](refman-static_invoke_command.md) - Similar to `invoke_commad`, but explicitly gives the type of the handler object (not type-safe, but more efficient). 49 | 50 | :memo: [`cpp-effects/clause-modifiers.h`](../include/cpp-effects/clause-modifiers.h) - Modifiers that force specific shapes or properties of command clauses in handlers: 51 | 52 | - [`no_manage`](refman-no_manage.md) - Command clause that does not memory-manage the handler. 53 | 54 | - [`no_resume`](refman-no_resume.md) - Command clause that does not use its resumptions. 55 | 56 | - [`plain`](refman-plain.md) - Command clause that interprets a command as a function (i.e., a self- and tail-resumptive clause). 57 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (threads threads.cpp) 2 | add_executable (generators generators.cpp) 3 | add_executable (async-await async-await.cpp) 4 | add_executable (actors actors.cpp) 5 | add_executable (rollback-state rollback-state.cpp) 6 | add_executable (exceptions exceptions.cpp) 7 | add_executable (fuel fuel.cpp) 8 | add_executable (state state.cpp) 9 | add_executable (shift0-reset shift0-reset.cpp) 10 | add_executable (composition-actors composition-actors.cpp) 11 | add_executable (react react.cpp) 12 | add_executable (dep-injection dep-injection.cpp) 13 | -------------------------------------------------------------------------------- /examples/actors.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Message-passing actors. Each actor is defined as a class 6 | // (Compare also with the "composition-actors" example) 7 | 8 | // TODO: This example still requires some thought on memory management 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "cpp-effects/cpp-effects.h" 18 | 19 | namespace eff = cpp_effects; 20 | 21 | // ---------------------- 22 | // Programmer's interface 23 | // ---------------------- 24 | 25 | // The user defines an actor as a class derived from Actor, where T 26 | // is the type of messages that the actor accepts. Each actor's life 27 | // purpose is to execute the virtual function body. When it's 28 | // finished, the actor dies. 29 | // 30 | // An actor is an instance of this class. It should also be added to 31 | // the scheduler. One can start a scheduler like this: 32 | // 33 | // Scheduler scheduler; 34 | // scheduler.Run(p); 35 | // 36 | // where p is a pointer to the first actor to be executed by the 37 | // scheduler. New actors can be added to the scheduler by calling 38 | // Spawn from inside actors' bodies. 39 | 40 | class Scheduler; 41 | 42 | class ActorBase { 43 | friend class Scheduler; 44 | template friend class Actor; 45 | protected: 46 | virtual void body() = 0; 47 | private: 48 | Scheduler* scheduler = nullptr; 49 | bool awaitingMsg = false; 50 | }; 51 | 52 | template 53 | class Actor : public ActorBase { 54 | public: 55 | void Send(T msg); // Send a message to this actor 56 | protected: 57 | std::queue msgQueue; 58 | 59 | // Functions to be used inside of body: 60 | T Receive(); // Read message from inbox (or suspend if there isn't any) 61 | void Yield(); // Give up control 62 | void Spawn(ActorBase* actor); // Add an actor to the scheduler 63 | }; 64 | 65 | struct ActorInfo; 66 | 67 | class Scheduler { 68 | friend class ActorHandler; 69 | template friend class Actor; 70 | public: 71 | void Run(ActorBase* actor); 72 | Scheduler() : label(eff::fresh_label()) { } 73 | private: 74 | int64_t label; 75 | ActorBase* currentActor; 76 | std::vector active; 77 | void wake(); 78 | }; 79 | 80 | // -------------- 81 | // Internal stuff 82 | // -------------- 83 | 84 | using Res = eff::resumption; 85 | 86 | struct ActorInfo { 87 | ActorBase* actorData; 88 | Res actorResumption; 89 | }; 90 | 91 | struct CmdYield : eff::command<> { }; 92 | 93 | struct CmdFork : eff::command<> { 94 | ActorBase* actor; 95 | }; 96 | 97 | class ActorHandler : public eff::handler { 98 | public: 99 | ActorHandler(Scheduler* scheduler) : scheduler(scheduler) { } 100 | private: 101 | Scheduler* scheduler; 102 | void handle_command(CmdYield, Res r) override 103 | { 104 | scheduler->active.push_back({scheduler->currentActor, std::move(r)}); 105 | scheduler->wake(); 106 | } 107 | void handle_command(CmdFork f, Res r) override 108 | { 109 | scheduler->active.push_back({ 110 | f.actor, 111 | {[f, this](){ scheduler->Run(f.actor); }}}); 112 | std::move(r).resume(); 113 | } 114 | void handle_return() override 115 | { 116 | if (scheduler->active.empty()) { return; } 117 | scheduler->wake(); 118 | } 119 | }; 120 | 121 | void Scheduler::wake() 122 | { 123 | for (auto it = active.begin(); it != active.end(); ++it) { 124 | if (it->actorData->awaitingMsg) { continue; } 125 | auto resumption = std::move(it->actorResumption); 126 | currentActor = it->actorData; 127 | active.erase(it); 128 | std::move(resumption).tail_resume(); 129 | return; 130 | } 131 | std::cerr << "deadlock detected" << std::endl; 132 | exit(-1); 133 | } 134 | 135 | void Scheduler::Run(ActorBase* f) 136 | { 137 | f->scheduler = this; 138 | currentActor = f; 139 | eff::handle(this->label, [f](){ f->body(); }, this); 140 | } 141 | 142 | template 143 | T Actor::Receive() 144 | { 145 | if (msgQueue.empty()) { 146 | awaitingMsg = true; 147 | eff::invoke_command(scheduler->label, CmdYield{}); 148 | } 149 | // A waiting actor can be resumed only after a message is put in its 150 | // inbox, so if the control reaches this point, it means the actor's 151 | // got mail. 152 | T t = msgQueue.front(); 153 | msgQueue.pop(); 154 | return t; 155 | } 156 | 157 | template 158 | void Actor::Send(T msg) 159 | { 160 | awaitingMsg = false; 161 | msgQueue.push(msg); 162 | eff::invoke_command(scheduler->label, CmdYield{}); 163 | } 164 | 165 | template 166 | void Actor::Yield() 167 | { 168 | eff::invoke_command(scheduler->label, CmdYield{}); 169 | } 170 | 171 | template 172 | void Actor::Spawn(ActorBase* actor) 173 | { 174 | actor->scheduler = scheduler; 175 | eff::invoke_command(scheduler->label, CmdFork{{}, actor}); 176 | } 177 | 178 | // ---------------- 179 | // Concrete example 180 | // ---------------- 181 | 182 | class Echo : public Actor*, int>> { 183 | void body() override 184 | { 185 | while (true) { 186 | auto [sender, msg] = Receive(); 187 | if (msg == -1) { 188 | std::cout << "no more echo" << std::endl; 189 | return; 190 | } 191 | sender->Send(msg); 192 | } 193 | } 194 | }; 195 | 196 | class Starter : public Actor { 197 | void body() 198 | { 199 | Echo echo; // Create an actor 200 | Spawn(&echo); // Add it to the scheduler 201 | 202 | for (int i = 0; i < 10; i++) { 203 | std::cout << "sent: " << i; 204 | echo.Send({this, i}); 205 | std::cout << ", received: " << Receive() << std::endl; 206 | } 207 | echo.Send({this, -1}); 208 | } 209 | }; 210 | 211 | int main() 212 | { 213 | Scheduler scheduler; 214 | Starter starter; 215 | scheduler.Run(&starter); 216 | 217 | // Output: 218 | // sent: 0, received: 0 219 | // sent: 1, received: 1 220 | // sent: 2, received: 2 221 | // sent: 3, received: 3 222 | // sent: 4, received: 4 223 | // sent: 5, received: 5 224 | // sent: 6, received: 6 225 | // sent: 7, received: 7 226 | // sent: 8, received: 8 227 | // sent: 9, received: 9 228 | // no more echo 229 | } 230 | -------------------------------------------------------------------------------- /examples/async-await.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Lightweight async-await with global randomised scheduler 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "cpp-effects/cpp-effects.h" 15 | 16 | namespace eff = cpp_effects; 17 | 18 | // ---------------------------- 19 | // Async-await with a scheduler 20 | // ---------------------------- 21 | 22 | using Res = eff::resumption; 23 | 24 | template 25 | class Scheduler; 26 | 27 | struct GenericFuture { 28 | std::vector awaiting; 29 | }; 30 | 31 | template 32 | class Future : public GenericFuture { 33 | template friend class Scheduler; 34 | public: 35 | T Value() const { return value.value(); } 36 | operator bool() const { return (bool)value; } 37 | private: 38 | std::optional value; 39 | }; 40 | 41 | struct Yield : eff::command<> { }; 42 | 43 | struct Await : eff::command<> { 44 | GenericFuture* future; 45 | }; 46 | 47 | std::vector queue; 48 | 49 | template 50 | class Scheduler : public eff::handler { 51 | template friend Future* async(std::function f); 52 | public: 53 | Scheduler(Future* currentFuture) : currentFuture(currentFuture) { } 54 | static T Run(std::function f) 55 | { 56 | Future future; 57 | Scheduler::Run(&future, f); 58 | return future.Value(); 59 | } 60 | private: 61 | Future* currentFuture; 62 | static void Run(Future* future, std::function f) 63 | { 64 | eff::handle>(f, future); 65 | } 66 | void wakeRandom() 67 | { 68 | if (queue.empty()) { return; } 69 | auto it = queue.begin(); 70 | std::advance(it, rand() % queue.size()); 71 | auto resumption = std::move(*it); 72 | queue.erase(it); 73 | std::move(resumption).resume(); 74 | } 75 | void handle_command(Yield, Res r) override 76 | { 77 | queue.push_back(std::move(r)); 78 | wakeRandom(); 79 | } 80 | void handle_command(Await f, Res r) override 81 | { 82 | f.future->awaiting.push_back(std::move(r)); 83 | wakeRandom(); 84 | } 85 | void handle_return(T val) override 86 | { 87 | std::move(currentFuture->awaiting.begin(), currentFuture->awaiting.end(), 88 | std::back_inserter(queue)); 89 | currentFuture->value = val; 90 | wakeRandom(); 91 | } 92 | }; 93 | 94 | void yield() 95 | { 96 | eff::invoke_command(Yield{}); 97 | } 98 | 99 | template 100 | T await(Future* future) 101 | { 102 | if (*future) { return future->Value(); } 103 | eff::invoke_command(Await{{}, future}); 104 | return future->Value(); 105 | } 106 | 107 | // The pointer returned by async is not unique. It is shared between 108 | // the user and the scheduler. Only when the future is fulfilled, the 109 | // scheduler forgets the pointer and the user is safe to delete the 110 | // future. 111 | 112 | template 113 | Future* async(std::function f) 114 | { 115 | auto future = new Future; 116 | queue.push_back({[f, future]() { 117 | Scheduler::Run(future, f); 118 | }}); 119 | return future; 120 | } 121 | 122 | // ------------------ 123 | // Particular example 124 | // ------------------ 125 | 126 | int worker() 127 | { 128 | for (int i = 0; i < 30; i++) { 129 | std::cout << "." << std::flush; 130 | yield(); 131 | } 132 | return 100; 133 | } 134 | 135 | std::monostate starter() 136 | { 137 | auto future = async(worker); 138 | std::cout << "[worker started]" << std::flush; 139 | 140 | for (int i = 0; i < 5; i++) { 141 | if (!*future) { 142 | std::cout << std::endl << "[no value yet]" << std::flush; 143 | yield(); 144 | } 145 | } 146 | std::cout << std::endl << "[I'd better wait]" << std::flush; 147 | int workerResult = await(future); 148 | delete future; 149 | std::cout << std::endl << "[worker returned " << workerResult << "]" << std::flush; 150 | return {}; 151 | } 152 | 153 | int main() 154 | { 155 | srand(11); 156 | Scheduler::Run(starter); 157 | 158 | std::cout << std::endl; 159 | 160 | // Output: 161 | // [worker started] 162 | // [no value yet] 163 | // [no value yet].... 164 | // [no value yet].. 165 | // [no value yet] 166 | // [no value yet]. 167 | // [I'd better wait]....................... 168 | // [worker returned 100] 169 | } 170 | -------------------------------------------------------------------------------- /examples/composition-actors.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Erlang-style actors obtained by composition of two effects: threads and mutable state 6 | 7 | // In this example, the dynamic binding of an effect to a handler is crucial. It is used by an 8 | // actor to get the reference to the right mailbox in the "receive" function. 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "cpp-effects/cpp-effects.h" 16 | #include "cpp-effects/clause-modifiers.h" 17 | 18 | namespace eff = cpp_effects; 19 | 20 | // ------------- 21 | // Mutable state 22 | // ------------- 23 | 24 | template 25 | struct Put : eff::command<> { 26 | S newState; 27 | }; 28 | 29 | template 30 | struct Get : eff::command { }; 31 | 32 | template 33 | void put(S s) { 34 | eff::invoke_command(Put{{}, s}); 35 | } 36 | 37 | template 38 | S get() { 39 | return eff::invoke_command(Get{}); 40 | } 41 | 42 | template 43 | class HStateful : public eff::flat_handler>, eff::plain>> { 44 | public: 45 | HStateful(S initialState) : state(initialState) { } 46 | private: 47 | S state; 48 | void handle_command(Put p) final override 49 | { 50 | state = p.newState; 51 | } 52 | S handle_command(Get) final override 53 | { 54 | return state; 55 | } 56 | }; 57 | 58 | // ------------------- 59 | // Lightweight threads 60 | // ------------------- 61 | 62 | struct Yield : eff::command<> { }; 63 | 64 | struct Fork : eff::command<> { 65 | std::function proc; 66 | }; 67 | 68 | struct Kill : eff::command<> { }; 69 | 70 | void yield() 71 | { 72 | eff::invoke_command(Yield{}); 73 | } 74 | 75 | void fork(std::function proc) 76 | { 77 | eff::invoke_command(Fork{{}, proc}); 78 | } 79 | 80 | void kill() 81 | { 82 | eff::invoke_command(Kill{}); 83 | } 84 | 85 | using Res = eff::resumption; 86 | 87 | class Scheduler : public eff::flat_handler { 88 | public: 89 | static void Start(std::function f) 90 | { 91 | queue.push_back(eff::wrap(f)); 92 | while (!queue.empty()) { // Round-robin scheduling 93 | auto resumption = std::move(queue.front()); 94 | queue.pop_front(); 95 | std::move(resumption).resume(); 96 | } 97 | } 98 | private: 99 | static std::list queue; 100 | void handle_command(Yield, Res r) override 101 | { 102 | queue.push_back(std::move(r)); 103 | } 104 | void handle_command(Fork f, Res r) override 105 | { 106 | queue.push_back(std::move(r)); 107 | queue.push_back(eff::wrap(f.proc)); 108 | } 109 | void handle_command(Kill, Res) override { } 110 | }; 111 | 112 | std::list Scheduler::queue; 113 | 114 | // ------ 115 | // Actors 116 | // ------ 117 | 118 | using Pid = std::shared_ptr>; 119 | 120 | Pid spawn(std::function body) 121 | { 122 | auto msgQueue = std::make_shared>(); 123 | fork([=]() { 124 | eff::handle>(body, msgQueue); 125 | }); 126 | return msgQueue; 127 | } 128 | 129 | Pid self() 130 | { 131 | return get(); 132 | } 133 | 134 | template 135 | T receive() 136 | { 137 | auto q = get(); 138 | while (q->empty()) { yield(); } 139 | auto result = q->front(); 140 | q->pop(); 141 | return std::any_cast(result); 142 | } 143 | 144 | template 145 | void send(Pid p, T msg) 146 | { 147 | p->push(msg); 148 | } 149 | 150 | // ------------------ 151 | // Particular example 152 | // ------------------ 153 | 154 | void pong() 155 | { 156 | while (true) { 157 | auto [pid, n] = receive>(); 158 | if (n == 0) { return; } 159 | send(pid, n); 160 | } 161 | } 162 | 163 | void ping() 164 | { 165 | auto pongPid = spawn(pong); 166 | for (int i = 1; i <= 10; i++) { 167 | std::cout << "sent: " << i << ", "; 168 | send>(pongPid, {self(), i}); 169 | int response = receive(); 170 | std::cout << "received: " << response << std::endl; 171 | } 172 | send>(pongPid, {self(), 0}); 173 | } 174 | 175 | int main() 176 | { 177 | Scheduler::Start(std::bind(spawn, ping)); 178 | 179 | // Output: 180 | // sent: 1, received: 1 181 | // sent: 2, received: 2 182 | // sent: 3, received: 3 183 | // sent: 4, received: 4 184 | // sent: 5, received: 5 185 | // sent: 6, received: 6 186 | // sent: 7, received: 7 187 | // sent: 8, received: 8 188 | // sent: 9, received: 9 189 | // sent: 10, received: 10 190 | } 191 | -------------------------------------------------------------------------------- /examples/dep-injection.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: We control the output stream by using handlers 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cpp-effects/cpp-effects.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | // ----------------- 17 | // Log 18 | // ----------------- 19 | 20 | // By invoking this operation, the handler logs data in different streams 21 | 22 | struct Log : eff::command<> {}; 23 | 24 | // Use stdout for logging 25 | 26 | template 27 | class StdoutLog : public eff::handler, T, Log> { 28 | private: 29 | std::ostream &output = std::cout; 30 | std::optional 31 | handle_command(Log, eff::resumption()> r) override { 32 | return std::move(r).resume(); 33 | } 34 | std::optional handle_return(T val) override { 35 | output << val << std::endl; 36 | return std::optional(val); 37 | } 38 | }; 39 | 40 | // Use stderr for logging 41 | 42 | template 43 | class StderrLog : public eff::handler, T, Log> { 44 | private: 45 | std::ostream &output = std::cerr; 46 | std::optional 47 | handle_command(Log, eff::resumption()> r) override { 48 | return std::move(r).resume(); 49 | } 50 | std::optional handle_return(T val) override { 51 | output << val << std::endl; 52 | return std::optional(val); 53 | } 54 | }; 55 | 56 | // ------------------ 57 | // Particular example 58 | // ------------------ 59 | 60 | int64_t fib(int64_t n) { 61 | eff::invoke_command(Log{}); 62 | switch (n) { 63 | case 0: 64 | return 0; 65 | case 1: 66 | return 1; 67 | default: 68 | return fib(n - 1) + fib(n - 2); 69 | } 70 | } 71 | 72 | void stdoutFib(int64_t n) { 73 | // Log the result of fib(n) using stdout 74 | eff::handle>(std::bind(fib, n)); 75 | } 76 | 77 | void stderrFib(int64_t n) { 78 | // Log the result of fib(n) using stderr 79 | eff::handle>(std::bind(fib, n)); 80 | } 81 | 82 | int main() { 83 | stdoutFib(5); 84 | stderrFib(10); 85 | stdoutFib(15); 86 | stderrFib(20); 87 | 88 | // Output: 89 | // stdout -> fib(5) = 5 90 | // stderr -> fib(10) = 55 91 | // stdout -> fib(15) = 610 92 | // stderr -> fib(20) = 6765 93 | } 94 | -------------------------------------------------------------------------------- /examples/exceptions.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Exception handlers via effect handlers 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpp-effects/cpp-effects.h" 12 | #include "cpp-effects/clause-modifiers.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | // ------------------------ 17 | // Exceptions and a handler 18 | // ------------------------ 19 | 20 | // There is actually one exception, called Error. We define one 21 | // handler, which handles an exception by returning a value specified 22 | // in the constructor. 23 | 24 | struct Error : eff::command<> { }; 25 | 26 | template 27 | [[noreturn]] T error() 28 | { 29 | eff::invoke_command(Error{}); 30 | exit(-1); // This will never happen! 31 | } 32 | 33 | template 34 | class WithDefault : public eff::flat_handler { 35 | public: 36 | WithDefault(const T& t) : defaultVal(t) { } 37 | private: 38 | const T defaultVal; 39 | T handle_command(Error, eff::resumption) override 40 | { 41 | return defaultVal; 42 | } 43 | }; 44 | 45 | // ------------------ 46 | // Particular example 47 | // ------------------ 48 | 49 | // We multiply numbers in a vector, but if any of the terms is 0, we 50 | // can break the loop using an exception, as we know that the product 51 | // is 0 as well. 52 | 53 | int product(const std::vector& v) 54 | { 55 | return eff::handle>( 56 | [&v]() { 57 | int r = 1; 58 | for (auto i : v) { 59 | if (i == 0) { error(); } 60 | r *= i; 61 | } 62 | return r; 63 | }, 64 | 0); // the default value is 0 65 | } 66 | 67 | int main() 68 | { 69 | std::cout << product({1, 2, 3, 4, 5}) << std::flush << std::endl; 70 | std::cout << product({1, 2, 0, 4, 5}) << std::flush << std::endl; 71 | 72 | // Output: 73 | // 120 74 | // 0 75 | } 76 | -------------------------------------------------------------------------------- /examples/fuel.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: We control execution of a computation by limiting the 6 | // amount of "fuel" it can use. 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "cpp-effects/cpp-effects.h" 14 | 15 | namespace eff = cpp_effects; 16 | 17 | // ----------------- 18 | // Bounded execution 19 | // ----------------- 20 | 21 | // By invoking this operation, a computation "consumes" a given amount 22 | // of fuel 23 | 24 | struct Consume : eff::command<> { 25 | int64_t amount; 26 | }; 27 | 28 | // Allow a computation to use only a certain amount of fuel 29 | 30 | template 31 | class BoundedExecution : public eff::handler, T, Consume> { 32 | public: 33 | BoundedExecution(int64_t fuel) : fuel(fuel) { } 34 | private: 35 | int64_t fuel; 36 | std::optional handle_command(Consume c, eff::resumption()> r) override 37 | { 38 | if (fuel < c.amount) { return {}; } // Not enough fuel left to continue 39 | fuel -= c.amount; 40 | return std::move(r).resume(); 41 | } 42 | std::optional handle_return(T val) override 43 | { 44 | return std::optional(val); 45 | } 46 | }; 47 | 48 | // Run a computation to completion, but record the amount of fuel it used 49 | 50 | template 51 | class MeasureFuel : public eff::handler, T, Consume> { 52 | private: 53 | int64_t fuel = 0; 54 | std::tuple handle_command(Consume c, 55 | eff::resumption()> r) override 56 | { 57 | fuel += c.amount; 58 | return std::move(r).resume(); 59 | } 60 | std::tuple handle_return(T val) override 61 | { 62 | return {val, fuel}; 63 | } 64 | }; 65 | 66 | // ------------------ 67 | // Particular example 68 | // ------------------ 69 | 70 | int64_t fib(int64_t n) 71 | { 72 | eff::invoke_command(Consume{{}, 1}); // consume 1 unit of fuel 73 | switch (n) { 74 | case 0: return 0; 75 | case 1: return 1; 76 | default: return fib(n-1) + fib(n-2); 77 | } 78 | } 79 | 80 | void tryFib(int64_t n) 81 | { 82 | std::cout << "fib(" << n << ") = "; 83 | std::optional res = eff::handle>( 84 | std::bind(fib, n), 85 | 10000); // supply 10000 units of fuel 86 | 87 | if (res) { 88 | std::cout << res.value() << std::endl; 89 | } else { 90 | std::cout << "(not enough fuel to complete this computation)" << std::endl; 91 | } 92 | } 93 | 94 | void measureFib(int64_t n) 95 | { 96 | std::tuple res = eff::handle>(std::bind(fib, n)); 97 | 98 | std::cout << "fib(" << n << ") = " << std::get<0>(res) 99 | << "\t(took " << std::get<1>(res) << " steps to complete)" << std::endl; 100 | } 101 | 102 | int main() 103 | { 104 | tryFib(5); 105 | tryFib(10); 106 | tryFib(15); 107 | tryFib(20); 108 | 109 | // Output: 110 | // fib(5) = 5 111 | // fib(10) = 55 112 | // fib(15) = 610 113 | // fib(20) = (not enough fuel to complete this computation) 114 | 115 | measureFib(5); 116 | measureFib(10); 117 | measureFib(15); 118 | measureFib(20); 119 | 120 | // Output: 121 | // fib(5) = 5 (took 15 steps to complete) 122 | // fib(10) = 55 (took 177 steps to complete) 123 | // fib(15) = 610 (took 1973 steps to complete) 124 | // fib(20) = 6765 (took 21891 steps to complete) 125 | } 126 | -------------------------------------------------------------------------------- /examples/generators.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Generators 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpp-effects/cpp-effects.h" 12 | #include "cpp-effects/clause-modifiers.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | // -------------- 17 | // Internal stuff 18 | // -------------- 19 | 20 | template 21 | class GeneratorHandler; 22 | 23 | template 24 | struct Yield : eff::command<> { 25 | T value; 26 | }; 27 | 28 | template 29 | void yield(int64_t label, T x) 30 | { 31 | eff::static_invoke_command>(label, Yield{{}, x}); 32 | } 33 | 34 | template 35 | struct GenState; 36 | 37 | template 38 | using Result = std::optional>; 39 | 40 | template 41 | struct GenState { 42 | T value; 43 | eff::resumption()> resumption; 44 | }; 45 | 46 | template 47 | class GeneratorHandler : public eff::handler, void, eff::no_manage>> { 48 | Result handle_command(Yield y, eff::resumption()> r) override 49 | { 50 | return GenState{y.value, std::move(r)}; 51 | } 52 | Result handle_return() override 53 | { 54 | return {}; 55 | } 56 | }; 57 | 58 | // ---------------------- 59 | // Programmer's interface 60 | // ---------------------- 61 | 62 | // When a generator is created, its body is executed until the first 63 | // Yield. It is only because in this example we want the programmer's 64 | // interface to be as simple as possible, so that the "there is a 65 | // value" and "you can resume me" states of a generator always hve the 66 | // same value, available via operator bool. 67 | 68 | template 69 | class Generator { 70 | public: 71 | Generator(std::function)> f) 72 | { 73 | auto label = eff::fresh_label(); 74 | result = eff::handle>(label, [f, label](){ 75 | f([label](T x) { yield(label, x); }); 76 | }); 77 | } 78 | Generator() // Create a dummy generator that generates nothing 79 | { 80 | } 81 | T Value() const 82 | { 83 | if (!result) { throw std::out_of_range("Generator::Value"); } 84 | return result.value().value; 85 | } 86 | bool Next() 87 | { 88 | if (!result) { throw std::out_of_range("Generator::Next"); } 89 | result = std::move(result->resumption).resume(); 90 | return result.has_value(); 91 | } 92 | explicit operator bool() const 93 | { 94 | return result.has_value(); 95 | } 96 | private: 97 | Result result = {}; 98 | }; 99 | 100 | // ------------------ 101 | // Particular example 102 | // ------------------ 103 | 104 | int main() 105 | { 106 | Generator naturals([](auto yield) { 107 | int i = 1; 108 | while (true) { yield(i++); } 109 | }); 110 | 111 | Generator peaks([](auto yield) { 112 | yield("Everest"); 113 | yield("K2"); 114 | yield("Kangchenjunga"); 115 | yield("Lhotse"); 116 | yield("Makalu"); 117 | }); 118 | 119 | while ((bool)peaks) { 120 | std::cout << naturals.Value() << " " << peaks.Value() << std::endl; 121 | naturals.Next(); 122 | peaks.Next(); 123 | } 124 | 125 | // Output: 126 | // 1 Everest 127 | // 2 K2 128 | // 3 Kangchenjunga 129 | // 4 Lhotse 130 | // 5 Makalu 131 | } 132 | -------------------------------------------------------------------------------- /examples/react.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | /* 6 | Example: A toy library for lightweight-threaded GUI in terminal 7 | 8 | (toy = the only event is timer, and components are: text, blinking 9 | text, spinner, and progress bar) 10 | 11 | The programmer's interface is very loosely based on React with 12 | functional components and hooks. The idea is that we supply a function 13 | that renders the interface. This function can use "hooks", which 14 | access pieces of data modified by threads. Such a thread can, for 15 | example, describe an animation. 16 | 17 | The point of this example is to show that effect handlers are useful 18 | when one needs a customised scheduler for lightweight threads. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "cpp-effects/cpp-effects.h" 33 | #include "cpp-effects/clause-modifiers.h" 34 | 35 | namespace eff = cpp_effects; 36 | 37 | using namespace std::literals::chrono_literals; 38 | 39 | namespace chrono = std::chrono; 40 | 41 | // --------- 42 | // Internals 43 | // --------- 44 | 45 | using TimePoint = chrono::time_point; 46 | 47 | struct Sleep : eff::command<> { 48 | const chrono::milliseconds time; 49 | }; 50 | 51 | struct Fork : eff::command<> { 52 | std::function proc; 53 | bool background = false; 54 | }; 55 | 56 | struct Kill : eff::command<> { }; 57 | 58 | void yield() 59 | { 60 | eff::invoke_command(Sleep{{}, 0ms}); 61 | } 62 | 63 | void sleep(chrono::milliseconds t) 64 | { 65 | eff::invoke_command(Sleep{{}, t}); 66 | } 67 | 68 | void fork(std::function proc) 69 | { 70 | eff::invoke_command(Fork{{}, proc}); 71 | } 72 | 73 | void kill() 74 | { 75 | eff::invoke_command(Kill{}); 76 | } 77 | 78 | using Res = eff::resumption; 79 | 80 | using INT = int; 81 | 82 | struct ThreadInfo { 83 | TimePoint wakeAt; 84 | bool background; 85 | Res res; 86 | }; 87 | 88 | class Scheduler : public eff::flat_handler { 89 | public: 90 | static void Start(std::function f) 91 | { 92 | std::cout << "\x1B[?25l"; // hide cursor 93 | f(); 94 | std::cout << std::flush; 95 | 96 | while (!queue.empty()) { 97 | // Check if anything left to run 98 | auto checkBg = [](ThreadInfo const& ti){ return !ti.background; }; 99 | if (std::find_if(queue.begin(), queue.end(), checkBg) == queue.end()) { 100 | // Last render before goodbye 101 | Clear(); 102 | f(); 103 | break; 104 | } 105 | 106 | // Run an event 107 | auto now = chrono::system_clock::now(); 108 | auto [t, background, resumption] = std::move(queue.front()); 109 | queue.pop_front(); 110 | if (t > now) { std::this_thread::sleep_for(t - now); } 111 | currentThread.background = background; 112 | std::move(resumption).resume(); 113 | 114 | // Render 115 | Clear(); 116 | f(); 117 | std::cout << std::flush; 118 | } 119 | std::cout << "\x1B[?25h"; // show cursor 120 | } 121 | static std::map hooks; 122 | static std::function root; 123 | static int lines; 124 | static ThreadInfo currentThread; 125 | static std::list queue; 126 | static void Clear() 127 | { 128 | for (int i = 0; i < lines; i++) { 129 | std::cout << "\033[2K" << "\r" << "\033[1F" ; 130 | } 131 | std::cout << "\033[2K" << "\r"; 132 | lines = 0; 133 | } 134 | static void Run(std::function f) 135 | { 136 | eff::handle(f); 137 | } 138 | static void Enqueue(TimePoint wakeAt, bool background, Res r) 139 | { 140 | auto pred = [&](auto&& b) { return wakeAt < b.wakeAt; }; 141 | auto it = std::find_if(queue.begin(), queue.end(), pred); 142 | queue.insert(it, {wakeAt, background, std::move(r)}); 143 | } 144 | void handle_command(Sleep s, Res r) override 145 | { 146 | Enqueue(chrono::system_clock::now() + s.time, currentThread.background, std::move(r)); 147 | } 148 | void handle_command(Fork f, Res r) override 149 | { 150 | auto time = chrono::system_clock::now(); 151 | Enqueue(time, currentThread.background, std::move(r)); 152 | Enqueue(time, f.background, Res{std::bind(Run, f.proc)}); 153 | } 154 | void handle_command(Kill, Res) override { } 155 | }; 156 | 157 | std::list Scheduler::queue; 158 | std::map Scheduler::hooks; 159 | int Scheduler::lines = 0; 160 | ThreadInfo Scheduler::currentThread; 161 | 162 | // ------------------------------------------- 163 | // Interface for using hooks in user's program 164 | // ------------------------------------------- 165 | 166 | // General function for using a hook 167 | 168 | template 169 | INT useHook(bool background, std::string hid, F const& /*std::function*/ hook, INT init, Args&&... args) 170 | { 171 | if (Scheduler::hooks.count(hid) == 0) 172 | { 173 | Scheduler::hooks[hid] = init; 174 | Scheduler::currentThread.background = background; 175 | Scheduler::Run(std::bind(hook, std::ref(Scheduler::hooks[hid]), std::forward(args)...)); 176 | } 177 | return Scheduler::hooks[hid]; 178 | } 179 | 180 | // Use a foreground hook 181 | // 182 | // (The program runs until the end of all foreground hooks. They are 183 | // useful for threads that provide control of the program.) 184 | 185 | template 186 | INT useHook(std::string hid, F const& hook, INT init, Args&&... args) 187 | { 188 | return useHook(false, hid, hook, init, std::forward(args)...); 189 | } 190 | 191 | // Use a background hook 192 | // 193 | // (Background hooks are useful for looped animations that end 194 | // together with the rest of the program.) 195 | 196 | template 197 | INT useBackgroundHook(std::string hid, F const& hook, INT init, Args&&... args) 198 | { 199 | return useHook(true, hid, hook, init, std::forward(args)...); 200 | } 201 | 202 | // ----------------- 203 | // Examples of hooks 204 | // ----------------- 205 | 206 | void animate(int& from, int to, chrono::milliseconds interval) 207 | { 208 | yield(); 209 | while (from < to) 210 | { 211 | from += 1; 212 | sleep(interval); 213 | } 214 | } 215 | 216 | void animateLoop(int& from, int to, chrono::milliseconds interval) 217 | { 218 | yield(); 219 | while (true) { 220 | from += 1; 221 | from %= to + 1; 222 | sleep(interval); 223 | } 224 | } 225 | 226 | // ---------------------- 227 | // Examples of components 228 | // ---------------------- 229 | 230 | void text(std::string s, bool bold = false) 231 | { 232 | if (bold) { std::cout << "\033[1m"; } // bold 233 | std::cout << s; 234 | if (bold) { std::cout << "\033[22m"; } // reset 235 | } 236 | 237 | void space() 238 | { 239 | text(" "); 240 | } 241 | 242 | void newLine() 243 | { 244 | Scheduler::lines++; 245 | text("\n"); 246 | } 247 | 248 | void progressBar(int val, int width = 40) 249 | { 250 | std::cout << "["; 251 | if (val < 100) 252 | { 253 | std::cout << "\033[0;31m"; // red 254 | } else { 255 | std::cout << "\033[0;32m"; // green 256 | } 257 | for (int i = 0; i < width; i++) 258 | { 259 | if (((i * 100) / width) < val) { 260 | std::cout << "━"; 261 | } else { 262 | std::cout << " "; 263 | } 264 | } 265 | std::cout << "\033[0m"; // reset 266 | std::cout << "]"; 267 | } 268 | 269 | void spinner() 270 | { 271 | auto d = useBackgroundHook("spinner", animateLoop, 0, 3, 200ms); 272 | const char chars[] = {'|', '/', '-', '\\'}; 273 | std::cout << "\033[1m" << chars[d] << "\033[22m"; // bold, reset 274 | } 275 | 276 | void blinkText(std::string s) 277 | { 278 | auto d = useBackgroundHook("blinkText", animateLoop, 0, 1, 800ms); 279 | if (d) { 280 | std::cout << s; 281 | } else { 282 | for (size_t i = 0; i < s.length(); i++) { std::cout << " "; } 283 | } 284 | } 285 | 286 | // ------------------ 287 | // Particular example 288 | // ------------------ 289 | 290 | // A user-defined component 291 | 292 | void myBar(int val, int width = 40) 293 | { 294 | progressBar(val, width); 295 | if (val < 100) { 296 | text(" "); 297 | text(std::to_string(val)); 298 | text("%"); 299 | } else { 300 | text(" done", true); 301 | } 302 | newLine(); 303 | } 304 | 305 | // This is a particular program 306 | 307 | void interface() 308 | { 309 | int a = useHook("progressA", animate, 0, 100, 30ms); 310 | int b = useHook("progressB", animate, 0, 100, 100ms); 311 | int c = useHook("progressC", animate, 0, 100, 60ms); 312 | int d = useHook("progressD", animate, 0, 100, 40ms); 313 | int e = useHook("progressE", animate, 0, 100, 85ms); 314 | auto sum = a + b + c + d + e; 315 | if (sum < 500) { 316 | spinner(); 317 | space(); 318 | } 319 | text("progress: "); 320 | text(std::to_string((a + b + c + d + e) / 5)); 321 | text("%"); 322 | newLine(); 323 | 324 | myBar(a, 30); 325 | myBar(b, 30); 326 | myBar(c, 30); 327 | myBar(d, 30); 328 | myBar(e, 30); 329 | 330 | if (sum < 500) { 331 | blinkText("WAIT..."); 332 | } else { 333 | text("ALL DONE!"); 334 | } 335 | } 336 | 337 | int main() 338 | { 339 | Scheduler::Start(interface); 340 | 341 | std::cout << std::endl; 342 | } 343 | 344 | // Output: 345 | 346 | /* 347 | / progress: 75% 348 | [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] done 349 | [━━━━━━━━━━━━━━━ ] 47% 350 | [━━━━━━━━━━━━━━━━━━━━━━━━ ] 77% 351 | [━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━] done 352 | [━━━━━━━━━━━━━━━━━ ] 55% 353 | WAIT... 354 | */ 355 | -------------------------------------------------------------------------------- /examples/rollback-state.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Track assignments, which can be later rolled back 6 | 7 | #include 8 | #include 9 | 10 | #include "cpp-effects/cpp-effects.h" 11 | 12 | namespace eff = cpp_effects; 13 | 14 | // -------------- 15 | // Internal stuff 16 | // -------------- 17 | 18 | struct AssignBase { 19 | virtual void undo() = 0; 20 | virtual ~AssignBase() { } 21 | }; 22 | 23 | template 24 | struct Assign : AssignBase { 25 | T& var; 26 | T val; 27 | Assign(T& x, T v) : var(x), val(x) { x = v; } 28 | virtual void undo() override { var = val; }; 29 | virtual ~Assign() { } 30 | }; 31 | 32 | struct AssignCmd : eff::command<> { 33 | AssignBase* asg; 34 | }; 35 | 36 | struct Rollback : eff::command<> { }; 37 | 38 | // ---------------------- 39 | // Programmer's interface 40 | // ---------------------- 41 | 42 | void rollback() 43 | { 44 | eff::invoke_command(Rollback{}); 45 | } 46 | 47 | class RollbackState : public eff::handler { 48 | bool handle_return() override 49 | { 50 | return true; 51 | } 52 | bool handle_command(Rollback, eff::resumption) override 53 | { 54 | return false; 55 | } 56 | bool handle_command(AssignCmd a, eff::resumption r) override 57 | { 58 | bool result = std::move(r).resume(); 59 | if (!result) { a.asg->undo(); } 60 | delete a.asg; 61 | return result; 62 | } 63 | }; 64 | 65 | // track is a syntactic trick that allows the programmer to "annotate" 66 | // an assignment that might be rolled back. 67 | 68 | template 69 | struct track { 70 | T& var; 71 | track(T& var) : var(var) { } 72 | void operator=(const T& val) { eff::invoke_command(AssignCmd{{}, new Assign(var, val)}); } 73 | }; 74 | 75 | // ------------------ 76 | // Particular example 77 | // ------------------ 78 | 79 | int main() 80 | { 81 | int x = 1; 82 | char c = 'a'; 83 | eff::handle([&]() { 84 | std::cout << "x = " << x << ", " << "c = " << c << std::endl; 85 | (track) x = 2; 86 | (track) c = 'b'; 87 | std::cout << "x = " << x << ", " << "c = " << c << std::endl; 88 | (track) x = 3; 89 | (track) c = 'c'; 90 | std::cout << "x = " << x << ", " << "c = " << c << std::endl; 91 | std::cout << "rolling back!" << std::endl; 92 | rollback(); 93 | (track) x = 4; 94 | (track) c = 'd'; 95 | }); 96 | std::cout << "x = " << x << ", " << "c = " << c << std::endl; 97 | 98 | // Output: 99 | // x = 1, c = a 100 | // x = 2, c = b 101 | // x = 3, c = c 102 | // rolling back! 103 | // x = 1, c = a 104 | } 105 | -------------------------------------------------------------------------------- /examples/shift0-reset.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: The shift0/reset operators via effect handlers 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "cpp-effects/cpp-effects.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | // ----------------------------- 17 | // Shift0 and reset via handlers 18 | // ----------------------------- 19 | 20 | template 21 | struct Shift0 : eff::command { 22 | std::function)> e; 23 | }; 24 | 25 | template 26 | class Reset : public eff::flat_handler> { 27 | Answer handle_command( 28 | Shift0 s, eff::resumption r) final override 29 | { 30 | return s.e(std::move(r)); 31 | } 32 | }; 33 | 34 | // -------------------- 35 | // Functional interface 36 | // -------------------- 37 | 38 | template 39 | Answer reset(std::function f) 40 | { 41 | return eff::handle>(f); 42 | } 43 | 44 | template 45 | Hole shift0(std::function)> e) 46 | { 47 | return eff::invoke_command(Shift0{{}, 48 | [=](eff::resumption k) -> Answer { 49 | return e([k = k.release()](Hole out) -> Answer { 50 | return eff::resumption(k).resume(out); 51 | }); 52 | } 53 | }); 54 | } 55 | 56 | // ------------------ 57 | // Particular example 58 | // ------------------ 59 | 60 | int main() 61 | { 62 | std::cout << 63 | reset([]() { 64 | return "2 + 2 = " + std::to_string( 65 | 2 + shift0([](auto k) { return "It is not true that " + k(3); })); 66 | }) << std::endl;; 67 | } 68 | -------------------------------------------------------------------------------- /examples/state.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | /* 6 | Example: Different forms of state: 7 | 8 | 1. Stateful Handler -- In this example state is kept as a member in 9 | the handler object. Commands change this state. 10 | 11 | 2. State using lambdas -- This is the usual way that state is 12 | presented using non-parameterised handlers in pure languages. The 13 | computation is interpreted as a function from the initial state to a 14 | value, while commands are interpreted as appropriate composition of 15 | these functions. 16 | 17 | 3. State using handler-switching -- This is a tricky implementation, 18 | in which the Get command is interpreted using a "reader" handler. The 19 | Set command is interpreted by switching the initial reader handler for 20 | a different reader handler that provides the new state. This switching 21 | is done by sandwiching the reader handlers in a pair of handlers: Aid 22 | (on the outside) and Abet (on the inside). The role of Abet is to 23 | capture the continuation without the reader's handler, and pass it to 24 | Aid, which throws away its continuation (together with the old 25 | reader), and handles (using the new reader) the resumed continuation 26 | caught by Abet. On top of this (or rather on the inside of this), we 27 | have the actual handler for Put and Get, which dispatches the commands 28 | to the current reader and Abet. 29 | */ 30 | 31 | #include 32 | 33 | #include "cpp-effects/cpp-effects.h" 34 | #include "cpp-effects/clause-modifiers.h" 35 | 36 | namespace eff = cpp_effects; 37 | 38 | // ----------------------------------- 39 | // Commands and programmer's interface 40 | // ----------------------------------- 41 | 42 | template 43 | struct Put : eff::command<> { 44 | S newState; 45 | }; 46 | 47 | template 48 | struct Get : eff::command { }; 49 | 50 | template 51 | void put(S s) { 52 | eff::invoke_command(Put{{}, s}); 53 | } 54 | 55 | template 56 | S get() { 57 | return eff::invoke_command(Get{}); 58 | } 59 | 60 | // ---------------------- 61 | // Particular computation 62 | // ---------------------- 63 | 64 | void test() 65 | { 66 | std::cout << get() << " "; 67 | put(get() + 1); 68 | std::cout << get() << " "; 69 | put(get() * get()); 70 | std::cout << get() << std::endl; 71 | } 72 | 73 | std::string test2() 74 | { 75 | std::cout << get() << " "; 76 | put(get() + 1); 77 | std::cout << get() << " "; 78 | put(get() * get()); 79 | std::cout << get() << std::endl; 80 | return "ok"; 81 | } 82 | 83 | // ------------------- 84 | // 1. Stateful handler 85 | // ------------------- 86 | 87 | template 88 | class HStateful : public eff::flat_handler>, eff::plain>> { 89 | public: 90 | HStateful(S initialState) : state(initialState) { } 91 | private: 92 | S state; 93 | void handle_command(Put p) final override 94 | { 95 | state = p.newState; 96 | } 97 | S handle_command(Get) final override 98 | { 99 | return state; 100 | } 101 | }; 102 | 103 | // Specialisation for Answer = void 104 | 105 | void testStateful() 106 | { 107 | eff::handle>(test, 100); 108 | std::cout << eff::handle>(test2, 100); 109 | std::cout << std::endl; 110 | 111 | // Output: 112 | // 100 101 10201 113 | // 100 101 10201 114 | // ok 115 | } 116 | 117 | // ---------------------- 118 | // 2. State using lambdas 119 | // ---------------------- 120 | 121 | template 122 | class HLambda : public eff::handler, Answer, Put, Get> { 123 | std::function handle_command(Put p, 124 | eff::resumption()> r) override 125 | { 126 | return [p, r = r.release()](S) -> Answer { 127 | return eff::resumption()>(r).resume()(p.newState); 128 | }; 129 | } 130 | std::function handle_command(Get, 131 | eff::resumption(S)> r) override 132 | { 133 | return [r = r.release()](S s) -> Answer { 134 | return eff::resumption(S)>(r).resume(s)(s); 135 | }; 136 | } 137 | std::function handle_return(Answer a) override 138 | { 139 | return [a](S){ return a; }; 140 | } 141 | }; 142 | 143 | template 144 | class HLambda : public eff::handler, void, Put, Get> { 145 | std::function handle_command(Put p, 146 | eff::resumption()> r) override 147 | { 148 | return [r = r.release(), p](S) -> void { 149 | eff::resumption()>(r).resume()(p.newState); 150 | }; 151 | } 152 | std::function handle_command(Get, 153 | eff::resumption(S)> r) override 154 | { 155 | return [r = r.release()](S s) -> void { 156 | eff::resumption(S)>(r).resume(s)(s); 157 | }; 158 | } 159 | std::function handle_return() override 160 | { 161 | return [](S){ }; 162 | } 163 | }; 164 | 165 | void testLambda() 166 | { 167 | eff::handle>(test)(100); 168 | std::cout << eff::handle>(test2)(100); 169 | std::cout << std::endl; 170 | 171 | // Output: 172 | // 100 101 10201 173 | // 100 101 10201 174 | // ok 175 | } 176 | 177 | // -------------------------------- 178 | // 3. State using handler-switching 179 | // -------------------------------- 180 | 181 | class Bottom { Bottom() = delete; }; 182 | 183 | template 184 | struct CmdAid : eff::command { 185 | std::shared_ptr han; 186 | eff::resumption_data* res; 187 | }; 188 | 189 | template 190 | struct CmdAbet : eff::command<> { 191 | std::shared_ptr han; 192 | }; 193 | 194 | template 195 | class Aid : public eff::handler> { 196 | typename H::answer_type handle_command(CmdAid c, eff::resumption) override { 197 | return eff::handle>([=](){ 198 | return eff::handle_with([=](){ 199 | return eff::resumption(c.res).resume(); 200 | }, c.han); 201 | }); 202 | } 203 | typename H::answer_type handle_return(typename H::answer_type a) override 204 | { 205 | return a; 206 | } 207 | }; 208 | 209 | template 210 | class Abet : public eff::handler> { 211 | [[noreturn]] typename H::body_type handle_command(CmdAbet c, eff::resumption r) override { 212 | eff::invoke_command(CmdAid{{}, c.han, r.release()}); 213 | exit(-1); // This will never be reached 214 | } 215 | typename H::body_type handle_return(typename H::body_type b) override 216 | { 217 | return b; 218 | } 219 | }; 220 | 221 | template 222 | typename H::answer_type SwappableHandleWith(std::function body, std::shared_ptr handler) 223 | { 224 | return eff::handle>([=](){ 225 | return eff::handle_with([=](){ 226 | return eff::handle>(body); 227 | }, 228 | std::move(handler)); 229 | }); 230 | } 231 | 232 | template 233 | struct Read : eff::command { }; 234 | 235 | template 236 | using ReaderType = eff::handler>; 237 | 238 | template 239 | class Reader : public ReaderType { 240 | public: 241 | Reader(R val) : val(val) { } 242 | private: 243 | const R val; // Note the const modifier! 244 | Answer handle_command(Read, eff::resumption r) override 245 | { 246 | return std::move(r).tail_resume(val); 247 | } 248 | Answer handle_return(Answer b) override 249 | { 250 | return b; 251 | } 252 | }; 253 | 254 | template 255 | class HSwitching : public eff::handler, Get> { 256 | Answer handle_command(Put p, eff::resumption r) override 257 | { 258 | eff::invoke_command( 259 | CmdAbet>{{}, std::make_shared>(p.newState)}); 260 | return std::move(r).resume(); 261 | } 262 | Answer handle_command(Get, eff::resumption r) override 263 | { 264 | return std::move(r).resume(eff::invoke_command(Read{})); 265 | } 266 | Answer handle_return(Answer a) override 267 | { 268 | return a; 269 | } 270 | }; 271 | 272 | // TODO: Overloads for Answer = void 273 | 274 | void testSwitching() 275 | { 276 | std::cout << SwappableHandleWith( 277 | [](){ return eff::handle>(test2); }, 278 | std::shared_ptr>(new Reader(100))); 279 | 280 | std::cout << std::endl; 281 | 282 | // Output: 283 | // 100 101 10201 284 | // ok 285 | } 286 | 287 | // --------- 288 | // Run tests 289 | // --------- 290 | 291 | int main() 292 | { 293 | testStateful(); 294 | testLambda(); 295 | testSwitching(); 296 | } 297 | -------------------------------------------------------------------------------- /examples/threads.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Example: Lightweight threads with global round-robin scheduler 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "cpp-effects/cpp-effects.h" 15 | //#include "cpp-effects/clause-modifiers.h" 16 | 17 | namespace eff = cpp_effects; 18 | 19 | // ---------------------------------------- 20 | // Effect intarface for lightweight threads 21 | // ---------------------------------------- 22 | 23 | struct Yield : eff::command<> { }; 24 | 25 | struct Fork : eff::command<> { 26 | std::function proc; 27 | }; 28 | 29 | struct Kill : eff::command<> { }; 30 | 31 | void yield() 32 | { 33 | eff::invoke_command(Yield{}); 34 | } 35 | 36 | void fork(std::function proc) 37 | { 38 | eff::invoke_command(Fork{{}, proc}); 39 | } 40 | 41 | void kill() 42 | { 43 | eff::invoke_command(Kill{}); 44 | } 45 | 46 | // --------------------- 47 | // Scheduler for threads 48 | // --------------------- 49 | 50 | using Res = eff::resumption; 51 | 52 | class Scheduler : public eff::handler { 53 | public: 54 | static void Start(std::function f) 55 | { 56 | queue.push_back(eff::wrap(f)); 57 | 58 | while (!queue.empty()) { 59 | auto resumption = std::move(queue.front()); 60 | queue.pop_front(); 61 | std::move(resumption).resume(); 62 | } 63 | } 64 | private: 65 | static std::list queue; 66 | 67 | void handle_command(Yield, Res r) override { 68 | queue.push_back(std::move(r)); 69 | } 70 | 71 | void handle_command(Fork f, Res r) override { 72 | queue.push_back(std::move(r)); 73 | queue.push_back(eff::wrap(f.proc)); 74 | } 75 | 76 | void handle_command(Kill, Res) override { } 77 | 78 | void handle_return() override { } 79 | }; 80 | 81 | std::list Scheduler::queue; 82 | 83 | // ------------------ 84 | // Particular example 85 | // ------------------ 86 | 87 | void worker(int k) 88 | { 89 | for (int i = 0; i < 10; ++i) { 90 | std::cout << k; 91 | yield(); 92 | } 93 | } 94 | 95 | void starter() 96 | { 97 | for (int i = 0; i < 5; ++i) { 98 | fork([=](){ worker(i); }); 99 | } 100 | } 101 | 102 | int main() 103 | { 104 | Scheduler::Start(starter); 105 | 106 | std::cout << std::endl; 107 | 108 | // Output: 109 | // 01021032104321043210432104321043210432104321432434 110 | } 111 | 112 | // ----- 113 | // Notes 114 | // ----- 115 | 116 | /* 117 | We can implement randomised scheduleing e.g. like this: 118 | 119 | static void Start(std::function f) 120 | { 121 | Run(f); 122 | while (!queue.empty()) { // Randomised scheduling 123 | auto it = queue.begin(); 124 | std::advance(it, rand() % queue.size()); 125 | auto resumption = std::move(*it); 126 | queue.erase(it); 127 | OneShot::Resume(std::move(resumption)); 128 | } 129 | } 130 | 131 | In which case the output is something like: 132 | 00001002134110400012112432132113322433334244424234 133 | */ 134 | -------------------------------------------------------------------------------- /include/cpp-effects/clause-modifiers.h: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // This file contains modifiers that force specific shapes of command 6 | // clauses in handlers, which is useful for readability (e.g., we can 7 | // read from the type that we will not use the resumption) and 8 | // performance (e.g., there is no need to allocate the resumption). 9 | // 10 | // Each modifier consists of a template, which marks a command in the 11 | // type of the handler, and a specialisation of the CommandClause 12 | // class that modifies the type of the clause in the definition of the 13 | // handler. For example, we can specify that a particular handler will 14 | // not need the resumption: 15 | // 16 | // struct MyCmd : command { }; 17 | // struct OtherCmd : command<> { }; 18 | // class MyHandler : public handler>, OtherCmd> { 19 | // char handle_command(MyCmd) override { ... } 20 | // }; 21 | // 22 | // Note that because we used the no_resume modifier, the type of 23 | // handle_command for MyCmd is now different. 24 | 25 | #ifndef CPP_EFFECTS_CLAUSE_MODIFIERS_H 26 | #define CPP_EFFECTS_CLAUSE_MODIFIERS_H 27 | 28 | #include "cpp-effects/cpp-effects.h" 29 | 30 | namespace cpp_effects { 31 | 32 | // ----- 33 | // plain 34 | // ----- 35 | 36 | // Specialisation for plain clauses, which interpret commands as 37 | // functions (i.e., they are self- and tail-resumptive). No context 38 | // switching, no allocation of resumption. 39 | 40 | template 41 | struct plain { }; 42 | 43 | namespace cpp_effects_internals { 44 | 45 | template 46 | class command_clause> : public can_invoke_command { 47 | template friend class ::cpp_effects::handler; 48 | template friend class ::cpp_effects::flat_handler; 49 | protected: 50 | virtual typename Cmd::out_type handle_command(Cmd) = 0; 51 | public: 52 | virtual typename Cmd::out_type invoke_command( 53 | std::list::iterator it, const Cmd& cmd) final override 54 | { 55 | // (continued from OneShot::InvokeCmd) ...looking for [d] 56 | std::list stored_metastack; 57 | stored_metastack.splice( 58 | stored_metastack.begin(), metastack, metastack.begin(), it); 59 | // at this point: metastack = [a][b][c]; stored stack = [d][e][f][g.] 60 | std::swap(stored_metastack.front()->fiber, metastack.front()->fiber); 61 | // at this point: metastack = [a][b][c.]; stored stack = [d][e][f][g] 62 | 63 | if constexpr (!std::is_void::value) { 64 | typename Cmd::out_type a(handle_command(cmd)); 65 | std::swap(stored_metastack.front()->fiber, metastack.front()->fiber); 66 | // at this point: metastack = [a][b][c]; stored stack = [d][e][f][g.] 67 | metastack.splice(metastack.begin(), stored_metastack); 68 | // at this point: metastack = [a][b][c][d][e][f][g.] 69 | return a; 70 | } else { 71 | handle_command(cmd); 72 | std::swap(stored_metastack.front()->fiber, metastack.front()->fiber); 73 | metastack.splice(metastack.begin(), stored_metastack); 74 | } 75 | } 76 | }; 77 | 78 | } // namespace cpp_effects_internals 79 | 80 | // --------- 81 | // no_resume 82 | // --------- 83 | 84 | // Specialisation for command clauses that do not use the 85 | // resumption. This is useful for handlers that behave like exception 86 | // handlers or terminate the "current thread". 87 | 88 | template 89 | struct no_resume { }; 90 | 91 | namespace cpp_effects_internals { 92 | 93 | template 94 | class command_clause> : public can_invoke_command { 95 | template friend class ::cpp_effects::handler; 96 | template friend class ::cpp_effects::flat_handler; 97 | protected: 98 | virtual Answer handle_command(Cmd) = 0; 99 | public: 100 | [[noreturn]] virtual typename Cmd::out_type invoke_command( 101 | std::list::iterator it, const Cmd& cmd) final override 102 | { 103 | // (continued from OneShot::InvokeCmd) ...looking for [d] 104 | metastack.erase(metastack.begin(), it); 105 | // at this point: metastack = [a][b][c] 106 | 107 | std::move(metastack.front()->fiber).resume_with([&](ctx::fiber&& /*prev*/) -> ctx::fiber { 108 | if constexpr (!std::is_void::value) { 109 | *(static_cast*>(metastack.front()->return_buffer)) = 110 | this->handle_command(cmd); 111 | } else { 112 | this->handle_command(cmd); 113 | } 114 | return ctx::fiber(); 115 | }); 116 | 117 | // The current fiber is gone (because prev in the above goes out 118 | // of scope and is deleted), so this will never be reached. 119 | std::cerr << "Malformed no_resume handler" << std::endl; 120 | debug_print_metastack(); 121 | exit(-1); 122 | } 123 | }; 124 | 125 | } // namespace cpp_effects_internals 126 | 127 | // --------- 128 | // no_manage 129 | // --------- 130 | 131 | // Specialisation for handlers that either: 132 | // 133 | // - Don't expose the resumption (i.e., all resumes happen within 134 | // command clauses), 135 | // - Don't access the handler object after resume, 136 | // 137 | // which amounts to almost all practical uses of handlers. "no_manage" 138 | // clause does not participate in the reference counting of handlers, 139 | // saving a tiny bit of performance. The interface is exactly as in a 140 | // regular command clause. 141 | // 142 | // Plain and no_resume clauses are automaticslly no_manage. 143 | 144 | template 145 | struct no_manage { }; 146 | 147 | namespace cpp_effects_internals { 148 | 149 | template 150 | class command_clause> : public can_invoke_command { 151 | template friend class ::cpp_effects::handler; 152 | template friend class ::cpp_effects::flat_handler; 153 | protected: 154 | virtual Answer handle_command(Cmd, ::cpp_effects::resumption>) = 0; 155 | public: 156 | virtual typename Cmd::out_type invoke_command( 157 | std::list::iterator it, const Cmd& cmd) final override 158 | { 159 | using Out = typename Cmd::out_type; 160 | 161 | // (continued from OneShot::InvokeCmd) ...looking for [d] 162 | resumption_data& resumption = this->resumptionBuffer; 163 | resumption.stored_metastack.splice( 164 | resumption.stored_metastack.begin(), metastack, metastack.begin(), it); 165 | // at this point: [a][b][c]; stored stack = [d][e][f][g.] 166 | 167 | std::move(metastack.front()->fiber).resume_with([&](ctx::fiber&& prev) -> 168 | ctx::fiber { 169 | // at this point: [a][b][c.]; stored stack = [d][e][f][g.] 170 | resumption.stored_metastack.front()->fiber = std::move(prev); 171 | // at this point: [a][b][c.]; stored stack = [d][e][f][g] 172 | 173 | // We don't need to keep the handler alive for the duration of the command clause call 174 | // (compare command_clause::InvokeCmd) 175 | 176 | if constexpr (!std::is_void::value) { 177 | *(static_cast*>(metastack.front()->return_buffer)) = 178 | this->handle_command(cmd, ::cpp_effects::resumption>(resumption)); 179 | } else { 180 | this->handle_command(cmd, ::cpp_effects::resumption>(resumption)); 181 | } 182 | return ctx::fiber(); 183 | }); 184 | 185 | // If the control reaches here, this means that the resumption is 186 | // being resumed at the moment, and so we no longer need the 187 | // resumption object. 188 | if constexpr (!std::is_void::value) { 189 | Out cmdResult = std::move(resumption.command_result_buffer->value); 190 | resumption.stored_metastack.clear(); 191 | resumption.command_result_buffer = {}; 192 | return cmdResult; 193 | } else { 194 | resumption.stored_metastack.clear(); 195 | } 196 | } 197 | resumption_data resumptionBuffer; 198 | }; 199 | 200 | } // namespace cpp_effects_internals 201 | 202 | } // namespace CppEffects 203 | 204 | #endif // CPP_EFFECTS_CLAUSE_MODIFIERS_H 205 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin/test) 2 | 3 | add_executable (traits traits.cpp) 4 | add_executable (command-lifetime command-lifetime.cpp) 5 | add_executable (handler-lifetime handler-lifetime.cpp) 6 | add_executable (cut-out-the-middleman cut-out-the-middleman.cpp) 7 | add_executable (swap-handler swap-handler.cpp) 8 | add_executable (global-from-handle global-from-handle.cpp) 9 | add_executable (handlers-with-labels handlers-with-labels.cpp) 10 | add_executable (plain-handler plain-handler.cpp) 11 | add_executable (handler-noresume handler-noresume.cpp) 12 | -------------------------------------------------------------------------------- /test/command-lifetime.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: We print out the lifecycle of a command to see that we have 6 | // to copy it inside a command clause. 7 | 8 | #include 9 | #include 10 | 11 | #include "cpp-effects/cpp-effects.h" 12 | 13 | namespace eff = cpp_effects; 14 | 15 | class Cmd : public eff::command<> { 16 | public: 17 | int id; 18 | Cmd() : id(rand() % 1000) { std::cout << id << " born" << std::endl; } 19 | ~Cmd() { std::cout << id << " dead" << std::endl; } 20 | Cmd(const Cmd& cmd) 21 | { 22 | id = rand() % 1000; 23 | std::cout << id << " born (copied from " << cmd.id << ")" << std::endl; 24 | } 25 | Cmd(Cmd&& cmd) 26 | { 27 | id = rand() % 1000; 28 | std::cout << id << "born (moved from " << cmd.id << ")" << std::endl; 29 | } 30 | Cmd& operator=(const Cmd&) = default; 31 | Cmd& operator=(Cmd&&) = default; 32 | }; 33 | 34 | class MyHandler : public eff::handler { 35 | void handle_return() override { } 36 | void handle_command(Cmd c, eff::resumption r) override 37 | { 38 | std::cout << "In handler: received command id = " << c.id << std::endl; 39 | std::move(r).resume(); 40 | std::cout << "In handler: still using command id = " << c.id << std::endl; 41 | } 42 | }; 43 | 44 | void myComputation() 45 | { 46 | std::cout << "In computation: Creating a command..." << std::endl; 47 | { 48 | Cmd cmd; 49 | eff::invoke_command(cmd); 50 | } 51 | std::cout << "In computation: The command is gone..." << std::endl; 52 | } 53 | 54 | int main() 55 | { 56 | std::cout << "--- command-lifetime ---" << std::endl; 57 | eff::handle(myComputation); 58 | } 59 | 60 | // Output: 61 | // In computation: Creating a command... 62 | // 807 born 63 | // 249 born (copied from 807) 64 | // In handler: received command id = 249 65 | // 807 dead 66 | // In computation: The command is gone... 67 | // In handler: still using command id = 249 68 | // 249 dead 69 | 70 | // Note how catastrophic pass-by-reference would be in this example! 71 | -------------------------------------------------------------------------------- /test/cut-out-the-middleman.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: Computations taken out of their original context: 6 | // 7 | // 1. We use two nested handlers, and at some point remove the outer 8 | // one. 9 | // 2. We handle a computation, but read the final result elsewhere. 10 | 11 | #include 12 | #include 13 | 14 | #include "cpp-effects/cpp-effects.h" 15 | 16 | namespace eff = cpp_effects; 17 | 18 | // -- 19 | // 1. 20 | // -- 21 | 22 | struct PingOuter : eff::command<> { }; 23 | 24 | struct PingInner : eff::command<> { }; 25 | 26 | struct CutMiddlemanAid : eff::command<> { }; 27 | 28 | struct CutMiddlemanAbet : eff::command<> { 29 | eff::resumption_data* res; 30 | }; 31 | 32 | class HInner : public eff::handler { 33 | void handle_command(PingInner, eff::resumption r) override 34 | { 35 | std::cout << "Inner!" << std::endl; 36 | std::move(r).tail_resume(); 37 | } 38 | void handle_command(CutMiddlemanAid, eff::resumption r) override 39 | { 40 | eff::invoke_command(CutMiddlemanAbet{{}, r.release()}); 41 | } 42 | void handle_return() override { } 43 | }; 44 | 45 | class HOuter : public eff::handler { 46 | void handle_command(PingOuter, eff::resumption r) override 47 | { 48 | std::cout << "Outer!" << std::endl; 49 | std::move(r).tail_resume(); 50 | } 51 | void handle_command(CutMiddlemanAbet a, eff::resumption) override 52 | { 53 | eff::resumption(a.res).tail_resume(); 54 | } 55 | 56 | void handle_return() override { } 57 | }; 58 | 59 | void test1() 60 | { 61 | std::cout << "A+" << std::endl; 62 | eff::handle([](){ 63 | std::cout << "B+" << std::endl; 64 | eff::handle([&](){ 65 | std::cout << "C+" << std::endl; 66 | eff::invoke_command(PingOuter{}); 67 | eff::invoke_command(PingInner{}); 68 | eff::invoke_command(CutMiddlemanAid{}); 69 | // eff::invoke_command(PingOuter{}); // bad idea! 70 | eff::invoke_command(PingInner{}); 71 | std::cout << "C-" << std::endl; 72 | }); 73 | std::cout << "B-" << std::endl; 74 | }); 75 | std::cout << "A-" << std::endl; 76 | } 77 | 78 | // Output: 79 | // A+ 80 | // B+ 81 | // C+ 82 | // Outer! 83 | // Inner! 84 | // Inner! 85 | // C- 86 | // A- 87 | 88 | // ---------------------------------------------------------------- 89 | 90 | // -- 91 | // 2. 92 | // -- 93 | 94 | eff::resumption_data* Res = nullptr; 95 | 96 | struct Inc : eff::command<> { }; 97 | 98 | struct Break : eff::command<> { }; 99 | 100 | void inc() 101 | { 102 | eff::invoke_command(Inc{}); 103 | } 104 | void break_() 105 | { 106 | eff::invoke_command(Break{}); 107 | } 108 | int resume() 109 | { 110 | return eff::resumption(Res).resume(); 111 | } 112 | 113 | class HIP : public eff::handler { 114 | int handle_command(Break, eff::resumption r) override 115 | { 116 | Res = r.release(); 117 | return 0; 118 | } 119 | int handle_command(Inc, eff::resumption r) override 120 | { 121 | return std::move(r).resume() + 1; 122 | } 123 | int handle_return(int v) override { return v; } 124 | }; 125 | 126 | int comp() 127 | { 128 | inc(); 129 | inc(); 130 | break_(); 131 | inc(); 132 | break_(); 133 | inc(); 134 | return 100; 135 | } 136 | 137 | void test2() 138 | { 139 | std::cout << eff::handle(comp) << std::endl; 140 | std::cout << resume() << std::endl; 141 | std::cout << resume() << std::endl; 142 | } 143 | 144 | // Output: 145 | // 2 146 | // 1 147 | // 101 148 | 149 | // ---------------------------------------------------------------- 150 | 151 | int main() 152 | { 153 | std::cout << "--- cut-out-the-middleman ---" << std::endl; 154 | test1(); 155 | std::cout << "***" << std::endl; 156 | test2(); 157 | } 158 | -------------------------------------------------------------------------------- /test/global-from-handle.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: Handlers can be used to define global values 6 | 7 | #include 8 | #include 9 | 10 | #include "cpp-effects/cpp-effects.h" 11 | 12 | namespace eff = cpp_effects; 13 | 14 | struct Break : eff::command<> { }; 15 | 16 | class HH : public eff::handler { 17 | int handle_command(Break, eff::resumption) override { 18 | return 100; 19 | } 20 | int handle_return() override { 21 | return 10; 22 | } 23 | }; 24 | 25 | int g = eff::handle([](){ eff::invoke_command(Break{}); }); 26 | 27 | int main() 28 | { 29 | std::cout << "--- global-from-handle ---" << std::endl; 30 | std::cout << g << std::endl; 31 | } 32 | -------------------------------------------------------------------------------- /test/handler-lifetime.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: We print out the lifecycle of a handler. 6 | 7 | #include 8 | #include 9 | 10 | #include "cpp-effects/cpp-effects.h" 11 | 12 | namespace eff = cpp_effects; 13 | 14 | // ------------------------------------------------------------------- 15 | // In the first example, the handler object needs to survive after the 16 | // return clause 17 | // ------------------------------------------------------------------- 18 | 19 | struct Cmd : public eff::command<> { }; 20 | 21 | class MyHandler : public eff::handler { 22 | public: 23 | ~MyHandler() { msg = "dead"; std::cout << "The handler is dead!" << std::endl; } 24 | private: 25 | std::string msg = "alive"; 26 | void PrintStatus() { std::cout << "I'm " << this->msg << "!" << std::endl; } 27 | void handle_return() override { } 28 | void handle_command(Cmd, eff::resumption r) override 29 | { 30 | this->PrintStatus(); 31 | std::move(r).resume(); 32 | this->PrintStatus(); // Do I still exist? 33 | } 34 | }; 35 | 36 | void afterReturn() 37 | { 38 | std::cout << "Good morning!" << std::endl; 39 | eff::invoke_command(Cmd{}); 40 | std::cout << "How are you?" << std::endl; 41 | eff::invoke_command(Cmd{}); 42 | std::cout << "Cheers!" << std::endl; 43 | } 44 | 45 | // ----------------------------------------------------------- 46 | // In the second example, the HandleWith context dies, but the 47 | // resumption lives on 48 | // ----------------------------------------------------------- 49 | 50 | struct OtherCmd : eff::command<> { }; 51 | 52 | eff::resumption res; 53 | 54 | class EscapeHandler : public eff::handler { 55 | public: 56 | ~EscapeHandler() { std::cout << "The handler is dead!" << std::endl; } 57 | private: 58 | void handle_command(Cmd, eff::resumption r) 59 | { 60 | res = std::move(r); 61 | std::cout << "Must give us pause!" << std::endl; 62 | } 63 | void handle_command(OtherCmd, eff::resumption r) 64 | { 65 | std::cout << "[[This is other cmd]]" << std::endl; 66 | std::move(r).tail_resume(); 67 | //std::cout << "[[This is other cmd]]" << std::endl; 68 | } 69 | void handle_return() { std::cout << "Thanks!" << std::endl; } 70 | }; 71 | 72 | void resumptionEscape() 73 | { 74 | eff::handle([](){ 75 | std::cout << "To be" << std::endl; 76 | eff::invoke_command(Cmd{}); 77 | std::cout << "Or not to be" << std::endl; 78 | eff::invoke_command(OtherCmd{}); 79 | eff::invoke_command(OtherCmd{}); 80 | eff::invoke_command(OtherCmd{}); 81 | std::cout << "??" << std::endl; 82 | }); 83 | 84 | std::cout << "[A short break]" << std::endl; 85 | 86 | std::move(res).resume(); 87 | } 88 | 89 | // ---- 90 | // Main 91 | // ---- 92 | 93 | int main() 94 | { 95 | std::cout << "--- handler-lifetime ---" << std::endl; 96 | std::cout << "** handler exists after return clause **" << std::endl; 97 | eff::handle(afterReturn); 98 | std::cout << " (expected:" << std::endl 99 | << " Good morning!" << std::endl 100 | << " I'm alive!" << std::endl 101 | << " How are you?" << std::endl 102 | << " I'm alive!" << std::endl 103 | << " Cheers!" << std::endl 104 | << " I'm alive!" << std::endl 105 | << " I'm alive!" << std::endl 106 | << " The handler is dead!)" << std::endl; 107 | 108 | std::cout << "** handler exists after HandleWith returned **" << std::endl; 109 | resumptionEscape(); 110 | std::cout << " (expected:" << std::endl 111 | << " To be" << std::endl 112 | << " Must give us pause!" << std::endl 113 | << " [A short break]" << std::endl 114 | << " Or not to be" << std::endl 115 | << " Thanks!" << std::endl 116 | << " The handler is dead!)" << std::endl; 117 | } 118 | -------------------------------------------------------------------------------- /test/handler-noresume.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: no_resume command clauses 6 | 7 | #include 8 | #include 9 | 10 | #include "cpp-effects/cpp-effects.h" 11 | #include "cpp-effects/clause-modifiers.h" 12 | 13 | namespace eff = cpp_effects; 14 | 15 | struct Print : eff::command<> { }; 16 | 17 | class Printer : public eff::handler { 18 | public: 19 | Printer(const std::string& msg) : msg(msg) { } 20 | private: 21 | std::string msg; 22 | int handle_command(Print, eff::resumption r) override 23 | { 24 | std::cout << msg << std::flush; 25 | return std::move(r).resume(); 26 | } 27 | int handle_return(int a) override { return a + 1; } 28 | }; 29 | 30 | struct Error : eff::command<> { }; 31 | 32 | class Catch : public eff::handler> { 33 | int handle_command(Error) override 34 | { 35 | std::cout << "[caught]" << std::flush; 36 | return 42; 37 | } 38 | int handle_return(int a) override { return a + 100; } 39 | }; 40 | 41 | void test() 42 | { 43 | std::cout << 44 | eff::handle_with([](){ 45 | int i = 46 | eff::handle_with([](){ 47 | eff::handle_with([](){ 48 | eff::invoke_command(Print{}); 49 | eff::invoke_command(Print{}); 50 | eff::invoke_command(Error{}); 51 | eff::invoke_command(Print{}); 52 | eff::invoke_command(Print{}); 53 | return 100; 54 | }, std::make_shared("[in]")); eff::invoke_command(Print{}); return 2; 55 | }, std::make_shared()); eff::invoke_command(Print{}); return i; 56 | }, std::make_shared("[out]")); 57 | std::cout << "\t(expected: [in][in][caught][out]43)" << std::endl; 58 | } 59 | 60 | // --------------------------------- 61 | // Example from the reference manual 62 | // --------------------------------- 63 | 64 | class Cancel : public eff::handler> { 65 | void handle_command(Error) override 66 | { 67 | std::cout << "Error!" << std::endl; 68 | } 69 | void handle_return() override {} 70 | }; 71 | 72 | void testError() 73 | { 74 | std::cout << "Welcome!" << std::endl; 75 | eff::handle([](){ 76 | std::cout << "So far so good..." << std::endl; 77 | eff::invoke_command(Error{}); 78 | std::cout << "I made it!" << std::endl; 79 | }); 80 | std::cout << "Bye!" << std::endl; 81 | } 82 | 83 | 84 | // Output: 85 | // Welcome! 86 | // So far so good... 87 | // Error! 88 | // Bye! 89 | 90 | // ---- 91 | // Main 92 | // ---- 93 | 94 | int main() 95 | { 96 | std::cout << "--- noresume-handler ---" << std::endl; 97 | test(); 98 | testError(); 99 | } 100 | -------------------------------------------------------------------------------- /test/handlers-with-labels.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: Swapping handlers 6 | 7 | #include 8 | #include 9 | 10 | #include "cpp-effects/cpp-effects.h" 11 | 12 | namespace eff = cpp_effects; 13 | 14 | template 15 | struct Read : eff::command { }; 16 | 17 | template 18 | using ReaderType = eff::handler>; 19 | 20 | template 21 | class Reader : public eff::handler> { 22 | public: 23 | Reader(R val) : val(val) { } 24 | private: 25 | const R val; 26 | void handle_command(Read, eff::resumption r) override 27 | { 28 | std::move(r).tail_resume(val); 29 | } 30 | void handle_return() override { } 31 | }; 32 | 33 | void rec(int64_t n) 34 | { 35 | if (n > 0) { 36 | eff::handle>(n, std::bind(rec, n - 1), n); 37 | } else { 38 | for (int64_t i = 0; i < 300000; i += 767) { 39 | std::cout << eff::invoke_command(i % 999 + 1, Read{}) << " "; 40 | } 41 | } 42 | } 43 | 44 | int main() 45 | { 46 | std::cout << "--- handlers-with-labels ---" << std::endl; 47 | rec(1000); 48 | std::cout << std::endl; 49 | } 50 | -------------------------------------------------------------------------------- /test/plain-handler.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: Plain command clauses 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cpp-effects/cpp-effects.h" 12 | #include "cpp-effects/clause-modifiers.h" 13 | 14 | namespace eff = cpp_effects; 15 | 16 | // ----------------------------------- 17 | // Commands and programmer's interface 18 | // ----------------------------------- 19 | 20 | template 21 | struct Put : eff::command<> { 22 | S newState; 23 | }; 24 | 25 | template 26 | struct Get : eff::command { }; 27 | 28 | template 29 | void put(S s) { 30 | eff::invoke_command(Put{{}, s}); 31 | } 32 | 33 | template 34 | S get() { 35 | return eff::invoke_command(Get{}); 36 | } 37 | 38 | // ---------------------- 39 | // Particular computation 40 | // ---------------------- 41 | 42 | void test() 43 | { 44 | std::cout << get() << " "; 45 | put(get() + 1); 46 | std::cout << get() << " "; 47 | put(get() * get()); 48 | std::cout << get() << std::endl; 49 | } 50 | 51 | std::string test2() 52 | { 53 | std::cout << get() << " "; 54 | put(get() + 1); 55 | std::cout << get() << " "; 56 | put(get() * get()); 57 | std::cout << get() << std::endl; 58 | return "ok"; 59 | } 60 | 61 | // ------------------- 62 | // 1. Stateful handler 63 | // ------------------- 64 | 65 | template 66 | class HStateful : public eff::handler>, eff::plain>> { 67 | public: 68 | HStateful(S initialState) : state(initialState) { } 69 | private: 70 | S state; 71 | void handle_command(Put p) override 72 | { 73 | state = p.newState; 74 | } 75 | S handle_command(Get) override 76 | { 77 | return state; 78 | } 79 | Answer handle_return(Answer a) override { return a; } 80 | }; 81 | 82 | // Specialisation for Answer = void 83 | 84 | template 85 | class HStateful : public eff::handler>, eff::plain>> { 86 | public: 87 | HStateful(S initialState) : state(initialState) { } 88 | private: 89 | S state; 90 | void handle_command(Put p) override 91 | { 92 | state = p.newState; 93 | } 94 | S handle_command(Get) override 95 | { 96 | return state; 97 | } 98 | void handle_return() override { } 99 | }; 100 | 101 | void testStateful() 102 | { 103 | eff::handle>(test, 100); 104 | std::cout << eff::handle>(test2, 100); 105 | std::cout << std::endl; 106 | 107 | // Output: 108 | // 100 101 10201 109 | // 100 101 10201 110 | // ok 111 | } 112 | 113 | // ------------------------------------------------------- 114 | 115 | struct Do : eff::command<> { }; 116 | 117 | class Bracket : public eff::handler { 118 | public: 119 | Bracket(const std::string& msg) : msg(msg) { } 120 | private: 121 | std::string msg; 122 | void handle_command(Do, eff::resumption r) override 123 | { 124 | std::string lmsg = this->msg; 125 | std::cout << lmsg << "+" << std::flush; 126 | std::move(r).resume(); 127 | std::cout << lmsg << "-" << std::flush; 128 | } 129 | void handle_return() override { } 130 | }; 131 | 132 | struct Print : eff::command<> { }; 133 | 134 | class Printer : public eff::handler> { 135 | public: 136 | Printer(const std::string& msg) : msg(msg) { } 137 | private: 138 | std::string msg; 139 | void handle_command(Print) override 140 | { 141 | std::string lmsg = this->msg; 142 | std::cout << lmsg << "+" << std::flush; 143 | eff::invoke_command(Do{}); 144 | std::cout << lmsg << "-" << std::flush; 145 | } 146 | void handle_return() override { } 147 | }; 148 | 149 | void sandwichComp() 150 | { 151 | eff::invoke_command(Do{}); 152 | eff::invoke_command(Print{}); 153 | } 154 | 155 | void testSandwich() 156 | { 157 | eff::handle_with([](){ 158 | eff::handle_with([](){ 159 | eff::handle_with([](){ 160 | sandwichComp(); 161 | }, std::make_shared("[in]")); 162 | }, std::make_shared("[print]")); 163 | }, std::make_shared("[out]")); 164 | std::cout << std::endl; 165 | } 166 | 167 | // --------------------------------- 168 | // Example from the reference manual 169 | // --------------------------------- 170 | 171 | struct Add : eff::command { 172 | int x, y; 173 | }; 174 | 175 | template 176 | class Calculator : public eff::handler> { 177 | int handle_command(Add c) override { 178 | return c.x + c.y; 179 | } 180 | T handle_return(T s) override { 181 | return s; 182 | } 183 | }; 184 | 185 | void testCalc() 186 | { 187 | eff::handle>([]() -> std::monostate { 188 | std::cout << "2 + 5 = " << eff::invoke_command({{}, 2, 5}) << std::endl; 189 | std::cout << "11 + 3 = " << eff::invoke_command({{}, 11, 3}) << std::endl; 190 | return {}; 191 | }); 192 | } 193 | 194 | // ----------------- 195 | // The main function 196 | // ----------------- 197 | 198 | int main() 199 | { 200 | std::cout << "--- plain-handler ---" << std::endl; 201 | testStateful(); 202 | testSandwich(); 203 | testCalc(); 204 | } 205 | -------------------------------------------------------------------------------- /test/swap-handler.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | // Test: Swapping handlers 6 | 7 | #include 8 | #include 9 | 10 | #include "cpp-effects/cpp-effects.h" 11 | 12 | namespace eff = cpp_effects; 13 | 14 | class Bottom { Bottom() = delete; }; 15 | 16 | template 17 | struct CmdAid : eff::command { 18 | std::shared_ptr han; 19 | eff::resumption_data* res; 20 | }; 21 | 22 | template 23 | struct CmdAbet : eff::command<> { 24 | std::shared_ptr han; 25 | }; 26 | 27 | template 28 | class Aid : public eff::handler> { 29 | typename H::answer_type handle_command(CmdAid c, eff::resumption) override { 30 | return eff::handle>([=](){ 31 | return eff::handle_with([=](){ 32 | return eff::resumption(c.res).resume(); }, 33 | c.han); 34 | }); 35 | } 36 | typename H::answer_type handle_return(typename H::answer_type a) override 37 | { 38 | return a; 39 | } 40 | }; 41 | 42 | template 43 | class Abet : public eff::handler> { 44 | [[noreturn]] typename H::body_type handle_command(CmdAbet c, 45 | eff::resumption r) override 46 | { 47 | eff::invoke_command(CmdAid{{}, c.han, r.release()}); 48 | exit(-1); // This will never be reached 49 | } 50 | typename H::body_type handle_return(typename H::body_type b) override 51 | { 52 | return b; 53 | } 54 | }; 55 | 56 | template 57 | typename H::answer_type SwappableHandleWith(std::function body, std::shared_ptr handler) 58 | { 59 | return eff::handle>([=](){ 60 | return eff::handle_with([=](){ 61 | return eff::handle>(body); 62 | }, 63 | std::move(handler)); 64 | }); 65 | } 66 | 67 | template 68 | struct Read : eff::command { }; 69 | 70 | template 71 | using ReaderType = eff::handler>; 72 | 73 | template 74 | class Reader : public ReaderType { 75 | public: 76 | Reader(R val) : val(val) { } 77 | private: 78 | const R val; 79 | Answer handle_command(Read, eff::resumption r) override 80 | { 81 | return std::move(r).resume(val); 82 | } 83 | Answer handle_return(Answer b) override 84 | { 85 | return b; 86 | } 87 | }; 88 | 89 | void put(int a) 90 | { 91 | eff::invoke_command(CmdAbet>{{}, std::make_shared>(a)}); 92 | } 93 | int get() 94 | { 95 | return eff::invoke_command(Read{}); 96 | } 97 | 98 | int comp() 99 | { 100 | std::cout << get() << std::endl; 101 | put(get() + 10); 102 | std::cout << get() << std::endl; 103 | put(200); 104 | put(300); 105 | put(get() + 10); 106 | std::cout << get() << std::endl; 107 | std::cout << get() << std::endl; 108 | put(get() + 10); 109 | std::cout << get() << std::endl; 110 | return 18; 111 | } 112 | 113 | int main() 114 | { 115 | std::cout << "--- swap-handler ---" << std::endl; 116 | std::cout << 117 | SwappableHandleWith(comp, std::shared_ptr>(new Reader(100))) 118 | << std::endl; 119 | 120 | } 121 | 122 | // Output: 123 | // 100 124 | // 110 125 | // 310 126 | // 310 127 | // 320 128 | // 18 129 | -------------------------------------------------------------------------------- /test/traits.cpp: -------------------------------------------------------------------------------- 1 | // C++ Effects library 2 | // Maciej Pirog, Huawei Edinburgh Research Centre, maciej.pirog@huawei.com 3 | // License: MIT 4 | 5 | /* Test: Traits of type arguments one can use in the library: 6 | 7 | - AnswerType - at least move-costructible 8 | - BodyType - at least move-constructible 9 | - classes derived from eff::command - at least copy-constructible 10 | - OutType - at least move-constructible and move-assignable 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #include "cpp-effects/cpp-effects.h" 17 | 18 | namespace eff = cpp_effects; 19 | 20 | class XTCC { // exclude trivial constructor and copy 21 | public: 22 | XTCC(int v) : val(v) { } 23 | XTCC(XTCC&&) = default; 24 | XTCC& operator=(XTCC&&) = default; 25 | 26 | XTCC() = delete; 27 | XTCC(const XTCC&) = delete; 28 | XTCC& operator=(const XTCC&) = delete; 29 | 30 | int val; 31 | }; 32 | 33 | struct Cmd : eff::command { }; 34 | 35 | // ------------------------------------------------------------------ 36 | 37 | class TestAnswerType : public eff::handler { 38 | XTCC handle_command(Cmd, eff::resumption r) override { 39 | return XTCC(std::move(r).resume(100).val + 1); 40 | } 41 | XTCC handle_return(int v) override { 42 | return XTCC(v); 43 | } 44 | }; 45 | 46 | void testAnswerType() 47 | { 48 | std::cout << eff::handle_with( 49 | [](){ return eff::invoke_command(Cmd{}) + 10; }, 50 | std::make_shared()).val 51 | << "\t(expected: 111)" << std::endl; 52 | } 53 | 54 | // ------------------------------------------------------------------ 55 | 56 | class TestFlatAnswerType : public eff::flat_handler { 57 | XTCC handle_command(Cmd, eff::resumption r) override { 58 | return XTCC(std::move(r).resume(100).val + 1); 59 | } 60 | }; 61 | 62 | void testFlatAnswerType() 63 | { 64 | std::cout << eff::handle_with( 65 | [](){ return eff::invoke_command(Cmd{}) + 10; }, 66 | std::make_shared()).val 67 | << "\t(expected: 111)" << std::endl; 68 | } 69 | 70 | 71 | // ------------------------------------------------------------------ 72 | 73 | class TestAnswerTypeVoid : public eff::handler { 74 | XTCC handle_command(Cmd, eff::resumption r) override { 75 | return XTCC(std::move(r).resume(100).val + 1); 76 | } 77 | XTCC handle_return() override { 78 | return XTCC(100); 79 | } 80 | }; 81 | 82 | void testAnswerTypeVoid() 83 | { 84 | std::cout << eff::handle_with( 85 | [](){ eff::invoke_command(Cmd{}); }, 86 | std::make_shared()).val 87 | << "\t(expected: 101)" << std::endl; 88 | } 89 | 90 | // ------------------------------------------------------------------ 91 | 92 | class TestBodyType : public eff::handler { 93 | int handle_command(Cmd, eff::resumption r) override { 94 | return std::move(r).resume(100) + 1; 95 | } 96 | int handle_return(XTCC v) override { 97 | return v.val; 98 | } 99 | }; 100 | 101 | void testBodyType() 102 | { 103 | std::cout << eff::handle_with( 104 | [](){ return XTCC(eff::invoke_command(Cmd{}) + 10); }, 105 | std::make_shared()) 106 | << "\t(expected: 111)" << std::endl; 107 | } 108 | 109 | // ------------------------------------------------------------------ 110 | 111 | class TestBodyTypeVoid : public eff::handler { 112 | void handle_command(Cmd, eff::resumption r) override { 113 | std::cout << "*"; 114 | std::move(r).resume(100); 115 | } 116 | void handle_return(XTCC v) override { 117 | std::cout << v.val; 118 | } 119 | }; 120 | 121 | void testBodyTypeVoid() 122 | { 123 | eff::handle_with( 124 | [](){ return XTCC(eff::invoke_command(Cmd{}) + 10); }, 125 | std::make_shared()); 126 | std::cout << "\t(expected: *110)" << std::endl; 127 | } 128 | 129 | // ------------------------------------------------------------------ 130 | 131 | class CmdX : public eff::command { 132 | public: 133 | CmdX(int v) : val(v) { } 134 | CmdX(const CmdX&) = default; 135 | 136 | CmdX() = delete; 137 | CmdX& operator=(CmdX&&) = delete; 138 | CmdX& operator=(const CmdX&) = delete; 139 | 140 | int val; 141 | }; 142 | 143 | // ------------------------------------------------------------------ 144 | 145 | class XTestAnswerType : public eff::handler { 146 | XTCC handle_command(CmdX, eff::resumption r) override { 147 | return XTCC(std::move(r).resume(100).val + 1); 148 | } 149 | XTCC handle_return(int v) override { 150 | return XTCC(v); 151 | } 152 | }; 153 | 154 | void xTestAnswerType() 155 | { 156 | std::cout << eff::handle_with( 157 | [](){ return eff::invoke_command(CmdX(0)) + 10; }, 158 | std::make_shared()).val 159 | << "\t(expected: 111)" << std::endl; 160 | } 161 | 162 | // ------------------------------------------------------------------ 163 | 164 | class XTestAnswerTypeVoid : public eff::handler { 165 | XTCC handle_command(CmdX, eff::resumption r) override { 166 | return XTCC(std::move(r).resume(100).val + 1); 167 | } 168 | XTCC handle_return() override { 169 | return XTCC(100); 170 | } 171 | }; 172 | 173 | void xTestAnswerTypeVoid() 174 | { 175 | std::cout << eff::handle_with( 176 | [](){ eff::invoke_command(CmdX(0)); }, 177 | std::make_shared()).val 178 | << "\t(expected: 101)" << std::endl; 179 | } 180 | 181 | // ------------------------------------------------------------------ 182 | 183 | class XTestBodyType : public eff::handler { 184 | int handle_command(CmdX, eff::resumption r) override { 185 | return std::move(r).resume(100) + 1; 186 | } 187 | int handle_return(XTCC v) override { 188 | return v.val; 189 | } 190 | }; 191 | 192 | void xTestBodyType() 193 | { 194 | std::cout << eff::handle_with( 195 | [](){ return XTCC(eff::invoke_command(CmdX(0)) + 10); }, 196 | std::make_shared()) 197 | << "\t(expected: 111)" << std::endl; 198 | } 199 | 200 | // ------------------------------------------------------------------ 201 | 202 | class XTestBodyTypeVoid : public eff::handler { 203 | void handle_command(CmdX c, eff::resumption r) override { 204 | if (c.val != -1) { std::cout << "*"; } 205 | std::move(r).resume(100); 206 | } 207 | void handle_return(XTCC v) override { 208 | std::cout << v.val; 209 | } 210 | }; 211 | 212 | void xTestBodyTypeVoid() 213 | { 214 | eff::handle_with( 215 | [](){ return XTCC(eff::invoke_command(CmdX(0)) + 10); }, 216 | std::make_shared()); 217 | std::cout << "\t(expected: *110)" << std::endl; 218 | } 219 | 220 | // ------------------------------------------------------------------ 221 | 222 | class YTCC { 223 | public: 224 | YTCC(int v) : val(v) { } 225 | YTCC(YTCC&&) = default; 226 | 227 | YTCC() = delete; 228 | YTCC(const YTCC&) = delete; 229 | YTCC& operator=(YTCC&&) = default; 230 | YTCC& operator=(const YTCC&) = delete; 231 | 232 | int val; 233 | }; 234 | 235 | struct CmdY : public eff::command { int val; }; 236 | 237 | // ------------------------------------------------------------------ 238 | 239 | class YTestOutType : public eff::handler { 240 | int handle_command(CmdY c, eff::resumption r) override { 241 | return std::move(r).resume(YTCC(c.val)) + 1; 242 | } 243 | int handle_return(int v) override { 244 | return v; 245 | } 246 | }; 247 | 248 | void yTestOutType() 249 | { 250 | std::cout << eff::handle_with( 251 | [](){ return eff::invoke_command(CmdY{{}, 100}).val + 10; }, 252 | std::make_shared()) 253 | << "\t(expected: 111)" << std::endl; 254 | } 255 | 256 | // ------------------------------------------------------------------ 257 | 258 | int main() 259 | { 260 | std::cout << "--- traits ---" << std::endl; 261 | testAnswerType(); 262 | testFlatAnswerType(); 263 | testAnswerTypeVoid(); 264 | testBodyType(); 265 | testBodyTypeVoid(); 266 | xTestAnswerType(); 267 | xTestAnswerTypeVoid(); 268 | xTestBodyType(); 269 | xTestBodyTypeVoid(); 270 | yTestOutType(); 271 | } 272 | --------------------------------------------------------------------------------