首页 > 编程语言 >【C#/F#】「函数式编程」序章 - 不可变与表达式两大戒律

【C#/F#】「函数式编程」序章 - 不可变与表达式两大戒律

时间:2024-12-18 23:32:24浏览次数:7  
标签:Console 函数 C# 编程 序章 我们 表达式

首先提振一下读者的信心。

随着发展,并行计算已经成为主流,函数式编程与并行计算有着天然良好的相性

新世代的语言/框架/库,许多都会采用,或者一定程度上使用函数式编程的思想

即使C#未来大概率不会变成一个完整意义上的支持函数式编程语言,但新世代C#许多的特性都是来自函数式思想。或许很多你认为好用的特性,正是来自于函数式。或许其实你已经深深的陷入了函数式的美妙体验之中

再引用一个我喜欢的up的说法:我还没见到有人深入学习函数式编程后再把它抛弃的,只有还没开始学就抛弃的

“本系列部分内容适合有一定编程基础,有C#或类似语言的编程经验更佳。”

C#部分可能会采用LanguageExt库来进行一部分说明

在一切的开始之前,我们也需要说明,函数式编程绝对不是什么万能的编程模式,但了解函数式编程的思想,绝对是一件好事。对于习惯了面对对象的学习者来说, 函数式的一些概念可能非常反直觉,难以理解。如果此时就望而却步,或是嗤之以鼻的话,你可能就错过了一片未曾开拓过的宝藏区域。现在,暂时放弃以前可能被我们视为准则的,时间/空间复杂度,面向对象,设计模式等许多概念,去了解一个不一样的领域。

再次需要说明的是,面向对象与函数式并不是两个冲突的概念。他们完全可以一起使用,纯粹的OOP和FP都是极端的。

不过,OOP解决问题的方式是存在一定“问题”的,FP会是大多数时候更优秀的方案。在后面的内容,我们会对比两者的实现的区别

无论如何,在使用一门函数式语言后,你一定会以全新的视角去看待以前写的代码,获得不一样的认知。

最后,引用我一个非常喜欢的老师引用过的话

"纸上得来终觉浅,绝知此事要躬行"-


希望读者在后面的实操部分,也能亲自编写属于自己的函数式代码,这样便可以更好的理解函数式魅力。


关于FP各个教程中老生常谈的啊或是清晰啊这种。说了对于初学者来说也只是一头雾水。我们且在这里提一句,具体是或不是,等我们学习到了一定程度,自然而然的就会得到自己的答案。


函数式编程,说是模式其实更像是一种戒律,他只是简单的让编程者遵循几个法则,程序自然而然的就会变好了。

在之前的文章中我们可能已经见识到了一大堆概念。但这些都可以理解为,函数式编程的工具。在使用他们之前,我们需要了解函数式编程的核心法则

函数作为第一公民这件事其实也非常重要,即函数与值具有相等的地位,可以作为函数入参,出参,可以被赋值/绑定等操作。不过这个更偏向于概念/语言的先决条件,并且在之前的简介中已经介绍过了,本文中就不再做详细介绍

"变量"不可变法则(不可变性)

这是我们将要学习的第一个法则,在之前我们介绍到了类型不该默认为空时(此处插入文章), 引申思考了“变量”是否应该可变的问题。

我们来简单想一想如果变量(我们不再应该称之为变量,接下来我们将称之为数据)不再可变,我们能获得什么好处

最简单也是最直接的,没错,我们不需要担心这个数据会被意料之外的操作修改。我们可以一直放心的使用他,他会的值从被定义之后就不会再次发生更改。(无需担心篡改)

以此为基础,我们再想的深入一些。如果数据不会被修改,也就是说,我们只能对数据进行读的操作而不是写的操作,显然,并行的读操作直接不会有冲突,不可变的数据天然对并行操作有相性(容易并行)(安全)

这就是最简单,不可变性带给我们的好处。当然此时或许会想,这个不可变好像让人感觉行为受限。 这么想也没错,不过

其一,通过一定的思路转变,我们会在代码中见识到不可变的可能性。

