万字长文说说C#和Rust_Cangjie们的模式匹配

news/2024/10/19 7:30:01/

C#11新增列表模式后,这个从C#6就开始的特性,算是拼接好了模式匹配的最后一块拼图。对比Swift、Rust或Cangjie这类先天支持模式匹配的语言,能否一战?今天就来全面battle一下。Swift、Rust和Cangjie的模式匹配,一肪相承,这次对比,赶个热度,选取Cangjie。(Swift不熟悉,略懂Rust,据说Rust的模式匹配是抄Swift的?)

一、先说结论

  1. 模式匹配用于判断表达式是否符合某种特征,相对于普通的常量比较,它的比较范围更加丰富,比如可以比较类型。C#中,模式匹配可以用于switch表达式、switch语句、is表达式(常用于if语句);Cangjie中,模式匹配可用于match表达式、if-let表达式和if-while表达式。
  2. C#的模式匹配已经相当完整,覆盖了类型(类型模式)、基本类型(常量模式)、对象类型(属性模式)、元组类型(位置模式)、列表类型(列表模式),同时实现了通配符(弃元)、pattern guard(when)、表达式的捕获(var匹配任意表达式)、关系和逻辑运算等。打补丁走到强过天生模式匹配,相当哇塞、相当逆天了!!!
  3. Cangjie的模式匹配,目前应该还算是一个半成品,比如以下功能“似乎”还没有实现:对象类型匹配、列表类型匹配、关系和复杂逻辑运算等。

二、前置知识点

1.1 C#的switch表达式和is表达式

大多数情况下,我们都是使用switch和if语句,比较少接触switch和is表达式,尤其是switch表达式,好些人可能从来都没用过。

