现代 C++ 函数式编程指南

  • 现代 C++ 函数式编程指南
    • 什么是 柯里化 (Curry)
    • 什么是 部分应用 (Partial Application)
      • 二元函数 (Partial Application)
      • 参数排序 (Partial Application)
        • 应用场景
          • 计算碳衰减周期求年龄
        • 多参数 (Partial Application)
        • 高阶函数 (Partial Application)
    • 结论

现代 C++ 函数式编程指南

函数式编程是一种编程范式,它强调程序的构建是通过应用(applying)和组合函数(composing functions)来实现的。在函数式编程中,程序被视为由函数定义的表达式树,这些函数将一个值映射到另一个值,而不是一系列更新程序运行状态的命令式语句。

https://en.wikipedia.org/wiki/Functional_programming

什么是 柯里化 (Curry)

一种函数,将具有多个参数的函数作为输入并返回仅具有一个参数的函数。

Curry: A function that takes a function with multiple parameters as input and returns a function with exactly one parameter.

让我们首先看一个简单的例子,展示柯里化的基本概念:

#include <print> // C++23// 柯里化函数模板
template<typename Func, typename... Args>
auto curry(Func func, Args... args) {return [=](auto... remainingArgs) {return func(args..., remainingArgs...);};
}// 示例一:加法器的柯里化
int add(int a, int b) {return a + b;
}int main() {// 使用柯里化创建新的加法函数auto curriedAdd = curry(add, 5);  // 固定第一个参数为 5// 调用柯里化后的函数std::println("{:d}", curriedAdd(3));  // 输出 8 (5 + 3)return 0;
}

这个例子中,curry 函数接受一个函数和部分参数,返回一个接受剩余参数的函数。curriedAdd 就是一个将加法函数柯里化后的结果,固定了第一个参数为 5。

什么是 部分应用 (Partial Application)

将函数应用于其某些参数的过程。 部分应用的函数将被返回以供以后使用。 换句话说,一个函数接受一个具有多个参数的函数并返回一个具有较少参数的函数。 部分应用修复(部分应用函数)返回函数内的一个或多个参数,返回函数将其余参数作为参数以完成函数应用。

Partial Application: The process of applying a function to some of its arguments. The partially applied function gets returned for later use. In other words, a function that takes a function with multiple parameters and returns a function with fewer parameters. Partial application fixes (partially applies the function to) one or more arguments inside the returned function, and the returned function takes the remaining parameters as arguments in order to complete the function application.

参考 https://en.wikipedia.org/wiki/Partial_application

注文中 特化 代指 Partial Application 。

二元函数 (Partial Application)

作为 API 创建者,我们经常希望特化功能或预填充某些参数,这可以通过部分应用来实现。

partial_application_scheme

我们提供具体论据的子集,并产生一个较小数量的函数。

我们来看一个具体的例子。

该函数计算扇形的面积。

partial_application_circle_sector

double secArea(double theta, double radius){return 0.5*theta*pow(radius,2);
}

让我们专门计算这个函数来计算整圆的面积,我们需要嵌套 lambda 来表达 部分应用(Partial Application)

// papply 是 Partial Application 缩写的形式,p(Partial) apply(Application)
auto papply = [](auto f, auto x) {return [=](auto y){return f(x,y);};
};

为了实现特化,我们只需要传递函数及其第一个参数。

auto op = papply(secArea,2*M_PI); // 固定第一个参数为完整的圆弧长度即完整的圆
auto val = op(3.0); // 计算半径为 3 的圆的面积

partial_application_circle_sector2

在前一种情况下,特化涉及第一个函数参数。

double secArea(double rAngle, double radius);

完整代码如下:

