【函数式编程】【C#/F#】第五讲 函数式编程中的错误处理,副作用管理

F#语言的数据库交互 F#是微软开发的一种功能编程语言,它基于.NET平台。F#允许开发者以更简洁、优雅的方式编写代码,同时能够保持高性能,与C#等传统语言很好地兼容。F#特别适合处理复杂的业务逻辑,尤其在数据分析和科学计算领域得到了广泛应用。F#在数据库交互方面提供了丰富的选择,从使用ORM框架、直接执行SQL命令,到异步处理和错误管理,都能轻松实现。通过合理利用这些特性,开发者可以高效地构建功能丰富的应用。F#不仅在数据处理和分析领域具有优势,其强大的并发和异步处理能力,使得它也适合构建高性能的Web应用。 阅读详情

在上一讲中,我们学习了在函数式编程中一个,非常重要的概念。藉由这个武器,我们将来挑战我们最开始做讲述的概念,代码的副作用。当然,如果你还记得的话,程序的异常也是副作用的其中一种。

复习时间:会影响外界状态(状态突变)、使用到了IO(输入输出)的功能、改变输入参数,或是抛出异常叫做副作用

如果你觉得有部分知识点已经忘了,可以回顾一下之前的内容

【C#/F#】「函数式编程」序章 - 不可变与表达式两大戒律https://blog.youkuaiyun.com/scixing/article/details/144569395?spm=1001.2014.3001.5502

在本期开始之前需要安装LanguageExt库(nuget)

并引入以下两行

using LanguageExt;
using static LanguageExt.Prelude;

单子在FP之中有一个重要的任务,那就是管控副作用的范围。这也是我们一直想要强调的,副作用在程序中不可避免。但我们不应该让副作用在程序中到处都是,而是要牢牢地把控住其能影响的范围。副作用不可或缺,但其存在具有两面性。而单子的存在,能最大限度的减少其不好的一面。

之前我们已经了解到了Option这个单子,今天我们还将遇见一个新的单子

我们先来想象一下option是否真的够用

Either<L,R>

