熟悉函数式和 C++ 的老哥有偿帮忙解决个问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
FH0
V2EX    C++

熟悉函数式和 C++ 的老哥有偿帮忙解决个问题

  •  
  •   FH0 2023-10-30 12:54:24 +08:00 2442 次点击
    这是一个创建于 710 天前的主题,其中的信息可能已经有所发展或是发生改变。

    不知道下面的代码高亮了没有,我加了 ```c++,但是预览下的没高亮。应该是支持高亮的,我看其他的帖子里面有高亮的代码。

    新做的一个项目,本来是用 Rust 写的,包管理很方便。但是组长说 Rust 招不到人,就让我用 C++ 重写。C++ 的话现在用的是 git submodules 和 cmake ,没那么爽。好在组长没说 C++ 的版本,应该能用上 C++20 。

    p>本来是面向对象的命令式写法,但是网上冲浪太多了,经常看到函数式,早就想试试了,但其实自己面向对象也才懂一点点。最近想把测试写起来,因为自己的代码的其中两个 bug 让项目延期了几天,如果能写测试的话项目就不会在我这里卡住了。

    如果是面向对象的测试的话,我看一般是定义一个接口,然后实现里面的纯虚函数,然后写 mock 类测试。但这样的话就需要为了测试去额外定义一个接口。我就想试试函数式,虽然我还不确定函数式能否让测试变得简单、没有额外负担。

    下面的代码和测试无关,我只是想体验一下函数式。我想的是把那一大堆的 if 用 std::map 来代替,同时这个 std::map 在添加子命令的时候自动生成,这样子 main 函数的可读性就比原来好多了,行数也比较少。还有就是上面定义了变量,下面又传递给 SubCommand ,感觉复用度太低了,也许可以用一个结构体,然后和 SubCommand 组合一下。但是不知道具体怎么写。

    有偿的话不知道 200 元行不行。

    int main(int argc, char **argv) { // parse command line CLI::App cli; string logLevel = "info"; cli.add_option("-l,--logLevel", logLevel, "log level") ->option_text("debug, info, warn, error") ->capture_default_str(); string logFile = "stdout"; cli.add_option("-f,--logFile", logFile, "log file")->capture_default_str(); bool version = false; cli.add_flag("-v,--version", version, "show version")->capture_default_str(); auto &serveCli = *cli.add_subcommand("serve", "serve uart-manager service"); auto &readDataCli = *cli.add_subcommand("readData", "read data from uart-manager service"); uint32_t readDataPort; readDataCli.add_option("-p,--port", readDataPort, "uart port")->required(); uint32_t messages = 1; readDataCli.add_option("-m,--messages", messages, "stop after read ? messages") ->capture_default_str(); auto &setBaudRateCli = *cli.add_subcommand("setBaudRate", "set uart baud rate"); uint32_t setBaudRatePort; setBaudRateCli.add_option("-p,--port", setBaudRatePort, "uart port")->required(); uint32_t setBaudRateBaudRate; setBaudRateCli.add_option("-b,--baudRate", setBaudRateBaudRate, "uart baud rate") ->option_text("9600, 14400, 19200, 38400, 57600, 115200") ->required(); auto &writeDataCli = *cli.add_subcommand("writeData", "write data to uart-manager service"); vector<uint32_t> writeDataPorts; writeDataCli.add_option("-p,--port", writeDataPorts, "uart port")->required(); string writeDataHex; writeDataCli.add_option("-H,--hex", writeDataHex, "hex data"); string writeDataStr; writeDataCli.add_option("-s,--str", writeDataStr, "string data"); auto &updateFpgaCli = *cli.add_subcommand("updateFpga", "update fpga"); string updateFpgaFile; updateFpgaCli.add_option("-f,--file", updateFpgaFile, "fpga file")->required(); auto &getAutoRespOnseCli= *cli.add_subcommand("getAutoResponse", "get auto response"); uint32_t getAutoResponsePort; getAutoResponseCli.add_option("-p,--port", getAutoResponsePort, "uart port")->required(); auto &clearAutoRespOnseCli= *cli.add_subcommand("clearAutoResponse", "clear auto response"); uint32_t clearAutoResponsePort; clearAutoResponseCli.add_option("-p,--port", clearAutoResponsePort, "uart port")->required(); auto &addAutoRespOnseCli= *cli.add_subcommand("addAutoResponse", "add auto response"); uint32_t addAutoResponsePort; addAutoResponseCli.add_option("-p,--port", addAutoResponsePort, "uart port")->required(); vector<string> addAutoResponseHex; addAutoResponseCli.add_option("-H,--hex", addAutoResponseHex, "response hex data"); vector<string> addAutoResponseStr; addAutoResponseCli.add_option("-s,--str", addAutoResponseStr, "response string data"); vector<string> addAutoResponseWhenContains; addAutoResponseCli .add_option("-w,--whenContains", addAutoResponseWhenContains, "response str or hex data when contains str or hex data") ->required(); auto &getFpgaVersiOnCli= *cli.add_subcommand("getFpgaVersion", "get fpga version"); auto &set12vStatusCli = *cli.add_subcommand("set12vStatus", "set 12v of ports 1-48 status"); auto set12vStatusEnableFirst = true; set12vStatusCli .add_flag("-e,--enableFirst", set12vStatusEnableFirst, "enable first 12v, ports 1-24") ->capture_default_str(); auto set12vStatusEnableSecOnd= true; set12vStatusCli .add_flag("-s,--enableSecond", set12vStatusEnableSecond, "enable second 12v, ports 25-48") ->capture_default_str(); auto &get12vStatusCli = *cli.add_subcommand("get12vStatus", "get 12v of ports 1-48 status"); CLI11_PARSE(cli, argc, argv); // version if (version) { printf("version: %s\n", VERSION); return 0; } // log initLog(logLevel, logFile); // subcommands if (serveCli) { Manager().start(); } else if (readDataCli) { SubCommand().readData(readDataPort, messages); } else if (setBaudRateCli) { SubCommand().setBaudRate(setBaudRatePort, setBaudRateBaudRate); } else if (writeDataCli) { SubCommand().writeData(writeDataPorts, writeDataHex, writeDataStr); } else if (updateFpgaCli) { SubCommand().updateFpga(updateFpgaFile); } else if (getAutoResponseCli) { SubCommand().getAutoResponse(getAutoResponsePort); } else if (clearAutoResponseCli) { SubCommand().clearAutoResponse(clearAutoResponsePort); } else if (addAutoResponseCli) { SubCommand().addAutoResponse(addAutoResponsePort, addAutoResponseHex, addAutoResponseStr, addAutoResponseWhenContains); } else if (getFpgaVersionCli) { SubCommand().getFpgaVersion(); } else if (set12vStatusCli) { SubCommand().set12vStatus(set12vStatusEnableFirst, set12vStatusEnableSecond); } else if (get12vStatusCli) { SubCommand().get12vStatus(); } else { cout << cli.help() << endl; } return 0; } 
    15 条回复    2023-11-01 14:24:02 +08:00
    buf1024
        1
    buf1024  
       2023-10-30 13:15:02 +08:00
    伪代码示意
    ```

    std::list xx;
    class Cmd {
    bool* isSet;
    func handler
    };

    cmd 定义以下宏定义
    #define AddCmd
    do {

    auto &get12vStatusCli = *cli.add_subcommand("get12vStatus", "get 12v of ports 1-48 status");
    xx.push(new Cmd{get12vStatusCli, handler...})

    } while (0)

    最后的 if
    for cmd in xx
    if (cmd.isSet) {
    xx()
    }

    ```
    s7964926
        2
    s7964926  
       2023-10-30 13:43:31 +08:00
    ```cpp
    #include <map>
    #include <functional>
    #include <CLI/CLI.hpp>

    struct CmdOptions {
    bool serveCli = false;
    uint32_t readDataPort = 0;
    uint32_t messages = 1;
    };

    class SubCommand {
    public:
    void serve(const CmdOptions &options) {
    // 实现
    }

    void readData(const CmdOptions &options) {
    // 使用 options.readDataPort 和 options.messages
    }

    // 其他
    };

    int main(int argc, char **argv) {
    CLI::App cli;
    CmdOptions options;
    SubCommand subCmd;

    auto &serveCli = *cli.add_subcommand("serve", "serve uart-manager service");
    serveCli.callback([&options, &subCmd]() { subCmd.serve(options); });

    auto &readDataCli = *cli.add_subcommand("readData", "read data from uart-manager service");
    readDataCli.add_option("-p,--port", options.readDataPort, "uart port")->required();
    readDataCli.add_option("-m,--messages", options.messages, "stop after read ? messages");
    readDataCli.callback([&options, &subCmd]() { subCmd.readData(options); });

    // 其他

    CLI11_PARSE(cli, argc, argv);

    return 0;
    }

    ```
    FH0
        3
    FH0  
    OP
       2023-10-30 13:45:42 +08:00
    @buf1024 兄弟,你这个没用到函数式吧
    FH0
        4
    FH0  
    OP
       2023-10-30 13:56:31 +08:00
    @s7964926 确实,用了 callback 就可以省略后面的 if 了。那前面的代码能不能用函数式处理一下。比如:
    FH0
        5
    FH0  
    OP
       2023-10-30 13:58:58 +08:00
    ```c++
    compose(
    newCli,
    serve,
    readData,
    setBaudRate,
    );
    ```


    ```c++
    cli.map(serve)
    .map(readData)
    .map(setBaudRate);
    ```
    luassuns
        6
    luassuns  
       2023-10-30 15:08:32 +08:00
    @FH0 你想要这样只要在 add_option 返回 return this 不就好了
    luassuns
        7
    luassuns  
       2023-10-30 15:23:27 +08:00
    ```cpp

    struct Command
    {
    std::string msg;
    std::string description;
    bool require;
    std::function<void(std::any)> command;
    void exec(std::any v)
    {
    if (command)
    command(v);
    }
    };

    class Args
    {
    public:
    Args *add_opt(Command *command)
    {
    commandMap[command->msg] = command;
    return this;
    }

    void parse(int argc, char *argv[])
    {
    // do parse....
    std::map<std::string, std::any> valueMap;
    for (auto &[key, value] : commandMap) {
    if(valueMap.find(key) != valueMap.end()) {
    value->exec(valueMap[key]);
    } else {
    if(value->require) {
    // require but not found
    }
    }
    }
    }
    std::map<std::string, Command *> commandMap;
    };

    Args *args = new Args();

    args->add_opt(
    new Command{"-p", "port", true, [](std::any v) { std::cout << std::any_cast<int>(v); }})
    ->add_opt(new Command())
    ->add_opt(new Command())
    ->add_opt(new Command())
    ->add_opt(new Command());
    ```
    FH0
        8
    FH0  
    OP
       2023-10-30 15:26:40 +08:00
    @luassuns return this 确实可以,但是方法都是封装在一个类里面的,这是面向对象的思想。我想尝试一下函数式,就是纯函数、副作用、柯里化、函子那些。
    netabare
        9
    netabare  
       2023-10-30 16:44:20 +08:00 via Android   1
    c++没必要太函数式吧,如果 op 想要用的话可以试试 optional 来替代返回错误码/exception 的写法,但不确定 20 是否支持流式的 flatmap 写法。

    感觉用好 move 和 unique_ptr 就好了,c++真的没必要太追求纯函数或者柯里化。
    FH0
        10
    FH0  
    OP
       2023-10-30 18:20:18 +08:00
    @netabare 好吧,C++ 函数式的讨论确实比较少,我似乎走上了一条荒无人烟的道路。
    kilasuelika
        11
    kilasuelika  
       2023-10-30 23:18:27 +08:00 via Android
    boost 里面有个 hof ,高阶函数,可以去了解一下。c++ 本身不是专门做函数式的,要想实现那些必须要进行一些封装和借助一些奇技淫巧。
    FH0
        12
    FH0  
    OP
       2023-10-31 01:03:40 +08:00
    netabare
        13
    netabare  
       2023-10-31 08:28:35 +08:00 via Android   1
    @FH0 其实如果要做函数式的话,C#、Java 之类的虚拟机托管语言会合适很多,至少写起来没那么痛苦。

    函数式写法主要还是追求更好的抽象和表达能力,比较吃编译器的代码优化和语法糖。在 C++这种偏底层,程序员希望能够对内存和变量生命周期有更多掌控权的语言里,有点吃力不讨好。Rust 里面其实也没多少函数式的写法,更多是从函数式编程里面借用了比如模式匹配、trait 之类的思路。

    不过 C++也没有模式匹配或者 trait ,所以还是有点尴尬。
    FH0
        14
    FH0  
    OP
       2023-10-31 14:24:15 +08:00
    @netabare 我找到了一个项目用了很多的函数式思想,还是想探索一下。github.com/arximboldi/ewig
    FH0
        15
    FH0  
    OP
       2023-11-01 14:24:02 +08:00
    最后代码优化成了这样,先暂时用着。
    ```c++
    int main(int argc, char **argv) {
    auto [mainCli, beforeSubcommand] = makeCli(argc);

    const auto subcommands = {
    serve(),
    readData(),
    writeData(),
    setBaudRate(),
    clearAutoResponse(),
    getAutoResponse(),
    addAutoResponse(),
    clearTimedResponse(),
    getTimedResponse(),
    addTimedResponse(),
    get12vStatus(),
    set12vStatus(),
    getFpgaVersion(),
    updateFpga(),
    };
    unordered_map<App *, function<void()>> subcommandMap;
    for (const auto &[cli, callback] : subcommands) {
    mainCli->add_subcommand(cli);
    subcommandMap.emplace(&*cli, callback);
    }

    CLI11_PARSE(*mainCli, argc, argv);

    beforeSubcommand();

    for (auto *key : mainCli->get_subcommands()) {
    subcommandMap[key]();
    }

    return 0;
    }
    ```
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5699 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 32ms UTC 02:57 PVG 10:57 LAX 19:57 JFK 22:57
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86