其二,不可变确实也不是绝对的。但与可为空类型一样,不可变应该是默认的行为,可变数据应该要被显示的标记出来,并只在有限的范围内有效(例如性能关键代码等)。

我们来看一个最简单的例子

C#有两种排序的写法 C#为什么又两种排序呢?(因为C#最早是偏向命令式语言)

int[] a = { 5, 3, 1, 4, 6 };
Array.Sort(a);
Console.WriteLine(string.Join(",", a));
//1,3,4,5,6

int[] b = { 5, 3, 1, 4, 6 };
b.Order();
// -> IOrderEnumerable<int>
Console.WriteLine(string.Join(",", b));
// 5,3,1,4,6

可以看到Sort直接改变了原有序列,而Order则是返回了一个新序列

如果我们分别用并行的方式同时排序和求值可以得到如下结果

var c = Enumerable.Range(-100000, 200000)
    .Select(s => (long)s)
    .ToArray();

Random.Shared.Shuffle(c);

var task = () =>
{
    Array.Sort(c);
    Console.WriteLine("1:" + c.Sum());
};

var task2 = () =>
{
    Console.WriteLine("2:" + c.Sum());
};

Parallel.Invoke(task, task2);

// 2:98528530
// 1:-100000

Random.Shared.Shuffle(c);

var task3 = () =>
{
    Console.WriteLine("1:" + c.Order().Sum());
};

var task4 = () =>
{
    Console.WriteLine("2:" + c.Sum());
};

Parallel.Invoke(task3, task4);

// 2:-100000
// 1:-100000

可以看到由于Order不会去影响原来的序列,我们同时对原数组同时做类似类似的任意操作不会有负面影响,这就是不变性给我们带来的好处之一。

表达式法则(表达式)

这是我们第二个要记住的法则,在函数式编程中,我们总是倾向于用表达式而不是语句,那么什么是表达式呢,我们给出一个最简单的定义,如果一段代码,他有一个返回值,那么他就是表达式。表达式的主要工作是返回一个值,而语句既然不返回值了,所他总是会执行一些副作用

为什么要采用表达式呢?

其实原因很简单,正是因为他总是有返回值(我们似乎发现函数式编程似乎总是在追求一致性)所以表达式可以做到将返回值输入下一个函数(函数的组合),同时他也更容易消除副作用。

我们还是以C#来举一个例子,


string info = string.Empty;

UserStatus status = UserStatus.Idle;

switch (status)
{
    case UserStatus.Idle:
        info = "Idle";
        break;

    case UserStatus.Offline:
        info = "Offline";
        break;
    default:
        info = "Unknown";
        Console.WriteLine(1);  // 副作用
        break;
}

我们可以看到我们为了给info准确的值,不得不在switch语句中去修改info的值,其间如果做了一些其他副作用(IO)的事情而不好察觉

而这是我们自C#8引入的switch表达式

string info2 = status switch
{
    UserStatus.Idle => "Idle",
    UserStatus.Offline => "Offline",
    _ => "Unknown"
};

使用switch表达式之时,我们无需再引入任何的副作用。(info是在初始化被赋值而不是后续修改)整体也变得更清晰,更不容易出错。这就是表达式能够带来的好处之一。

再回头看到我们不可变法则中的Linq代码,你会发现Order也是有返回值的,如果我们想进一步对结果操作,可以继续在之后调用扩展函数(函数组合)。表达式总是有值也意味着函数更容易组合使用。

b.Order().Select(s => s * 2).ToArray()

所以在函数式编程中,不希望有所谓的void的存在,void总是不和谐的。事实上C#当中你也不能拿出一个void类型的数据来正常使用。同样是C#中,为什么需要Action和Func两种类型来表达委托,Action的本质其实是Func<Void>,可惜我们做不到这一点。

发现了吗,switch表达式和Linq系列的函数都有浓浓的函数式编程的味道。这正是C#从函数式编程(或是说F#)的思想中吸收过来的功能之一。如果你之前已经喜欢上了这些语法,那么恭喜你,你已经体验到了函数式编程的魅力了

函数式编程不止只有F#这类语言可以使用,C#同样也可以遵循函数式编程的法则,并且会越来越完善。我们将带着函数式编程的武器,继续我们编程的旅程!