#include <print>    // C++23
#include <numbers>  // C++20// papply 是 Partial Application 缩写的形式,p(Partial) apply(Application)
auto papply = [](auto f, auto x) {return [=](auto y) {return f(x, y);};
};// 计算圆弧的面积
double secArea(double theta, double radius) {return 0.5 * theta * pow(radius, 2);
}int main() {auto op  = papply(secArea, 2 * std::numbers::pi);  // 固定第一个参数为完整的圆弧长度即完整的圆auto val = op(3.0);                                // 计算半径为 3 的圆的面积// 使用 std::format 格式化浮点数并保留两位小数std::println("{:.2f}", val);  // 输出半径为2的圆面积 28.27return 0;
}

然而,我们常常不得不处理参数排序。

参数排序 (Partial Application)

partial_application_pow

double pow (double base, double exponent);

例如 将 pow C 库函数将基数(base)位置参数置换为指数(exponent)位置参数。

我们如何特化 pow 来返回基数(base)的 2 次方?

partial_application_pow2

下面这种特化可以解决我们上面的问题吗?

partial_application_pow3

如果我们特化 base 部分,papply 将返回一个 2 的任意幂函数。

auto op = papply(pow,2); // 2 的任意幂
auto val = op(3); // 2^3 = 8

该结果不是我们想要的。

pow 函数需要对第二个参数进行特化。

double pow (double base, double exponent);

这个问题可以通过参数交换来解决。

auto swapArgs = [] (auto f){return [=](auto x, auto y){return f(y,x);};
};
auto op = papply(swapArgs(pow), 2); // 现在2作为了指数,解决了我们上面的问题。
auto val = op(3); // 3^2 = 9

我们也可以使用特化专用于 pow 的 lambda 来解决。

auto powS = [](auto exponent, auto base){return pow(base, exponent);
};
auto op = papply(powS, 2); 
auto val = op(3); // 3^2 = 9

或者使用下面这种更加紧凑的形式。

auto op = papply([](auto exponent, auto base){return pow(base, exponent);}, 2);auto val = op(3); // 3^2 = 9

另一种选择是使用库函数 std::bind

此解决方案绕过了使用 lambda 表达式。

auto op = std::bind(pow, std::placeholders::_1, 2);
auto val = op(3); // 3^2 = 9
应用场景
计算碳衰减周期求年龄

接下来,让我们看一个关于碳衰减周期求年龄的例子:

含有有机物质的物体的年龄可以通过放射性同位素测年法确定。

partial_application_radioactive2

这是放射性衰变的一般方程

partial_application_radioactive3

double age(double remainingProportion, double halflife){return log(remainingProportion)*halflife / -log(2);
}

将半衰期替换为碳C14的半衰期,即5730年。

auto op = papply(swapArgs(age),5730);

问题1. 与活体样本相比,含有 40% C14 的化石有多少年了?

auto val = op(0.4); // 7575 years

完整代码如下:

#include <print> // C++23// papply 是 Partial Application 缩写的形式,p(Partial) apply(Application)
auto papply = [](auto f, auto x) {return [=](auto y) {return f(x, y);};
};auto swapArgs = [](auto f) {return [=](auto x, auto y) {return f(y, x);};
};double age(double remainingProportion, double halflife) {return log(remainingProportion) * halflife / -log(2);
}int main() {auto op  = papply(swapArgs(age), 5730);                  // 将半衰期替换为碳C14的半衰期,即5730年。auto val = op(0.4);                                      // 计算含有 40% C14 的化石有多少年了?std::println("{:d}", static_cast<int>(std::ceil(val)));  // 7575 yearsreturn 0;
}

与正则表达式相关的函数的特化也非常有用。

让我们专门研究 std::regex_match

std::regex_match 确定正则表达式 re 是否匹配整个字符序列 s。

bool std::regex_match( const std::string& s,const std::regex& re,std::regex_constants::match_flag_type flags =std::regex_constants::match_default);

我们如何特化使用 std::regex_match 来验证电子邮件地址?

我们使用特化的 lambda 来实现所需的参数排序

auto op = papply([](auto re, auto str){return std::regex_match(str, re);}, std::regex("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+"));
auto val1 = op("test@cheungxiongwei.com"); // return 1, i.e. true
auto val2 = op("test@cheungxiongweicom"); // return 0, i.e. false

