惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
The Last Watchdog
The Last Watchdog
C
Cisco Blogs
L
LINUX DO - 热门话题
T
Tenable Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
O
OpenAI News
The Hacker News
The Hacker News
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
量子位
V2EX - 技术
V2EX - 技术
GbyAI
GbyAI
W
WeLiveSecurity
Recorded Future
Recorded Future
P
Proofpoint News Feed
Project Zero
Project Zero
P
Palo Alto Networks Blog
Microsoft Security Blog
Microsoft Security Blog
S
Security Affairs
Stack Overflow Blog
Stack Overflow Blog
B
Blog
Y
Y Combinator Blog
月光博客
月光博客
K
Kaspersky official blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Latest news
Latest news
Forbes - Security
Forbes - Security
T
Troy Hunt's Blog
T
The Blog of Author Tim Ferriss
C
Check Point Blog
D
DataBreaches.Net
MyScale Blog
MyScale Blog
博客园 - 三生石上(FineUI控件)
博客园 - 聂微东
D
Docker
P
Privacy International News Feed
C
Cybersecurity and Infrastructure Security Agency CISA
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
C
Cyber Attacks, Cyber Crime and Cyber Security
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
雷峰网
雷峰网
SecWiki News
SecWiki News
腾讯CDC
博客园 - Franky

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
函数式编程常用术语 - 元视角
飞鸿踏雪 · 2017-02-03 · via 元视角

近年来函数式编程这种概念渐渐流行起来,尤其是在 React/Vuejs 这两个前端框架的推动下,函数式编程就像股新思潮一般瞬间席卷整个技术圈。虽然博主接触到的前端技术并不算深入,可这并不妨碍我们通过类似概念的延伸来理解这种概念。首先,函数式编程是一种编程范式,而我们所熟悉的常见编程范式则有命令式编程(Imperative Programmming)函数式编程(Functional Programming)逻辑式编程(Logic Programming)、**声明式编程(Declarative Programming)响应式编程(Reactive Programming)**等。现代编程语言 在发展过程中实际上都在借鉴不同的编程范式,比如 Lisp 和 Haskell 是最经典的函数式编程语言,而 SmartTalk、C++和 Java 则是最经典的命令式编程语言。微软的 C#语言最早主要借鉴 Java 语言,在其引入 lambda 和 LINQ 特性以后,使得 C#开始具备实施函数式编程的基础,而最新的 Java8 同样开始强化 lambda 这一特性,为什么 lambda 会如此重要呢?这或许要从函数式编程的基本术语开始说起。

什么是函数式编程

我们提到函数式编程是一种编程范式,它的基本思想是将计算机运算当作是数学中的函数,同时避免了状态和变量的概念。一个直观的理解是,在函数式编程中面向数据,函数是第一等公民,而我们传统的命令式编程中面向过程,类是第一等公民。为什么我们反复提到 lambda 呢?因为函数式编程中最重要的基础是 lambda 演算(Lambda Calculus),并且 lambda 演算的函数可以接受函数作为参数和返回值,这听起来和数学有关,的确函数式编程是面向数学的抽象,任何计算机运算在这里都被抽象为表达式求值,简而言之,函数式程序即为一个表达式。值得一提的是,函数式编程是图灵完备的,这再次说明数学和计算机技术是紧密联系在一起的。虽然在博主心目中认为,图灵这位天纵英才的英国数学家,是真正的计算机鼻祖,但历史从来都喜欢开玩笑的,因为现代计算机是以冯.诺依曼体系为基础的,而这一体系天生就是面向过程即命令式的,在这套体系下计算机的运算实则是硬件的一种抽象,命令式程序实际上是一组指令集。因此,函数式程序目前依然需要编译为该体系下的计算机指令来执行,这听起来略显遗憾,可这对我们来说并不重要,下面让我们来一窥函数式编程的真容:

squares = map(lambda x: x * x, [0, 1, 2, 3, 4])  
print squares

这是使用 Python 编写的函数式编程风格的代码,或许看到这样的代码,我们内心是完全崩溃的,可是它实现得其实是这样一个功能,即将集合{0, 1, 2, 3, 4}中的每个元素进行平方操作,然后返回一个新的集合。如果使用命令式编程,我们注定无法使用如此简单的代码实现这个功能。而这个功能在.NET 中其实是一个 Select 的功能:

int[] array = new int[]{0, 1, 2, 3, 4};
int[] result = array.Select(m => m * m).ToArray();

这就是函数式编程的魅力,我们所做的事情都是由一个个函数来完成的,这个函数定义了输入和输出,而我们只需要将数据作为参数传递给函数,函数会返回我们期望的结果。好了,下面再看一个例子:

sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])
print sum

即使我们从来没有了解过函数式编程,从命名我们依然可以看出这是一个对集合中的元素求和的功能实现,这就是规范命名的重要性。幸运的是.NET 中同样有类似的扩展方法,我喜欢 Linq,我喜欢 lambda:

int[] array = new int[]{0, 1, 2, 3, 4};
int result = array.Sum();

考虑到博主写不出更复杂的函数式编程的代码示例,这里不再列举更多的函数式编程风格的代码,可是我们从直观上来理解函数式编程,就会发现函数式编程同 lambda 密不可分,函数在这里扮演着重要的角色。好了,下面我们来了解下函数式编程中的常用术语。

函数式编程的常用术语

函数式编程首先是一种编程范式,这意味着它和面向对象编程一样,都是一种编程的思想。而函数式编程最基本的两个特性就是不可变数据和表达式求值。基于两个基础特性,我们延伸出了各种函数式编程的相关概念,而这些概念就是函数式编程的常用术语。常用的函数式编程术语有高阶函数、柯里化/局部调用、惰性求值,递归等。在了解这些概念前,我们先来理解,什么是函数式编程的不可变性。不可变性,意味着在函数式编程中没有变量的概念,即操作不会改变原有的值而是修改新产生的值。举一个基本的例子,.NET 中 IEnumerable接口提供了大量的如 Select、Where 等扩展方法,而这些扩展方法同样会返回 IEnumerable类型,并且这些扩展方法不会改变原来的集合,所有的修改都是作用在一个新的集合上,这就是函数式编程的不可变性。实现不可变性的前提是纯函数,即函数不会产生副作用。一个更为生动的例子是,如果我们尝试对一个由匿名类型组成的集合进行修改,会被提示该匿名类型的属性为只读属性,这意味着数据是不可改变的,如果我们要坚持对数据进行“修改”,唯一的方法就是调用一个函数。

高阶函数(Higer-Order-Function)

高阶函数是指函数自身能够接受函数,并返回函数的一种函数。这个概念听起来好像非常复杂的样子,其实在我们使用 Linq 的时候,我们就是在使用高阶函数啦。这里介绍三个非常有名的高阶函数,即 Map、Filter 和 Fold,这三个函数在 Linq 中分别对应于 Select、Where 和 Sum。我们可以通过下面的例子来理解:

  • Map 函数需要一个元素集合和一个访问该元素集合中每一个元素的函数,该函数将生成一个新的元素集合,并返回这个新的元素集合。通过 C#中的迭代器可以惰性实现 Map 函数:
IEnumerable<R> Map<T,R>(Func<T,R> func, IEnumerable<T> list)
{
	foreach(T item in list)
		yield return func(item);
}
  • Filter 函数需要一个元素集合和一个筛选该元素结合的函数,该函数将从原始元素集合中筛选中符合条件的元素,然后组成一个新的元素集合,并返回这个新的元素集合。通过 C#中的 Predicate委托类型,我们可以写出下面的代码:
IEnumerable<T> Filter<T>(Predicate<T> predicate, IEnumerable<T> list)
{
	foreach(T item in list)
	{
		if(predicate(item))
			yield return item;
	}
}
  • Fold 函数实际上代表了一系列函数,而最重要的两个例子是左折叠和右折叠,这里我们选择相对简单地左折叠来实现累加的功能,它需要一个元素集合,一个累加函数和一个初始值,我们一起来看下面的代码实现:
R Fold<T,R>(Func<R,T,R> func, IEnumerable<T> list, R startValue = default(R))
{
	R result = startValue;
	foreach(T item in list)
		result = func(result, item);
	return result;
}

相信现在大家应该理解什么是高阶函数了,这种听起来非常数学的名词,当我们尝试用代码来描述的时候会发现非常简单。相信大家都经历过学生时代,临近期末考试的时候死记硬背名词解释的情形,其实可以用简洁的东西描述清楚的概念,为什么需要用这种方式来理解呢?为什么我这里选择了 C#中的委托来编写这些示例代码呢?自然是同样的道理啦,因为我们都知道,在 C#中委托是一种类似函数指针的概念,因为当我们需要传入和返回一个函数的时候,选择委托这种特殊的类型可谓是恰如其分啦,这样并不会影响我们去理解高阶函数。

柯里化(Curring)/局部套用

柯里化(Curring)得名于数学家 Haskell Curry,你的确没有看错,这位伟大的数学家不仅创造了 Haskell 这门函数式编程语言,而且提出了局部套用(Currin)这种概念。所谓局部套用,就是指不管函数中有多少个参数,都可以函数视为函数类的成员,而这些函数只有一个形参,局部套用和部分应用息息相关,尤其是部分应用是保证函数模块化的两个重要技术之一(部分应用和组合**(Composition)**是保证函数模块化的两个重要技术)。众所周知,在 C#中一个函数一旦完成定义,那么它的参数列表就是确定的,即相对静态。它不能像 Python 和 Lua 一样去动态改变参数列表,虽然我们可以通过缺省参数来减少参数的个数,可是在大多数情况下,我们都需要在调用函数前准备好所有参数,而局部套用所做的事情与这个理念截然相反,它的目标是用非完全的参数列表去调用函数。我们来一起看下面这个例子:

Func<int,int,int> add = (x,y) => {return x + y;};

这是一个由匿名方法定义的委托类型,显然我们需要在调用这个方法前准备好两个参数 x 和 y,这意味着 C#不允许我们在改变参数列表的情况下调用这个方法。而通过局部套用:

Func<int,int,int> curriedAdd => (x) =>
{
	return (y) => { return x + y;};
};

实际上在这里两个参数 x 和 y 的顺序对最终结果没有任何影响,我们这样写仅仅是为了符合人类正常的认知习惯,而此时我们注意到我们在调用 curriedAdd 时会发生质的的变化:

//x和y同时被传入add
add(x,y)
//x和y可以不同时被传入curriedAdd
curriedAdd(x)(y);

而如果我们将这里的函数用 Lambda 表达式来表示,则会发现:

Func<int,int,int> add = (x,y) => return x + y;
Func<int,Fucn<int,int>> curriedAdd = x = > y => x + y;

至此,对一般的局部套用,存在:

Func<...> f = (part1, part2, part3, ...) => ... 可转换为:
Func<...> cf = part1 => part2 => part3 ... => ...

则称后者为前者的局部套用形式。

惰性求值

我们在前文中曾经提到过,在函数式编程中函数是第一等公民,而这里的函数更接近数学意义上的函数,即将函数视为一个可以对表达式求值的纯函数,所以我们这里自然而然地就提到了惰性求值。首先,博主这里想说说求值策略这个问题,求值策略通常有严格求值和非严格求值两种,而对 C#语言来讲,它在大多数情况下使用严格求值策略,即参数在传递给函数前求值。与之相对应的,我们将参数在传递给函数前不进行求值或者延迟求值的这种情况,称为非严格求值策略。一个经典的例子是 C#中的“短路”效应:

bool isTrue = (10 < 5) && (MyCheck())