这是我们新的单子!也有称之为Result(Result<Ok, Error>(F#就是如此)),不过Result在LanguageExt中有另外一个意义,这里我们还是用Either作为介绍。

Either内部有两类值,一般我们称之为Left和Right。

当然按照字面意义,Left会写在左面,而Right在英文中也有正确的意思,所以Either代表的是一个成功的值Right或者一个失败的信息Left。

相比于Option,Either能够传达更多更详细的信息,所以在实际使用时可能会见到更多的使用Either在代码中进行流转。(因为option只能表达数据的缺失这一种状态)

事不宜迟,我们先来写一下Either的Bind和Map的函数签名...

图片

欸等等? Either有两个有效数据我们应该怎么写?

确实是这样,不过通常情况下我们只需要关心Right的数据即可

bind:  (Either<L, R>, (R -> Either<L, R1>)) -> Either<L, R1>
map:  (Either<L, R>, (R -> R1)) -> Either<L, R1>

以下是Map与Bind的实际实现(当然 安装了库的我们不需要手动自己实现)

public static Either<L, R1> Map<L, R, R1>(this Either<L, R> either,
        Func<R, R1> f) =>
        either.Match<Either<L, R1>>(
            Right: r => Right(f(r)),
            l => Left(l)
        );
    public static Either<L, R1> Bind<L,R,R1>(this Either<L, R> either,
        Func<R, Either<L, R1>> f) =>
        either.Match<Either<L, R1>>(
            Right: r => f(r),
            l => Left(l)
        );

同样的非常简单,当Either中值为Left的时候,bind,map都不会再影响到Either的值,当然如果你想对其中的Left值进行操作,试着实现一下MapLeft与BindLeft!

现在我们得到了一种保存错误情况下信息的方式,我们还有必要在代码中抛出错误吗?throw会肆意的中断我们的程序,这其实在大部分时候往往不是我们所想要的。试着思考一下,当我们去问一个问题的时候,我们希望得到什么样的回复。

如果能得到正确的回复当然最好不过,如果无法解决的时候,你希望获得的是

1. 一个什么都没有的回复(Option.None)

2. 一个告诉你为什么问题不能回答的原因(Either.Left(reason))

3. 不回复,然后直接打了一拳,但你可以在医院里知道打你造成的结果(throw)

你会选择什么呢?相信答案显而易见,当然可能对throw的描述略显夸张,但抛出错误的语义可能会跨越非常大的代码范围,如果没有处理,也可能直接使程序崩溃。这不是一件好事,我们需要控制他的范围。

实战

现在来看一种情况,你是否会遇到过这种类似这种每一步都需要尝试校验,最终才能得到结果的代码

bool DoSth1(int id)
{
    var equip = Equipment.Get(id);
    if (equip == null)
    {
        throw new Exception("No equipment found");
    }
    var open = equip.Open();
    if (!open)
    {
        throw new Exception("open failed");
        return false;
    }
    var dosth = equip.DoSth();
    if (!dosth)
    {
        throw new Exception("dosth failed");
        return false;
    }
    var close = equip.Close();
    if (!close)
    {
        throw new Exception("close failed");
        return false;
    }
    return true;
}

难以想象的复杂,开发者最后有可能最后为了缩减代码,将计就计,让错误直接在方法内部调用的时候就直接抛出。这是一种方法,但是毫无疑问,这会造成全局乱成一锅粥,随便调用一个接口都要承担程序直接崩溃的风险!

而如果你使用了Either, 并聪明的使用了Bind去连接函数操作,你会发现你得到了相当简洁的一个代码,并且不会有任何崩溃的风险

Either<string, EquipmentF> DoSth(int id) =>
    id.Get()
        .Bind(EquipmentExtensions.Open)
        .Bind(EquipmentExtensions.Pre)
        .Bind(EquipmentExtensions.DoSth)
        .Bind(EquipmentExtensions.Close);

当我们拿到结果后,我们会得到一个可能的结果,这个结果可能是Right, 那么我们将拿到正确的值。也可能是Left,我们会拿到最早导致错误的信息。当然既然错误了,错误之后的步骤自然没有执行。

以下便是FP中代码运行的流程

图片

(也有将形如这种的编程模式称之为面向铁路编程)

可以看到,新的方法让我们回避了多次无聊的判断,并且让程序的出口也只变成了唯一一个,同时如果我们希望的话,也可以直接使用=>来返回最终的结果。我们成功利用了单子,将错误牢牢地管控在了一个很小的范围中,不必担心突如其来的程序无法处理的错误。

try..catch

即使圣人不偏不倚的走在了正确的道路上,也依然难以避免这样或者那样的错误。

没错事实上很多异常我们没有办法避免,我们还是需要Try来帮助我们捕获一些异常。

不过在C#之中,try是一个语句。这使得try的语句连贯性不高,我想编写过类似代码的同学一定有类似的体验

var t = SomeMethod(); // 这步可能出错
DoSth(t);

如果SomeMethod是一个可能出错的代码,并且我们也没有办法对SomeMethod本身进行重构。我们可能需要try来帮我们捕获代码的错误。我们可能不得不这么修改

SType t = null; // 这步可能出错
try
{
    t = SomeMethod();
}
catch (Exception e)
{
    throw;
}
DoSth(t);

太难看了!

我们不仅要捏着鼻子将一个null赋值,最终有可能还是会抛出错误。代码整体也变得特别割裂,我相信大家都不想经常写这么难看的代码,这也是我们排斥语句的原因之一。

那么根据我们学到的内容来说,我们更期待一种怎么样的写法呢?

我们想到了刚刚所说的Either,在这种情况下,try中有可能有一个有效的值,或是一个错误。根据定义我们可以得出一个类型Either<Exception, R>, 他的左值总是一个错误类型,这可以是一个Either的特化类型,Exceptional<T>, (在LanguageExt中,它叫做Result<T>),现在有了我们想要的结果的类型,接下来要做的,则是规避掉这个疯狂的语法,转换为语句

我们当然想要这么写

var t = 
    try
    {
        return SomeMethod();
    }
    catch (Exception e)
    {
        return e;
    }
DoSth(t);

这种写法,清晰,明确,且没有复杂的赋值顺序,这在F#有类似的实现,但在C#之中,我们没有类似的语法帮我们实现这一点。所以我们需要自己创建一个函数帮我们解决这一切。

其实我们需要的只是一段代码的封装, 接下来写出我们所需要的函数签名

Run<T>:  (()->T) -> Exceptional<T>

我们将这个()->T声明为Try<T>, 并编写出Run/Invoke函数

public delegate Result<T> Try<T>();
    public static Result<T> Run<T>(this Try<T> f)
    {
        try
        {
            return f();
        }
        catch (Exception e)
        {
            return new(e);
        }
    }

我们简单的Try就完成了,在LanguageExt中,Try也提供Bind和Map函数,所以同样也是一个单子,有兴趣的同学,也可以尝试实现这一个工作

在F#中,我们直接支持try作为表达式使用,非常方便。

验证器

如果仔细观察Exception, 可以注意到,Exception都是一些意料之外的错误,其实在代码中我们还可能遇到一些错误,这些错误不是致命性的,但是可能有悖业务逻辑,例如我们需要判定某人年龄是否大于18岁,或是是否是教师。这些意料之内的不合理,我们更常使用验证器来对齐收集,这是另一个版本的Etiher,我们通常用它来收集数据/逻辑不合理的等情况,

这时候我们可能会使用另一个Either变体 Validation,

Either<IEnumerable<Error>,T>

篇幅问题在本片就不继续衍生了,在之后的实战演练中,我们会更深刻的理解这部分内容。

Bilibili:@无聊的年

微信公众号:@scixing的炼丹炉

声明式编程与函数式思维:.NET开发者元能力升级指南 声明式编程是一种以‘做什么’(What)而非‘怎么做’(How)为核心的软件构造范式,其本质是通过抽象语法和领域特定语言(DSL)降低人类意图与机器执行之间的认知鸿沟。它依托函数式思维——如不可变性、纯函数、高阶操作——在C# LINQ、F# Actor模型及自定义内部DSL中落地;技术价值在于提升代码可读性、并发安全性与跨平台适配能力,广泛应用于高并发风控、实时流处理与医疗规则引擎等场景。本文结合Anders Hejlsberg经典趋势判断与.NET十二年工程实践,系统拆解如何将声明式理念转化为可测量、可 阅读详情

相关推荐

【三维重建】基于多视角图像的三维重建

基于多视角图像的三维重建》

m0_64446860的博客 8761

F#函数式编程实战:类型驱动开发与.NET生产力提升

函数式编程正从学术概念走向工业级应用,其核心价值在于通过强类型系统和不可变数据结构提升软件可靠性与可维护性。F#作为.NET平台原生支持的函数式语言,融合了Hindley-Milner类型推导、代数数据类型(ADT)和异步工作流等关键技术,实现编译期错误捕获、零运行时null异常及高并发安全。相比C#或Python,F#以极简语法承载严密逻辑,天然适配领域驱动设计(DDD)、ETL数据处理与高可用API开发等典型企业场景。本文聚焦F#在金融建模、实时风控与医疗AI等生产环境中的落地实践,详解如何用Resul

a6t2007的博客 411

OpenEuler内核编译及替换

一、内核源码下载 在gitee上下载OpenEuler内核源码 下载地址 选择需要的内核版本源码压缩包进行下载 二、内核编译 1.解压缩 将下载好的源码进行解压 tar xvf openeuler-kernel-4.19.90-2107.1.0.tar.gz 2.清理源代码树 解压后得到一个名为kernel的内核源码文件夹,进入后执行命令 make mrproper 清理过去内核编译产生的config,第一次编译时可不执行此命令 3.生成内核配置文件.config 先将将系统原配置文件拷贝过来,原

m0_56602092的博客 2万+

F#语言的数据库编程

F#是一种多范式的编程语言,主要基于函数式编程。它由Microsoft开发,适用于 .NET 平台。F#语言设计的核心目标是提高代码的简洁性、可读性和可维护性。与其他编程语言相比,F#在处理数据和并发问题上更具表达力,特别是在处理复杂的数据结构和函数时,能够提供更紧凑的语法。在本文中,我们探讨了F#语言在数据库编程中的应用,包括如何使用ADO.NET进行基本的数据库操作及如何使用ORM(如Entity Framework Core)简化数据访问的过程。

2501_90435796的博客 970

LabVIEW内存优化实战:用DVR和队列高效处理大型数组,告别数据副本

本文深入探讨LabVIEW内存优化技术,重点介绍数据值引用(DVR)和单元素队列(SEQs)在高效处理大型数组中的应用。通过对比传统方法与现代引用技术的性能差异,提供减少内存副本、提升程序效率的实战方案,帮助开发者在测试测量、图像处理等数据密集型场景中实现显著性能提升。

weixin_30835649的博客 1062

PaddleOCR多进程GPU推理实战:200万图片高效处理方案

本文详细介绍了PaddleOCR多进程GPU推理在高效处理200万张图片OCR任务中的实战方案。通过优化GPU利用率、多进程并发和关键参数调优,实现了从单进程3000张/小时到8进程3万张/小时的性能飞跃,为大规模OCR处理提供了可靠解决方案。

prometheus9mon的博客 261

C#中的Nutshell函数式编程

目录 介绍 函数编程定义 函数属性 纯度 头等函数 闭包的概念 成为函数式 函数式实用程序 纯度重要性 头等的重要性 函数编程和面向对象编程 集成函数编程 总结 下载源码 - 7.9 KB 介绍 如今,函数性编程正在流行。我们应该问自己有两个问题: 为什么这种变化发生在程序员和语言创造者身上? 以及如何证明这一点? 有很多函数语言: C#,C ++,Java。 Lisp,JavaScript,Python,Smalltalk,Ruby,OCaml,F#,Scala,Groovy,D,Erlang,Cloju

寒冰屋的专栏 608

ROS牛耕法全覆盖规划:从算法原理到清洁机器人实战解析

本文深入解析ROS牛耕法全覆盖规划算法在清洁机器人中的应用,从算法原理到实战优化技巧全面覆盖。详细介绍了地图预处理、区域分解和路径优化等关键步骤,并分享实际案例中的解决方案,帮助开发者提升机器人清扫效率和覆盖率。

weixin_27013291的博客 392

F#语言的并发编程

并发编程是指在同一时间段内处理多个任务的设计和实现过程。在计算机科学中,这意味着多个计算任务能够在同一时间段内发生,而不是顺序处理。并发编程的主要目的是提高程序的执行效率和响应能力,特别是在处理I/O操作、网络请求和计算密集型任务时。F#语言凭借其强大的函数式编程特性与.NET平台的强大支持,为并发编程提供了简洁而高效的方式。通过使用异步工作流、、延迟控制以及并发工具,开发者能够在F#中轻松实现高效的并发应用。

2501_90199971的博客 469

8155源码编译DSP(ubuntu环境下)

一.设置环境变量 -users:/$ sudo gedit ~/.bashrc #set adsp cdsp tools path #export ARMLMD_LICENSE_FILE=<LICENSE FILE INFO> #export ARMTOOLS=RVCT221 #export ARMROOT=/pkg/asw/compilers/arm/ADS1.2 #export ARM_COMPILER_PATH=/pkg/qct/software/arm/RVDS/2.2BLD593

Johnny2004的专栏 1678

【乐企】开票控制服务

例子:企业信息初始化。

九七的博客 453

F#语言的云计算

F#是一种多范式编程语言,支持函数式、面向对象和命令式编程。它是.NET平台上的一员,能够与C#、VB.NET等语言无缝协作。F#语言的设计理念强调表达能力和简洁性,使得开发者可以更加高效地编写和维护复杂的代码。函数式编程:F#是一种纯函数式编程语言,支持高阶函数和惰性求值,使得代码更加简洁和可读。类型推导:F#具有强大的类型推导系统,允许开发者无需显式声明变量类型,从而提高开发效率。模式匹配:F#提供了强大的模式匹配功能,使得处理复杂数据结构更加方便。异步编程。

2501_90770606的博客 971

OpenClaw小白使用全攻略

OpenClaw全能技能解锁指南:从基础到进阶 摘要: 本文提供OpenClaw AI智能体的完整使用教程,帮助用户从安装后"不知道如何用"到熟练掌握核心功能。教程包含:1)控制台5大区域详解和3个必做初始化设置;2)文件管理、浏览器自动化、文档生成等5大基础功能实操,每个功能配有详细操作步骤、指令模板和效果演示;3)10+高频场景解决方案,如整理文件、搜集资料、写总结等;4)进阶技巧和常见问题解答。通过清晰的图文指引,即使电脑小白也能快速上手,实现办公自动化、提升工作效率。

通俗易懂学IT 3060

F#语言的多线程编程

F# 是一种函数式编程语言,基于 .NET 平台,能够方便地与其他 .NET 语言(如 C# 和 VB.NET)进行互操作。其设计目标是提高开发效率,让开发者能够用较少的代码实现复杂的逻辑。同时,F# 支持多种编程范式,包括函数式编程、面向对象编程和命令式编程。得益于这些特性,F# 成为处理并发和异步任务的理想选择。

2501_90494494的博客 453

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

F#快速入门:用函数式流水线处理销售数据

函数式编程是一种以数据流和不可变性为核心的思想范式,其核心原理在于通过高阶函数、管道操作和模式匹配,将业务逻辑表达为清晰的数据转换过程。相比面向对象的命令式步骤清单,它天然具备线程安全、易测试和低副作用的技术价值。典型应用场景包括数据清洗、ETL管道、领域建模及金融计算等对逻辑准确性与可维护性要求高的工程领域。本文基于F#语言,聚焦真实销售CSV分析任务,通过`List.groupBy`、`|>`管道和`Option`类型等关键热词,演示如何在5分钟环境搭建后,写出比C#少40%行数且编译期防错的生产级脚本

diaoqi6581的博客 388

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

深入dos编程_Julia、Rust、F#、Go编程语言游览和感想

最近几周先后游览了Julia、Rust、F#、Go四种编程语言,了解了javascript和vue的一点前端知识。说是游览,是没写代码光看文档,当然必定是很浅的。肯定有很多错误见解,欢迎斧正。Julia看起来是一种静态和动态混合语言,动态类型采用RC,能推断出类型的地方都用静态,静态搞不定的地方好像是自动装箱成动态类型,需要速度的地方可以获得和C语言一个数量级的性能。我觉得Julia的这种混合模式...

weixin_39902875的博客 593

F#语言的云存储

F#是一种函数式编程语言,属于.NET生态系统。它不仅支持函数式编程的范式,还兼容面向对象编程和命令式编程。在数据处理、并发编程和云计算等领域,F#体现出了不凡的能力。Azure Blob Storage是Microsoft Azure提供的一种服务,专为存储大量非结构化数据而设计。用户可以在Azure Blob Storage中存储图像、视频、备份文件等数据。

2501_91525713的博客 565

F#语言的微服务

微服务是一种系统架构风格,它将一个大型复杂的应用程序拆解为一组小的、松耦合的服务。每个微服务可以独立运行、扩展和部署,各个服务之间通过网络进行通信。独立性:每个服务独立部署,可以使用不同的技术栈。灵活性:服务可以根据需求进行独立扩展。可维护性:小型服务易于理解和维护。容错性:某个服务的失败不会影响整个系统。F#是由微软开发的功能性编程语言,支持面向对象和命令式编程。它是一种静态类型语言,具有强大的类型推断能力,并且能够与.NET生态系统无缝集成。

