Skip to content

akemimadoka/AlgebraicEffect

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AlgebraicEffect

C++20 License

一个基于 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;
}

TODO

  • 实现自动传播嵌套 EffectExecutor 的同时,手动处理其中的部分 Effect

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors