第一章 介绍函数式编程
Posted 少女爱上芙兰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第一章 介绍函数式编程相关的知识,希望对你有一定的参考价值。
前言
参考链接:一、什么是函数式编程
定义: 函数式编程(FP)是一种编程范式,强调函数的同时避免状态突变的编程风格.
1、函数作为第一类值
简单讲,就是将函数当作值处理.
Func<int, int> triple = x => x * 3;
var range = Enumerable.Range(1, 3);
var triples = range.Select(triple);
triples //=>[3,6,9]
2、避免状态突变
意思是: 一旦创建一个对象,便永远不会改变,且变量永远不会被重新赋值.
/********排序问题***************/
//函数式方法:
Func<int, bool> isOdd = x => x % 3==1;
int[] original = { 7, 6, 1 };
var sorted = original.OrderBy(x=>x);//排序 不会改变原有数据,而是创建一个新数据
var filtered = original.Where(isOdd);
//非函数式方法:
var original = new List<int>{ 7, 6, 1 };
original.Sort();//排序 会改变原有数据
3、函数式(FP)与面向对象(OO)
大多数面向对象(oo)开发人员在其方法实现中严重依赖于命令式风格,使状态就地突变并使用显示控制流:他们在大规模的命令式编程中使用了OO设计.
也就是命令式与函数式区别.
函数式遵循原则:
- 模块化 (将软件划分为可复用组件)
- 关注分离 (每个组件只做一件事)
- 分层 (高层组件可依赖低层组件,反之不然)
- 松耦合 (对组件的更改不应该影响依赖它的组件)
二、C#函数式语言
总的来说,就是C#对函数式编程很好支持某些函数式技术,对部分支持不是很好在不断迭代中弥补.
1、LINQ函数式性质
最常见的操作:映射,排序和过滤.
具体示例:
Enumerable.Range(1, 100)
.Where(i => i % 20 == 0)
.OrderBy(i => -i)
.Select(i => $"{i}%");
// => ["100%","80%","60%","40%","20%",]
注意: Where,OrderBy,Select都接受函数作为参数,并且不会使给定的Enumerable突变,而是返回一个新的Enumerable.
2、C#6、7中函数式特性
与FP相关的C#6和C#7特性:
- 使用using static 导入静态变量
- 具有表达式体式成员的更简洁函数
- 具有只读的自动属性的更简易不可变类型
- 局部函数
- 更加的元组语法
相关示例:
using System;
using static System.Math;//使用using static 导入静态变量
public class Circle
{
public double Radius { get; }
public double Circumference=> PI * 2 * Radius;//具有表达式体式成员的更简洁函数
public Circle(double radius)=> Radius = radius;//具有只读的自动属性的更简易不可变类型
public double Area
{
get
{
double Square(double d) => Pow(d, 2);//局部函数
return PI * Square(Radius);
}
}
public (double Circumference, double Area) Stats=> (Circumference, Area);//新特性-5.更加的元组语法
}
3、未来更趋函数化
未来发展趋势:
- 记录类型(不含样板代码的不可变类型)
- 代数数据类型(对类型系统的强大补充)
- 模式匹配(类似作用于数据形态的switch语句,如类型,而不仅时值)
- 更佳的元组语法
三、函数思维
1、映射函数
- 定义域和值域的类型构成一个函数的接口,也称为类型或签名.
- 一个函数签名声明: 给定一个来自定义域的元素,将从值域生成一个元素.
2、C#中表函数
四种表示函数的语言结构:
- 方法(method)
//
- 委托(delegate)
//实例化和使用委托
var list = Enumerable.Range(1, 10).Select(i => i * 3).ToList();
//list =>[3,6,9,12,15,18,21,24,27,30]
Comparison<int> alphabetically = (l, r)=> l.ToString().CompareTo(r.ToString());
list.Sort(alphabetically);
//list =>[12,15,18,21,24,27,3,30,6,9]
- lambda表达式
//用一个lambda声明一个函数内联
var list = Enumerable.Range(1, 10).Select(i => i * 3).ToList();
//list =>[3,6,9,12,15,18,21,24,27,30]
list.Sort((l, r) => l.ToString().CompareTo(r.ToString()));
//list =>[12,15,18,21,24,27,3,30,6,9]
/****************************************/
// lambda可访问封闭作用域内的变量
var days = Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>();
// => [Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]
IEnumerable<DayOfWeek> daysStartingWith(string pattern)
=> days.Where(d => d.ToString().ToLower().StartsWith(pattern.ToLower()));
daysStartingWith("s");
// => [Saturday,Sunday]
- 字典(dictionary)
//
四、高阶函数(HOF)
高阶函数(HOF):是接受其他函数作为输入或返回一个函数作为输出的函数,或两者兼而有之.
1、依赖于其他函数的函数
有些HOF接受其他函数作为参数并调用它们以完成工作,有点像公司可能将其工作分包给另一家公司.
// Where 理想化实现
//Where: 一个迭代应用给定谓词的典型HOF
//where方法负责排序逻辑,调用者提供谓词,这是基于该条件过滤IEnumerable的准则.
public static IEnumerable<T> Where<T>(this IEnumerable<T> ts, Func<T, bool> predicate)
{
foreach (T t in ts)
{
if (predicate(t))
yield return t;
}
}
//HOF(高阶函数)- 控制倒转
public class Cache<T> where T : class
{
public T Get(Guid id) => default(T);
//
public T Get(Guid id, Func<T> onMiss) => Get(id) ?? onMiss();
}
2、设配器函数
有些HOF根本不应用所给定的函数,而是返回一个新函数,以某种方式与给定的作为参数的函数相关.
static Func<T2, T1, R> SwapArgs<T1, T2, R>(this Func<T1, T2, R> f)=> (t2, t1) => f(t1, t2);
//执行整数除法函数
Func<int, int, int> divide = (x, y) => x / y;
divide(10, 2);//=> 5
var divideBy = divide.SwapArgs();
var tBy = divideBy(2,10);// => 5
3、创建其他函数的函数
有时会编写主要用于创建其他函数的函数--可将其视为函数工厂.
//使用lambda过滤数字序列 取偶数
var range = Enumerable.Range(1, 20);
range.Where(i => i % 2 == 0);
//=> [2,4,6,8,10,12,14,16,18,20]
//计算给定的书是否被n整除
Func<int, bool> isMod(int n) => i => i % n == 0;
Enumerable.Range(1, 20).Where(isMod(2));//=> [2,4,6,8,10,12,14,16,18,20]
Enumerable.Range(1, 20).Where(isMod(3));//=> [3,6,9,12,15,18]
五、使用HOF避免重复
HOF最常用的方式是封装安装和拆卸的操作.
1、将安装和拆卸封装到HOF中
编写一个函数来执行安装和拆卸,并将其间的操作参数化.
//连接数据库帮助类
public static class ConnectionHelper
{
//执行安装和拆卸
public static R Connect<R>(string connString, Func<IDbConnection, R> f)
{
//操作已被参数化,整个过程属于安装
using (var conn = new SqlConnection(connString))
{
conn.Open();
return f(conn);
}
//结束时,拆卸
}
}
2、将using语句转换为HOF
using本身就是安装/拆卸例子,具体如下:
- 安装-通过计算给定的声明或表达式来获取IDisposable资源;
- 主体-执行块中的内容;
- 拆卸-退出该块,致使在安装中所获取的对象上调用Dispose;
参考-广泛复用案例
public static class F
{
//应该在没有被using static所限定的情况下可用
public static R Using<TDisp, R>(TDisp disposable, Func<TDisp, R> f) where TDisp : IDisposable
{
using (disposable) return f(disposable);
}
}
然后,对第一点连接数据库帮助类改进
public static class ConnectionHelper_2
{
//应该在没有被using static所限定的情况下可用
public static R Connect<R>(string connStr, Func<IDbConnection, R> f)
=> F.Using(new SqlConnection(connStr), conn => { conn.Open(); return f(conn);});
}
3、HOF的权衡
HOF优势:
- 简洁-改进案例显然更简洁.一般而言,安装拆卸越复杂,使用越广泛.
- 避免重复-整个安装/拆卸的逻辑,目前在一个地方执行.
- 关注分离
HOF缺陷:
- 增加了堆栈的使用.性能虽受到影响但可忽略不计.
- 由于回调,调试应用程序会稍微复杂一些.
注意:适时可使用HOF,但要注意可读性:使用简短的lambda表达式,清晰的命名以及有意义的缩进.
总结
函数式编程优点:
- 更干净的代码——除了简洁性,FP导致了更具表现力,更具易读性,更易于测试的代码.
- 更好地支持并发——从多核CPU到分布式系统的多个因素为你的应用程序带来了高度的并发性.
- 一个多范式方法——学习像FP这样的不同范式不可避免给予丰富的视角去解决问题.
以上是关于第一章 介绍函数式编程的主要内容,如果未能解决你的问题,请参考以下文章