2501_91339208的博客 1163

F#语言的测试开发

F#是一种函数式编程语言,最初是由微软开发的,旨在提高开发效率和代码的可读性。它的语法简洁,提供了模式匹配、类型推断和Immutable数据结构等特性,能够让开发者更专注于算法和业务逻辑的实现。同时,F#支持与C#和VB.NET无缝集成,开发者可以在同一个解决方案中使用不同的语言。函数式编程的特点在于高度的抽象化和更少的副作用,这为测试提供了良好的基础。由于函数是“第一等公民”,我们可以将它们作为参数传递、返回,甚至是动态生成。这样的特性使得测试的实施变得更加容易。

2501_90443936的博客 718

F#语言的安全开发

F#是由微软主导开发的一种多范式编程语言,主要支持函数式编程,但也支持面向对象和命令式编程。作为.NET生态系统的一部分,F#与C#、VB.NET等语言一同运行在Common Language Runtime (CLR) 上,从而能够直接访问.NET库和组件。F#的设计旨在提高开发人员的生产力,并通过类型系统的优势减少常见的编程错误。它的类型推断、不可变数据结构和一等函数等特性,使得代码更加简洁和易于维护。

2501_90407307的博客 423

F#语言的物联网

物联网(IoT)是指通过互联网将各种物理设备连接起来,实现信息的采集、传输、分析和反馈的智能网络。这些设备可以是传感器、家电、车辆、工业设备等,所产生的数据可以用于优化管理、提升效率、改善用户体验。物联网的核心在于“连通性”,而实现这一点的关键则在于软件开发。F#是一种多范式编程语言,主要基于函数式编程。它不仅支持函数式编程,还兼容面向对象和命令式编程,使得开发者可以在不同的场景下选择最合适的编程风格。类型推断。

2501_90485558的博客 315

F#语言的移动应用开发

F#是一种适用于.NET平台的多范式编程语言,主要支持函数式编程。它由微软开发并维护,旨在提供强大的类型安全性和代码简洁性。F#的核心特性包括模式匹配、不可变数据结构、高阶函数和强大的类型推导等。这些特性使得F#在构建复杂业务逻辑时变得非常高效与简洁。

2501_90406530的博客 297

第6章第4节:自定义分词器

本节详解Elasticsearch自定义分词器,解析analyzer三段式结构(char_filter→tokenizer→filter),掌握常用组件如ik_max_word、synonym_graph、ngram等。通过_analyze API调试分词效果,实现“索引时扩展同义词、搜索时不带”的生产级方案,提升中文搜索与智能提示体验。

啊渊的专栏 781
上一篇: 如何在各种限制(无网/无IDE)条件下编译我们的.NET程序(.NET Core ver.)
下一篇: 【F#入门】第五讲 模式匹配
scixing
博客等级 码龄2年 216粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值