完整代码:

#include <print>  // C++23
#include <regex>  // C++11// papply 是 Partial Application 缩写的形式,p(Partial) apply(Application)
auto papply = [](auto f, auto x) {return [=](auto y) {return f(x, y);};
};int main() {// 该例子中使用的特化 lambda 形式进行参数交换auto op   = papply([](auto re, auto str) { return std::regex_match(str, re); }, std::regex("(\\w+)(\\.|_)?(\\w*)@(\\w+)(\\.(\\w+))+"));auto val1 = op("test@cheungxiongwei.com");  // return 1, i.e. trueauto val2 = op("test@cheungxiongweicom");   // return 0, i.e. falsestd::println("{} {}", val1, val2);  // true falsereturn 0;
}

让我们继续讨论多参数问题

多参数 (Partial Application)

这是运动物体最终速度的公式。

partial_application_velocity

Note:这个函数有三个参数,而不是前面2个参数的形式

// 计算速度
double velocity(double v0/*初始速度*/, double a/*加速度*/, double t/*加速时间*/){return v0 + a*t;
}

我们如何将这个公式特化用于解决自由落体问题?

我们想要专门研究 两个参数 :初始速度和加速度。

我们需要嵌套 lambda 和参数包。

auto papply = [](auto f, auto... args) {return [=](auto... rargs) {return f(args..., rargs...);};
};

多个参数的使用通过参数包来表示 ...

我们将其类似地应用于二元情况

partial_application_velocity2

auto op = papply(velocity, 0.0, 9.81);
auto val = op(4.5/*4.5秒加速时间*/); // returns 44.15 m/s

在这种特定情况下,不需要交换。

完整代码:

#include <print>  // C++23// papply 是 Partial Application 缩写的形式,p(Partial) apply(Application)
auto papply = [](auto f, auto... args) {return [=](auto... rargs) {return f(args..., rargs...);};
};double velocity(double v0 /*初始速度*/, double a /*加速度*/, double t /*加速时间*/) {return v0 + a * t;
}int main() {auto op  = papply(velocity, 0.0, 9.81);auto val = op(4.5 /*4.5秒加速时间*/);  // returns 44.15 m/sstd::println("{:.2f} m/s", val);  // 44.15 m/sreturn 0;
}
高阶函数 (Partial Application)

如何特化高阶函数?

此函数对集合执行左折叠

auto leftFold = [](auto col, auto op, auto init) {return std::accumulate(std::begin(col), std::end(col), init, op);
};

函数 leftFold 使用二元运算 op 从值 init 开始组合集合 col 的元素。

  1. 使用 leftFold 特化执行求和
auto op = papply([](auto op, auto init, auto col){return leftFold(col, op, init);},std::plus<>(), 0.0);

该函数计算从值 0.0 开始的集合元素的总和。

  1. 使用 leftFold 特化计算集合的乘积
auto op = papply([](auto op, auto init, auto col){return leftFold(col, op, init);},std::multiplies<>(),1.0);

完整代码:

#include <print>    // C++23
#include <numeric>  // C++20
#include <vector>auto papply = [](auto f, auto... args) {return [=](auto... rargs) {return f(args..., rargs...);};
};auto leftFold = [](auto col, auto op, auto init) {return std::accumulate(std::begin(col), std::end(col), init, op);
};int main() {auto op_plus       = papply([](auto op, auto init, auto col) { return leftFold(col, op, init); }, std::plus<>(), 0.0);auto op_multiplies = papply([](auto op, auto init, auto col) { return leftFold(col, op, init); }, std::multiplies<>(), 1.0);std::vector<int> set = {1, 2, 3, 4, 5};auto val1 = op_plus(set);        // 1 + 2 + 3 + 4 + 5 = 15auto val2 = op_multiplies(set);  // 1 * 2 * 3 * 4 * 5 = 120std::println("{:d} {:d}", static_cast<int>(val1), static_cast<int>(val2)); // 15 120return 0;
}

结论

