一个基于 C++20 协程的高性能(!?)代数效应(Algebraic Effects)库实现。
代数效应是一种强大的编程抽象,允许你将副作用(如 I/O、状态管理、异常处理)与业务逻辑分离。通过代数效应,你可以:
- 在返回类型中声明需要的效应
- 在外部提供效应的具体实现(handler),使用
co_yield返回到外部处理器执行并获得结果 - 实现控制流的反转和依赖注入
- 支持效应的组合和嵌套调用,自动检察传播的兼容性,当手动处理时不会进行额外的检查
- 类型安全:编译期检查效应类型,避免运行时错误
- 零开销抽象:Header-only 库,编译器可充分内联优化
- C++20 协程:基于标准协程,无需第三方依赖
- 嵌套支持:支持嵌套调用和自动效应传播
- C++20 兼容的编译器(推荐 GCC 13+, Clang 16+, MSVC 2022+)
- CMake 3.20 或更高版本
#include <AlgebraicEffect/AlgebraicEffect.h>
#include <iostream>
#include <fstream>
#include <vector>
using namespace AlgebraicEffect;
// 定义一个效应类型
struct ReadFileEffect
{
std::string filename;
};
// 通过 EffectTraits 指定效应的结果类型
template <>
struct EffectTraits<ReadFileEffect>
{
using ResultType = std::vector<std::byte>;
};
// 使用效应
EffectExecutor<int, ReadFileEffect> CountFileSize()
{
// 使用 co_yield 执行效应
const auto content = co_yield ReadFileEffect{ "example.txt" };
co_return static_cast<int>(content.size());
}
// 提供效应的处理器
int main()
{
const auto result = CountFileSize()([](ReadFileEffect const& effect)
{
// 在这里实现具体的文件读取逻辑
std::ifstream file(effect.filename, std::ios::binary | std::ios::ate);
if (!file) {
throw std::runtime_error("Failed to open file: " + effect.filename);
}
std::vector<std::byte> content(file.tellg());
file.seekg(0);
file.read(reinterpret_cast<char*>(content.data()), content.size());
return content;
});
std::cout << "File size: " << result << " bytes\n";
return 0;
}struct LogEffect
{
std::string message;
};
struct GetConfigEffect
{
std::string key;
};
template <>
struct EffectTraits<LogEffect>
{
using ResultType = void;
};
template <>
struct EffectTraits<GetConfigEffect>
{
using ResultType = std::string;
};
EffectExecutor<int, LogEffect, GetConfigEffect> ProcessWithMultipleEffects()
{
co_yield LogEffect{ "Starting process..." };
const auto config = co_yield GetConfigEffect{ "api.endpoint" };
co_yield LogEffect{ "Got config: " + config };
co_return 0;
}
int main() {
ProcessWithMultipleEffects()([](auto const& effect)
{
using T = std::decay_t<decltype(effect)>;
if constexpr (std::same_as<T, LogEffect>)
{
std::cout << "[LOG] " << effect.message << "\n";
}
else if constexpr (std::same_as<T, GetConfigEffect>)
{
return std::string("https://api.example.com");
}
});
}EffectExecutor<int, ReadFileEffect> ReadConfig()
{
const auto data = co_yield ReadFileEffect{ "config.json" };
co_return static_cast<int>(data.size());
}
EffectExecutor<int, ReadFileEffect, LogEffect> MainProcess()
{
// 使用 co_await 执行并自动传播其他 EffectExecutor
const auto configSize = co_await ReadConfig();
co_yield LogEffect{ "Config size: " + std::to_string(configSize) };
co_return configSize;
}try
{
TestEffect()([](ReadFileEffect const& effect) -> std::vector<std::byte>
{
throw std::runtime_error("Simulated error");
});
}
catch (const std::exception& ex)
{
std::cout << "Caught exception: " << ex.what() << "\n";
}// IDE/编译器可在 co_await 处检查出错误
// Call to deleted member function 'await_suspend': Current Effect list must be a subset of the previous Effect list
// AlgebraicEffect.h(310, 15): Candidate function [with PreviousEffectExecutorPromise = AlgebraicEffect::EffectExecutor<int, (anonymous namespace)::MainProcess>::promise_type] has been explicitly deleted
// AlgebraicEffect.h(318, 15): Candidate template ignored: constraints not satisfied [with PreviousEffectExecutorPromise = AlgebraicEffect::EffectExecutor<int, (anonymous namespace)::MainProcess>::promise_type]
// AlgebraicEffect.h(316, 17): Because 'CheckIfCompatible<typename promise_type::ExecutorType::EffectsSequence>()' evaluated to false
EffectExecutor<int, LogEffect> TestErrorEffect()
{
const auto content = co_await MainProcess();
co_return content;
}- 实现自动传播嵌套 EffectExecutor 的同时,手动处理其中的部分 Effect