1.1.1 switch表达式
//使用方式1:函数式=============================================================
class Program
{static void Main(string[] args){var score = 100;Console.WriteLine(ReadScore(score));}//使用了常量匹配、逻辑匹配和关系匹配//【=> 参数 switch】,将参数带入方法体static string ReadScore(int num) => num switch{100 => "满分", //每个匹配逗号分隔>=80 and <100 => "A",>=60 and <80 => "B",>=0 and <60 => "不及格",_ => $"无效分{num}"  //读取参数值}; //分号结尾
}//使用方式2:表达式=============================================================
class Program
{static void Main(string[] args){var score = 100;var result = score switch{100 => "满分",>= 80 and < 100 => "A",>= 60 and < 80 => "B",>= 0 and < 60 => "不及格",_ => $"无效分{score}"}; //分号结尾Console.WriteLine(result);}
}
1.1.2 is表达式
//1、在if语句中使用==============================================================
//判断是否为null,常量模式匹配-----------
if (input is null)
{return;
}//判断是否不为null------------------------------
if (result is not null)
{Console.WriteLine(result.ToString());
}//类型/声明模式匹配----------------------------
int i = 34;
object iBoxed = i;
int? jNullable = 42;
//iBoxed表达式的值是否属于int类型,如果是,则将值赋值给变量a
//jNullable表达式的值是否属于int类型,如果是,则将值赋值给变量b
//注意并集条件用&&
if (iBoxed is int a && jNullable is int b)
{Console.WriteLine(a + b);  // 76
}//和switch一样,也可以在方法中使用---------------
//如下is返回一个布尔值。而switch是分支选择。
//以下使用到了属性匹配,详见本文的模式匹配
static bool IsFirstFridayOfOctober(DateTime date) => date is { Month: 10, Day: <=7, DayOfWeek: DayOfWeek.Friday };

1.2 Cangjie的枚举类型

Cangjie们的枚举类型是代数数据类型,枚举值可以带参。框架内置了一个非常重要的泛型枚举Option,用于实现可空(第一次在Rust中看见这种可空实现,还是很震惊的)。可空变量的值,无论是空值还是有值,都被Option类型的枚举值包裹,需要通过模式匹配取出,所以枚举类型和模式匹配的关联性很强。当然,匹配枚举类型只是模式匹配的应用之一。
对于Cangjie的枚举,多说两句。它和Rust一样,是代数数据类型,但又阉割了一些功能,比如命名属性。和Rust一样,使用Option实现了可空类型,但异常又不像Rust一样使用Result<T, E>,而是使用传统的throw和try…catch…finally。无法评价优劣,但撕裂感是比较强烈的。

1.2.1 枚举和内置枚举类型Option
//1、枚举=======================================================================
//Cangjie中枚举选项称为构造器
//构造器可以带参数,而且类似方法,可以重载
//在枚举中,还可以定义成员函数、操作符函数和成员属性,本例略
enum RGBColor {| Red | Green | Blue| Red(UInt8) | Green(UInt8) | Blue(UInt8)
}
//使用枚举
main() {let r = RGBColor.Red //通过【类型名.构造器】创建枚举实例let g = Green //如果没有Green命名冲突,可以直接使用构造器创建枚举实例let b = Blue(100) //带参数的枚举实例,Blue(100)和Blut(101),是不同的值
}//2、Option<T>枚举==============================================================
//Option<T>枚举由框架提供,通过Option<T>实现框架的可空
//定义如下:
enum Option<T> {| Some(T) //有值构造器,其中T为值的类型| None //空值构造器
}
//使用Option<T>枚举
let a: Option<Int64> = Some(100) //Option<Int64>类型,Some(..)直接使用构造器创建实例
let b: ?Int64 = Some(100) //【?Int64】是Option<Int64>的简写,这就接近C#的可空表达了
let c: Option<String> = Some("Hello")
let d: ?String = Nonelet b: ?Int64 = 100 //这个写法不会报错,编译器会使用Some包装。所以b==100,结果是false
let a = None<Int64> //等价于let a: ?Int64=None。如果let a=None,报错,因为无法确定类型
1.2.2 通过match模式匹配获取Option的T值
//1、通过模式匹配获取T值========================================================
func getString(p: ?Int64): String{match (p) {case Some(x) => "${x}" //使用到了绑定匹配,取出x值case None => "none"}
}
main() {let a = Some(1)let b: ?Int64 = Nonelet r1 = getString(a)let r2 = getString(b)println(r1)println(r2)
}//2、当然,Cangjie也提供了解构T值的语法糖-getOrThrow方法
main() {let a = Some(1)let b: ?Int64 = Nonelet r1 = a.getOrThrow()println(r1)try {let r2 = b.getOrThrow()} catch (e: NoneValueException) {println("b is None")}
}
1.2.2 if-let和if-while(类似C#中的is)
//macth用于分支选择,if-let用于真假判断,也可用于提升Some(T)的T值
main() {let result = Option<Int64>.Some(2023)if (let Some(value) <- result) {println("操作成功,返回值为:${value}")} else {println("操作失败")}
}//whilt-let和if-let差不多,只是while通过条件判断来循环执行语句
func recv(): Option<UInt8> {let number = Random().nextUInt8()if (number < 128) {return Some(number)}return None
}main() {//判断循环while (let Some(data) <- recv()) {println(data)}println("receive failed")
}

三、C#的模式匹配

2.1 类型模式

//1、匹配类型===================================================================
/*验证表达式的运行时类型是否与给定类型兼容,兼容有三种情况1)表达式类型是给定类型是的子类2)表达式类型是给定类型是的实现类3)或者存在从给定类型到表达式类型的隐式转化(如装箱拆箱)
*///下例中,object和string存在隐式转化------------------------
object greeting = "Hello, World!";
if (greeting is string message) //如果匹配,表达式结果将赋值给尾随局部变量message
{Console.WriteLine(message.ToLower());  // output: hello, world!
}//下例中,表达式类型分别是给定类型的子类或实现类-------------
//switch表达式用于分支选择,必须穷尽所有情况,最后一个分支通常使用_,类似default
var numbers = new int[] { 10, 20, 30 };
Console.WriteLine(GetSourceLabel(numbers));  // 1var letters = new List<char> { 'a', 'b', 'c', 'd' };
Console.WriteLine(GetSourceLabel(letters));  // 2static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
{//array和collection类似is表达式中的尾随局部变量Array array => 1, //int[]是Array的子类ICollection<T> collection => 2, //List<char>是ICollection<T>的实现类_ => 3, //通配符,匹配任何表达式
};//2、弃元_,用于匹配任何表达式====================================================
//弃元除了用于switch的分支,还可以用于类型模式、位置模式和列表模式,类似占位符
//下例中,弃元用于匹配类型模式中的局部变量,以及通配符
public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{Car _ => 2.00m,null => throw new ArgumentNullException(nameof(vehicle)),_ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

2.2 常量模式

//匹配常量
/*验证表达式的值是否与给定常量匹配(包括相等、比较、逻辑等),适用于以下类型或值1)数值、布尔、字符、字符串2)enum值、const字段3)null(见1.1.2节)
*///1、常量相等匹配===============================================================
public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{1 => 12.0m,2 => 20.0m,3 => 27.0m,4 => 32.0m,0 => 0.0m,_ => throw new ArgumentException($"不支持: {visitorCount}", nameof(visitorCount)),
};//2、常量比较匹配===============================================================
//<、>、<= 或 >= 中的任何一个
static string Classify(double measurement) => measurement switch
{< -4.0 => "Too low",> 10.0 => "Too high",double.NaN => "Unknown",_ => "Acceptable",
};//3、逻辑匹配====================================================================
//使用and or not (),匹配多种模式,除了用于常量匹配,也可用于其它匹配模式
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // 春
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // 夏
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // 冬static string GetCalendarSeason(DateTime date) => date.Month switch
{>= 3 and < 6 => "春",>= 6 and < 9 => "夏",>= 9 and < 12 => "秋",12 or (>= 1 and < 3) => "冬",_ => throw new ArgumentException(nameof(date), $"找不到{date.Month}."),
};

2.3 属性模式

//1、属性模式的基本使用=========================================================
//如果表达式的值是复杂类型,如类、结构体,可以匹配表达式结果(对象实例)的属性值
//属性值可以当成常量,使用相等、比较、逻辑等方式匹配
//如下例中date是DateTime的实例,匹配属性Year为2020,Month为5...
static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };//2、属性模式和类型模式一起使用==================================================
Console.WriteLine(TakeFive("Hello, world!"));  //  Hello
Console.WriteLine(TakeFive("Hi!"));  //  Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6'}));  // 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // abcstatic string TakeFive(object input) => input switch
{//匹配类型stringstring s => s,//匹配类型string,且Length属性即字符长度>=5string { Length: >= 5 } s => s.Substring(0, 5),//匹配类型ICollection<char>(表达式值是其实现类)ICollection<char> symbols => new string(symbols.ToArray()),//匹配类型ICollection<char>,且Count属笥即元素个数>=5ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),//其它匹配情况null => throw new ArgumentNullException(nameof(input)),_ => throw new ArgumentException("Not supported input type."),
};//3、属性模式的嵌套=============================================================
public record Point(int X, int Y);
public record Segment(Point Start, Point End);
//嵌套属性
static bool IsAnyEndOnXAxis(Segment segment) =>segment is { Start: { Y: 0 } } or { End: { Y: 0 } };
//重构一下
static bool IsAnyEndOnXAxis(Segment segment) =>segment is { Start.Y: 0 } or { End.Y: 0 };

2.4 位置模式

//1、解构器======================================================================
//1.1 什么是解构器-----------------------------------
//C# 中的 Deconstruct 方法(解构器)可以让实例能像元组一样被析构
//它是一种公开无返回值的方法,方法名必须为Deconstruct,且所有参数均为out参数
public class Point
{public int X { get; }public int Y { get; }public Point(int x, int y){ X = x; Y = y; }//解构器,public void Deconstruct(out int x, out int y){x = X;y = Y;}
}
//使用解构器分解 Point 对象的成员
Point point = new Point(10, 20);
var (x, y) = point; 
Console.WriteLine($"x: {x}, y: {y}"); //1.2 按位置匹配含有Deconstruct解构器的对象------------
static string Classify(Point point) => point switch
{(0, 0) => "Origin",(1, 0) => "positive X basis end",(0, 1) => "positive Y basis end",_ => "Just a point",
};//2、多参数,也可以使用元组对参数进行包装,然后使用位置匹配=======================
//每个位置,都可以使用常量相等、比较、逻辑、弃元、类型、属性等匹配模式
static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)=> (groupSize, visitDate.DayOfWeek) switch
{(<= 0, _) => throw new ArgumentException("Group size must be positive."),(_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,(>= 5 and < 10, DayOfWeek.Monday) => 20.0m,(>= 10, DayOfWeek.Monday) => 30.0m,(>= 5 and < 10, _) => 12.0m,(>= 10, _) => 15.0m,_ => 0.0m,
};//3、可以使用命名元组元素的名称,或者Deconstruct解构器的参数名称==================
//var用于捕获位置元素,并赋值给局部变量sum
var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}"); 
}
//根据给定整数列表,生成由(求合,求数)组成的命名元组
static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{int sum = 0;int count = 0;foreach (int number in numbers){sum += number;count++;}return (sum, count);
}

2.5 列表模式

//1、列表模式和位置模式比较像,位置模式用于元组,列表模式用于数组或列表=============
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers is [1, 2, 3]);  // True
Console.WriteLine(numbers is [1, 2, 4]);  // False
Console.WriteLine(numbers is [1, 2, 3, 4]);  // False
Console.WriteLine(numbers is [0 or 1, <= 2, >= 3]);  // True//2、弃元_可以匹配任何表达式,var用于捕获位置元素,并赋值给局部变量================
List<int> numbers = new() { 1, 2, 3 };
if (numbers is [var first, _, _])
{Console.WriteLine($"The first element of a three-item list is {first}.");
}//3、切片模式,[..]最多只能使用一次==============================================
Console.WriteLine(new[] { 1, 2, 3, 4, 5 } is [> 0, > 0, ..]);  // True
Console.WriteLine(new[] { 1, 1 } is [_, _, ..]);  // True
Console.WriteLine(new[] { 0, 1, 2, 3, 4 } is [> 0, > 0, ..]);  // False
Console.WriteLine(new[] { 1 } is [1, 2, ..]);  // FalseConsole.WriteLine(new[] { 1, 2, 3, 4 } is [.., > 0, > 0]);  // True
Console.WriteLine(new[] { 2, 4 } is [.., > 0, 2, 4]);  // False
Console.WriteLine(new[] { 2, 4 } is [.., 2, 4]);  // TrueConsole.WriteLine(new[] { 1, 2, 3, 4 } is [>= 0, .., 2 or 4]);  // True
Console.WriteLine(new[] { 1, 0, 0, 1 } is [1, 0, .., 0, 1]);  // True
Console.WriteLine(new[] { 1, 0, 1 } is [1, 0, .., 0, 1]);  // False//4、列表模式其它模式一起使用===================================================
var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
var result = message is ['a' or 'A', .. var s, 'a' or 'A']? $"Message {message} matches; inner part is {s}.": $"Message {message} doesn't match.";