柯里化(Curry) 和 (部分应用)Partial Application 作为函数式编程的重要概念,通过现代 C++ 中的函数对象和 lambda 表达式实现,为代码的模块化和灵活性提供了更多可能性。通过固定部分参数,生成新的函数,柯里化让函数处理变得更加高效、灵活和可复用。

在 C++ 中,柯里化(Curry) 和 (部分应用)Partial Application为处理函数式编程提供了一种强大的工具,可以应对各种复杂的场景,提高代码的可读性和可维护性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/189056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java SSM框架实现汽车在线销售系统项目【项目源码+论文说明】

基于java的SSM框架实现汽车在线销售系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&a…

quick_快应用_广告接入

目录 接入厂商广告oppo广商接入广告banner广告信息流广告[1] 组件封装[2] 渲染数据[3] 测试[4] 样式调整[5] 加载失败[6] 预加载[7] 应用要素信息 接入厂商广告 广告接口 [1] 接口声明 {"name":"service.ad"}[2] 导入模块 import ad from service.ad 或 c…

[FUNC]判断窗口在哪一个屏幕上

#Requires AutoHotkey v2.0#z:: { ToolTip "Notepad窗口所在显示屏是&#xff1a;" GetMonitor() } GetMonitor() {CoordMode("Mouse", "Screen"); MouseGetPos &mx, &myWinGetPos &mx, &my,,,"ahk_class Notepad"…

【小布_ORACLE笔记】Part11-6 RMAN Backups

【小布_ORACLE笔记】Part11-6 RMAN Backups 1.track文件的作用 当做差异性备份时&#xff0c;server process对应的RMAN客户端的server process就不用去每个块每个块的检查&#xff0c;只要到trackfile 里面去读一下&#xff0c;看哪个块改变了就直接把哪个块备份下来&#x…

matlab 混沌动力学行为-分岔图-李雅普指数等

1、内容简介 略 24-可以交流、咨询、答疑 2、内容说明 混沌动力学行为-分岔图-李雅普指数等 包含各种类型的混沌模型求解&#xff0c;包含其分叉图、李雅普指数等 混沌、分叉图、李雅普指数 3、仿真分析 略 4、参考论文 略 链接&#xff1a;https://pan.baidu.com/…

无限移动的风景 css3 动画 鼠标移入暂停

<style>*{margin:0;padding:0;/* box-sizing: border-box; */}ul{list-style: none;}#nav{width:900px;height:100px;border:2px solid rgb(70, 69, 69);margin:100px auto; overflow: hidden;}#nav ul{animation:moving 5s linear infinite;width:200%; /*怎么模拟动画…

【已解决】Cannot find project Scala library 2.11.8 for module XXX

问题描述 在 flink 示例程序调试过程中&#xff0c;reload project 报错 Cannot find project Scala library 2.11.8 for module HbasePrint 报错如下图所示&#xff1a; 问题解决 经过搜索&#xff0c;初步判定是 pom 文件中 Scala 版本配置和项目中实际使用的版本不一致导…

在 SQL Server 中备份和恢复数据库的最佳方法

在SQL Server中&#xff0c;创建备份和执行还原操作对于确保数据完整性、灾难恢复和数据库维护至关重要。以下是备份和恢复过程的概述&#xff1a; 方法 1. 使用 SQL Server Management Studio (SSMS) 备份和还原数据库 按照 SSMS 步骤备份 SQL 数据库 打开 SSMS 并连接到您…

什么是OV SSL证书?

OV SSL证书是组织验证SSL证书的缩写&#xff0c;是三个SSL验证级别之一的名称。 OV是指实名类型的SSL证书&#xff0c;这个实名其实只要证明发布者身份就可以签发&#xff0c;无论是个人还是企业都可以进行申请。 SSL证书大家都知道就是用于网站地址的http改成https加密协议的…

Redis部署-主从模式

目录 单点问题 主从模式 解析主从模式 配置redis主从模式 info replication命令查看复制相关的状态 断开复制关系 安全性 只读 传输延迟 拓扑结构 数据同步psync replicationid offset psync运行流程 全量复制流程 无硬盘模式 部分复制流程 积压缓冲区 实时复…

