第一章 介绍函数式编程

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特性:

  1. 使用using static 导入静态变量
  2. 具有表达式体式成员的更简洁函数
  3. 具有只读的自动属性的更简易不可变类型
  4. 局部函数
  5. 更加的元组语法

相关示例:

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、未来更趋函数化

未来发展趋势:

  1. 记录类型(不含样板代码的不可变类型)
  2. 代数数据类型(对类型系统的强大补充)
  3. 模式匹配(类似作用于数据形态的switch语句,如类型,而不仅时值)
  4. 更佳的元组语法

三、函数思维

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表达式,清晰的命名以及有意义的缩进.

总结

函数式编程优点:

  1. 更干净的代码——除了简洁性,FP导致了更具表现力,更具易读性,更易于测试的代码.
  2. 更好地支持并发——从多核CPU到分布式系统的多个因素为你的应用程序带来了高度的并发性.
  3. 一个多范式方法——学习像FP这样的不同范式不可避免给予丰富的视角去解决问题.

以上是关于第一章 介绍函数式编程的主要内容,如果未能解决你的问题,请参考以下文章

Java 8实战 (笔记)第一章

手把手介绍函数式编程:从命令式重构到函数式

《Java虚拟机精讲》读书笔记-第一章Java体系结构

专题《轻量函数式 JavaScript》之为什么要进行函数式编程?

有用函数编程

函数式编程