最后我们提出一个问题如果一个函数的输入一致时,输出一定一致,且不是返回void的时候,这时候函数变得更接近什么概念了?

标签:Console,函数,C#,编程,序章,我们,表达式
From: https://blog.csdn.net/scixing/article/details/144569395

相关文章

  • 【开源系列】CentOS7下Docker环境搭建开源堡垒机Apache Guacamole
    ApacheGuacamole是一个无客户端远程桌面网关。它支持VNC、RDP和SSH等标准协议。不需要插件或客户端软件。借助HTML5,一旦在服务器上安装了Guacamole,只需使用Web浏览器即可访问桌面。1.Guacamole的架构介绍Guacamole不是一个独立的网络应用程序,而是由多个部分组成的。该......
  • GhostRace: Exploiting and Mitigating Speculative Race Conditions-记录
    文章目录论文背景Spectre-PHT(TransientExecution)ConcurrencyBugsSRC/SCUAF和实验条件流程CreatinganUnboundedUAFWindowCraftingSpeculativeRaceConditionsExploitingSpeculativeRaceConditionspoc修复论文https://www.usenix.org/system/files/useni......
  • windows C#-为枚举创建新方法
    可使用扩展方法添加特定于某个特定枚举类型的功能。示例在下面的示例中,Grades枚举表示学生可能在班里收到的字母等级分。该示例将一个名为Passing的扩展方法添加到Grades类型中,以便该类型的每个实例现在都“知道”它是否表示合格的等级分。usingSystem;namespaceE......
  • #渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍04-盲SQL注入(Blind SQL Injection)
    免责声明本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章阅读。目录一、盲SQL注入(BlindSQLInjection)的概念二、盲SQL注......
  • CVPR-23 Towards Universal Fake Image Detectors that Generalize Across Generative
    论文标题:TowardsUniversalFakeImageDetectorsthatGeneralizeAcrossGenerativeModels论文链接:https://arxiv.org/abs/2302.10174 01摘要翻译随着生成模型的快速发展,人们对通用假图像检测器的需求日益增长。在这项工作中,我们首先展示了现有的模式,即训练一个深......
  • 【C语言的奥秘11】指针知识点总结(续)
    目录一、指针的运算1、指针与整数相加减2、指针-指针(地址-地址)3、指针的关系运算六、指针和数组七、二级指针八、指针数组一、指针的运算1、指针与整数相加减看一下下面的代码:#include<stdio.h>intmy_strlen(char*str){intcount=0;while(*str!='......
  • 169. 大学生HTML5期末大作业 ―【鲸鱼动物主题精品网页 (5页)】 Web前端网页制作 html5+
    目录一、网页概述二、网页文件三、网页效果四、代码展示1.html2.CSS五、总结1.简洁实用2.使用方便3.整体性好4.形象突出5.交互式强欢迎来到我的CSDN主页!Web前端网页制作、大学生期末大作业、课程设计、毕业设计、网页模版源码、学习资料等,更多优质博客文章、网......
  • ZZNUOJ_1341:简单密码破解(C/C++/Java)
    题目描述密码是我们生活中非常重要的东东,我们的那么一点不能说的秘密就全靠它了。哇哈哈. 接下来渊子要在密码之上再加一套密码,虽然简单但也安全。 假设渊子原来一个BBS上的密码为zvbo941987,为了方便记忆,他通过一种算法把这个密码变换成YUANzi1987,这个密码是他的名......
  • How to synchronize Elasticsearch with MySQL
    HowtosynchronizeElasticsearchwithMySQLUsingLogstashtocreateadatapipelinkingElasticsearchtoMySQLinordertobuildanindexfromscratchandtoreplicateanychangesoccurringonthedatabaserecordsintoElasticsearchhttps://towardsdatasc......
  • Cesium-(Primitive)-(CorridorGeometry)
    CorridorGeometry效果:以下是CorridorGeometry类的构造函数属性,以表格形式展示:属性名类型默认值描述positionsArray.定义走廊中心的坐标点数组。widthnumber走廊边缘之间的距离,单位为米。ellipsoidEllipsoidEllipsoid.default用作参考的椭......