如何生成纯文本的目录树

参考资料&#xff1a; https://ascii-tree-generator.com/ 无需多言&#xff0c;感谢这些前辈的智慧。界面如下&#xff1a;

河南省第一届职业技能大赛网络安全项目试题

河南省第一届职业技能大赛 网络安全项目试题 一、竞赛时间 总计&#xff1a;420分钟 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A模块 A-1 登录安全加固 240分钟 200分 A-2 Web安全加固&#xff08;Web&#xff09; A-3 流量完整性保护与事件监控&am…

【AB平台数据建设】从实验平台到数据管道

文章目录 前言1.从AB实验平台聊起(1)AB平台在业务中的发挥那些作用(2)AB平台进行实验工作流介绍 2.实验平台底层数据管道最小MVP解构(1)数据管道数据从哪里来&#xff1f;(2)数据管道的输出数据有哪些&#xff1f; 小结 前言 AB实验平台是一种通过小范围放量&#xff0c;测试不…

Python 网络爬虫(一):HTML 基础知识

《Python入门核心技术》专栏总目录・点这里 文章目录 1. 什么是 HTML2. HTML 的特点3. HTML 的标签和属性4. HTML 的结构4.1 文档类型声明4.2 根元素4.3 头部部分4.4 主体部分4.5 表格标签4.6 区块4.7 嵌套和层次结构4.8 表单4.9 注释 5. HTML 交互事件 大家好&#xff0c;我是…

#zookeeper集群+kafka集群

kafka3.0之前是依赖于zookeeper的。 zookeeper是开源&#xff0c;分布式的架构。提供协调服务&#xff08;Apache项目&#xff09; 基于观察者模式涉及的分布式服务管理架构。 存储和管理数据。分布式节点上的服务接受观察者的注册。一旦分布式节点上的数据发生变化&#xf…

【快速见刊|投稿优惠】2024年机电一体与自动化技术国际学术会议(IACMAT 2024)

2024年机电一体与自动化技术国际学术会议(IACMAT 2024) 2024 International Academic Conference on Mechatronics and Automation Technology(IACMAT 2024) 一【会议简介】 2024年机电一体与自动化技术国际学术会议(IACMAT 2024)即将召开&#xff0c;它以“机电一体&#xff0…

2023年【安全员-B证】最新解析及安全员-B证免费试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-B证最新解析是安全生产模拟考试一点通生成的&#xff0c;安全员-B证证模拟考试题库是根据安全员-B证最新版教材汇编出安全员-B证仿真模拟考试。2023年【安全员-B证】最新解析及安全员-B证免费试题 1、【多选题…

用友U8 ERP和面粉行业专版系统接口集成方案

面粉加工行业面临着数据管理和业务流程自动化的挑战。众诚ERP系统和用友U8系统的数据集成是解决这一挑战的关键。 解决方案 轻易云平台提供了一套完善的数据同步和集成解决方案&#xff0c;包括以下几个方面&#xff1a; 基础资料同步&#xff1a;包括物料、客户、供应商、仓…

解决:AttributeError: ‘NoneType’ object has no attribute ‘shape’

解决&#xff1a;AttributeError: ‘NoneType’ object has no attribute ‘shape’ 文章目录 解决&#xff1a;AttributeError: NoneType object has no attribute shape背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使用之前的代码时&…

【数值计算方法(黄明游)】矩阵特征值与特征向量的计算(二):Jacobi 过关法(Jacobi 旋转法的改进)【理论到程序】

文章目录 一、Jacobi 旋转法1. 基本思想2. 注意事项 二、Jacobi 过关法1. 基本思想2. 注意事项 三、Python实现迭代过程&#xff08;调试&#xff09; 矩阵的特征值&#xff08;eigenvalue&#xff09;和特征向量&#xff08;eigenvector&#xff09;在很多应用中都具有重要的数…