2.6 var和when

//var用于匹配任何表达式(包括 null),并将其结果分配给新的局部变量(捕获)
//when用于对表达式进行进一步判断匹配
public record Point(int X, int Y);static Point Transform(Point point) => point switch
{var (x, y) when x < y => new Point(-x, y),var (x, y) when x > y => new Point(x, -y),var (x, y) => new Point(x, y),
};static void TestTransform()
{Console.WriteLine(Transform(new Point(1, 2)));  // Point { X = -1, Y = 2 }Console.WriteLine(Transform(new Point(5, 2)));  // Point { X = 5, Y = -2 }
}

四、Cangjie的模式匹配

3.1 类型模式(C#的类型模式)

//父类
open class Base {var a: Int64public init() {a = 10}
}
//子类
class Derived <: Base {public init() {a = 20}
}
//匹配类型
main() {var d = Derived()var r = match (d) {case b: Base => b.a //匹配上,b可以是任意标识符,类似C#的尾随变量case _ => 0}
}
//如果不需要捕获变量,可以使用通配符
main() {var d = Derived()var r = match (d) {case _: Base => 1 //匹配上case _ => 0}
}

3.2 常量模式(C#的常量模式)

//不能使用关系运算和复杂的逻辑运算
//Rust中可以使用区间0..10,但Cangjie目前还不支持
main() {let score = 90let level = match (score) {case 0 | 10 | 20 | 30 | 40 | 50 => "D" //【|】表示或case 60 => "C"case 70 | 80 => "B"case 90 | 100 => "A" // Matched.case _ => "Not a valid score"}println(level)
}

3.3 绑定模式(类似C#的var)

//用于匹配任何表达式,并将其结果分配给新的局部变量(捕获)
//注意:如果模式使用了【|】,则不能使用绑定模式
main() {let x = -10let y = match (x) {case 0 => "zero"case n => "x is not zero and x = ${n}" // Matched.}println(y)
}//上例在C#中的实现
class Program
{static void Main(string[] args){var x = -10;var y = x switch{0 => "zero",var n => $"x is not zero and x={n}"};Console.WriteLine(y);}
}

3.4 Tuple模式(类似C#的位置模式)

//下例中,同时使用了绑定模式和通配符
main() {let tv = ("Alice", 24)let s = match (tv) {case ("Bob", age) => "Bob is ${age} years old"case ("Alice", age) => "Alice is ${age} years old" // Matchedcase (name, 100) => "${name} is 100 years old"case (_, _) => "someone"}println(s)
}//注意,同一个Tuple中,不允许使用两个名称相同的绑定
case (x, x) => "someone" //报错

3.5 enum模式(C#中enum模式在常量模式中)

//在C#中,枚举是简单的常量值
//而在Cangjie中,枚举是代数数据类型,表现更加丰富
enum TimeUnit {| Year(UInt64)| Month(UInt64)
}
//通过模式匹配,解构T值
main() {let x = Year(2)let s = match (x) {case Year(n) => "x has ${n * 12} months" // Matched,并解构T值case TimeUnit.Month(n) => "x has ${n} months"}println(s)
}//枚举模式的嵌套,元组模式也可以
enum TimeUnit {| Year(UInt64)| Month(UInt64)
}enum Command {| SetTimeUnit(TimeUnit)| GetTimeUnit| Quit
}main() {let command = SetTimeUnit(Year(2022))match (command) {case SetTimeUnit(Year(year)) => println("Set year ${year}")case SetTimeUnit(Month(month)) => println("Set month ${month}")case _ => ()}
}

3.6 where(类似C#中的when)

enum RGBColor {| Red(Int16) | Green(Int16) | Blue(Int16)
}
main() {let c = RGBColor.Green(-100)let cs = match (c) {case Red(r) where r < 0 => "Red = 0"case Red(r) => "Red = ${r}"case Green(g) where g < 0 => "Green = 0" // Matched.case Green(g) => "Green = ${g}"case Blue(b) where b < 0 => "Blue = 0"case Blue(b) => "Blue = ${b}"}print(cs)
}

http://www.ppmy.cn/news/1501482.html

相关文章

【大数据】:hdfs相关进程启停管理命令

HADOOP_HOME/sbin/start-dfs.sh&#xff0c;一键启动HDFS集群 执行原理&#xff1a; 在执行此脚本的机器上&#xff0c;启动SecondaryNameNode 读取core-site.xml内容&#xff08;fs.defaultFS项&#xff09;&#xff0c;确认NameNode所在机器&#xff0c;启动NameNode 读取wor…

【ffmpeg命令入门】视频的旋转与翻转

文章目录 前言什么时候需要使用旋转与翻转1. 视频拍摄方向不正确2. 视频编辑特效使用什么参数1. 旋转视频 - transpose2. 水平翻转视频 - hflip3. 垂直翻转视频 - vflip 总结 前言 在视频编辑的过程中&#xff0c;我们经常会遇到需要旋转或翻转视频的情况。无论是因为拍摄时相…

C++图书管理系统

目录 实现功能 用户管理 图书管理 借阅与归还 未归还图书 部分效果图 结构体 Book 结构体 User 结构体 源代码 编译时在连接器命令行加入 完整代码 实现功能 用户管理 添加用户&#xff1a;输入用户ID、用户名和密码…

Mysql备份恢复

目录 1.Mysql日志管理 1.1为什么需要日志 1.2日志作用 1.3数据丢失或破坏的原因 1.4常见日志类型之错误日志 1.5常见日志类型之通用查询日志 1.6常见日志类型之慢查询日志 1.7常见日志类型之二进制日志 1.8常见日志类型之事务日志 2.MySQL备份 2.1备份类型 2.2逻辑…

《华为数据之道》读书笔记六---面向自助消费的数据服务建设

七、从结果管理到过程管理&#xff0c; 从能“看”到能“管” 1、数据赋能业务运营 数字化运营旨在利用数字化技术获取、管理和分析数据&#xff0c;从而为企业的战略决策与业务运营提供可量化的、科学的支撑。 数字化运营归根结底是运营&#xff0c;旨在推动运营效率与能力的…

智能番茄新鲜度检测系统:基于深度学习的全面实现

基于深度学习的番茄新鲜度检测系统&#xff08;UI界面YOLOv8/v7/v6/v5代码训练数据集&#xff09; 引言 番茄是全球广泛种植和消费的蔬菜之一&#xff0c;其新鲜度直接影响其营养价值和口感。传统的番茄新鲜度检测主要依赖于人工观察和经验判断&#xff0c;这不仅费时费力&am…

PyCharm 2024.1最新变化

PyCharm 2024.1 版本带来了多项重要的更新和新特性&#xff0c;旨在提升开发者的编程效率和体验。以下是该版本的主要更新内容&#xff1a; 一、Hugging Face 集成 模型和数据集文档预览&#xff1a;PyCharm 2024.1 现在集成了对 Hugging Face 模型和数据集的支持&#xff0c…

npm与webpack的学习笔记

npm 定义&#xff1a;npm是Node.js标准的软件包管理器。它起初是作为下载和管理Node.js包依赖的方式&#xff0c;但其现在也已成为前端JavaScript中使用的工具。 包 包&#xff1a;将模块、代码、其他资料聚合成一个文件夹 包的分类&#xff1a; 项目包&#xff1a;主要用…