因为在这里表达式的第一部分返回值为 false,因此在实际调用中第二部分根本不会执行,因为无论第二部分返回 true 还是 false,实际上对整个表达式的结果都不会产生影响。这是一个非常经典的非严格求值的例子,同样的,布尔运算中的"||“运算符,同样存在这个问题。所以,至此我们可以领会到惰性求值的优点,即使程序的执行效率更好,尤其是在避免高昂运算代价的时候,我们要牢记:懒惰是程序员的一种美德,使用更简洁的代码来满足需求,是一名游戏程序员的永恒追求。我们可以联想那些在代码片段中优先 return 的场景,这大概勉强可以用这种理论来解释吧!例如我们强大的 Linq,原谅我如此执著于举 Linq 的例子,Linq 的一个特点是当数据需要被使用的时候开始计算,即数据是延迟加载的,而在此之前我们所有对数据的操作,从某种意义上来讲,更像是定义了一系列函数,这好像和数据库中的事务非常相近啦,其实这就是在告诉我们,懒惰是一种美德啊,哈哈!

函数式编程的利弊探讨

好了,现在让我们从函数式编程的各种术语中解放出来,高屋建瓴般地从更高的层面上探讨下函数式编程的利弊。当你讨论一种东西的利弊时,一种习惯性的做法是找一种东西来和它作比较,如果 Windows 和 Linux、SQL 和 NoSQ、面向对象和函数式…等等,我们常常关注一件事物的利弊,而非去寻找哪一个是最好。可惜自以为是的人类,常常以此来自我设限,划分各自的阵营,这当真是件无聊的事情,就像我一直不喜欢 SQL 和正则表达式,所以我就去了解数据库的设计、模式匹配相关内容,最终感觉颇有一番收获,我想这是我们真正的目的吧!好了,下面我们说说函数式编程有哪些优缺点?首先,函数式编程极大地改善了程序的模块化程度,高阶函数、递归和惰性求值让程序充分函数化,函数式让编程可以以一种声明式的风格来增强程序语义。当然,函数式编程的缺点是,我们这个现实世界本来就不是纯粹的,函数式编程强调的数据不可变性,意味着我们无法去模拟事物状态变化,因此我们不能为了追求无副作用、无锁而忽视现实,这个世界上总有些肮脏的问题,无法让我们用纯函数的思维去解决,这个时候我们不能说要让设计去适应这个世界,任何技术或者框架的诞生归根到底是为了解决问题,而函数式编程或者是面向对象编程,本质都是一种编程思想,我们最终是为了解决问题,就像这个世界有时候并不是面向对象的,我们用面向对象来描述这个世界,或许仅仅是我们自己的理解,这个世界到底是什么样子的,大概只有上帝会知道吧!

本文小结

本文主要对函数式编程及其常见术语进行了简要讨论,主要根据《C#函数式程序设计》一书整理并辅以博主的理解而成。首先,函数式编程中强调无状态、不可变性,认为函数是一等公民,并且在函数式编程中每一个函数都是一个纯函数,它是数学概念咋计算机领域的一种延伸,和冯.诺依曼计算机体系不同,函数式编程的核心思想是以 lambda 演算为基础的表达式求值,并且函数式编程强调无副作用。本文对函数式编程中的常见术语如高阶函数、局部套用/柯里化、惰性求值等结合 C#语言进行了简单分析。或许对我们而言,函数式编程是一个新鲜事物,可正如我们第一次接触面向对象编程时一样,我们并不知道这样一种编程思想会持续到今天。我不认为函数式编程会彻底替代面向对象编程,就像 Web 开发无法彻底替换原生开发一样,函数式编程会作为面向对象的一种延伸和补充,所以本文对函数式编程的理解实际上是非常肤浅的,可这个世界本来就是在不断变化的,希望我们可以在恰当的场景下去权衡选择什么样的技术,对这个世界而言,我们永远都是探索者,或许永远都不存在完全能满足现实场景的编程范式吧!