C#中的Nutshell函数式编程
作者:互联网
目录
介绍
如今,函数性编程正在流行。我们应该问自己有两个问题:
- 为什么这种变化发生在程序员和语言创造者身上?
- 以及如何证明这一点?
有很多函数语言:
- C#,C ++,Java。
- Lisp,JavaScript,Python,Smalltalk,Ruby,OCaml,F#,Scala,Groovy,D,Erlang,Clojure,Go,Swift等。
我们还可以注意到有很多关于函数式编程的书籍:
还有另外两个重要问题:
- 如何定义一种函数语言?
- 什么是函数式编程?
函数编程定义
函数式编程是基于函数,它们的组合,以及也基于函数分解的编程。
组合/分解的这种影响是编程范例的标志(一种建模和构建解决方案的方式)。
作为例子引用的所有语言都具有函数概念。函数式编程包括使用具有特定属性的函数。
函数属性
函数有两种可能的属性:
- 纯度:函数的结果取决于它们的参数而没有任何其他外部影响。
- 头等:函数具有值的状态。
函数编程包括利用这些属性中的一个或两个。
函数式语言是一种让函数式编程更具优势的语言。
纯度
如果函数是纯函数,则意味着它将生成仅依赖于函数的参数而没有任何其他外部影响的结果。
函数纯度:
- 拒绝副作用和状态。
- 倡导数据结构的不变性。
例如,下面是一个纯函数:
int f(int i){
return i + 4;
}
f(1); // -> 5
f(1); // -> 5
以下是一个不纯的函数:
int j = 4;
int g(int i){
j = i + j;
return j;
}
g(1); // -> 5
g(1); // -> 6
作为其他示例,Log是纯函数。Random 是一个不纯的函数。
头等函数
要成为头等函数意味着函数具有与诸如整数或字符之类的值相同的状态:
- 函数可以被命名、影响和类型化。
- 可以根据需要定义和创建函数。
- 函数可以作为另一个函数的参数传递。
- 可以从另一个函数返回一个函数。
- 函数可以存储在数据结构中。
下面是一些例子:
1.被命名、影响和类型化的能力:
Func<double, double> f = Math.Sin;
2.按需定义和创建的能力:
Func<int, int> f = x => x + 1;
3.作为参数传递给函数的能力:
g(f, 1, 2);
其中g 定义如下:
double g(Func<double, double> f, double x, double d){
return f(x) + d;
}
4. 作为函数结果的能力:
Func<double, double> f = g(10);
其中g 定义如下:
Func<double, double> g(double x){
return y => x + y;
}
5.能够存储在数据结构中,例如列表:
var l = new List<Func<double, double>> { Math.Sin, Math.Cos, Math.Tan };
此外,C#允许将lambda表达式表示为称为表达式树的数据结构:
Expression<Func<int, int>> expression = x => x + 1;
var d = expression.Compile();
d.Invoke(2);
因此,它们可以被存储和传输。
闭包的概念
闭包和头等是两个独立的属性。但是,要成为纯粹和/或头等,必须将函数转换为闭包。
例如,当我们将以下函数关联到以下环境时,以下函数将转换为其他上下文中的纯函数:
int i = 0, j = 1;
int f(int k){
return k + i + j;
}
函数式语言的标志是自动将函数的定义转换为闭包。
成为函数式
以前的定义表明:
- 纯度是编写函数的一门学科。
- 为了提高函数状态,不必全部实现头等的五个特征。
- 函数语言不需要专门使用。
- 纯度和头等可以在最初没有的语言中受到青睐。
函数式实用程序
在开发和项目方面,纯度和头等函数的实际后果是什么?
纯度重要性
纯度引发以下主要特征:与函数的应用程序上下文无关。
每当子表达式被赋值时,每个子表达式都可以被赋值。这意味着函数组合的稳定性。
以下是纯度的重要性:
- 函数概念简化了手续。
- 更完整和更具代表性的函数行为类型。
- 自然并行化。
- 提高了可读性和可维护性。
- 简单的测试。
- 记住值(缓存)。
- 控制评估。
然而,纯度使一些事情变得复杂:
- 数据结构的管理。
- 显式内存管理。
- 输入/输出的定义和错误处理,
只能使用纯函数进行编码。Haskell是一种强加于此的语言。
C#允许注释函数以明确指示它们是纯的:
[Pure]
bool f(int i){
return i + 4 > 0;
}
Contract.Requires(f(0));
在C#的官方文档中:“锲约中调用的所有方法都必须是纯粹的;换句话说,它们不能更新预先存在的状态。”
头等的重要性
头等的函数是:
- 可以被命名、影响和类型化的。
- 可以按需定义和创建。
- 可以作为参数传递给函数。
- 可以是函数的结果。
- 可以存储在数据结构中。
如果函数可以作为参数传递给函数,那么它将意味着泛化/函数抽象的可能性。函数中代码的每个部分都可以用抽象(函数调用)代替。
一个简单的代码:
float M(int y){
int x1 = [ ... ];
int x2 = [ ... ];
[ ... ]
[ ... some code ... ]; // some code using x1, x2 and y
[ ... ]
}
函数抽象:
public delegate int Fun(int x, int y, int z);
float MFun(Fun f, int x2, int y){
int x1 = [ ... ];
[ ... ]
f(x1, x2, y);
[ ... ]
}
int z1 = MFun(F1, 1, 2);
int z2 = MFun(F2, 1, 2);
函数抽象的优点是没有局部重复,并且存在关注点分离。
函数抽象的简单有效应用是对数据的通用高阶迭代操作。
例如,内部迭代器(Maps):
IEnumerable<T2> Map<T1, T2>(this IEnumerable<T1> data, Func<T1, T2> f){
foreach(var x in data)
yield return f(x);
}
someList.Map(i => i * i);
作为头等的另一个结果,我们对泛型函数类型进行了定义。C#为arity提供函数和程序通用委托预定义类型,最多16个:
delegate TResult Func<TResult >();
delegate TResult Func<T, TResult>(T a1);
delegate TResult Func<T1, T2, TResult>(T1 a1, T2 a2);
delegate void Action<T>(T a1);
[ ... ]
通过推理类型可以减轻头等的函数。C#提供了var关键字,但它对于函数类型来说太弱了。
要按需定义和创建,通过匿名函数完成,也称为lambdas:
delegate(string s) { return s + "some string"; };
匿名委托看起来更像lambda表达式:
(s => { return s + "some string"; });
s => s + "some string";
一个主要的例子是对数据结构的迭代处理的推广,它有三种类型的函数:map,reduce / folds和filters。
例如,内部迭代器(Maps):
IEnumerable<T2> Map<T1, T2>(this IEnumerable<T1> data, Func<T1, T2> f){
foreach (var x in data)
yield return f(x);
}
someList.Map(i => i * i);
大多数函数式语言在其库中提供映射/简化/过滤器(在C#中是Select/ Aggregate/ Where)。
映射/简化/过滤器函数集也用于表示治疗的组成:
例如LINQ:
var q = programmers
.Where(p => p.Age > 20)
.OrderByDescending(p => p.Age)
.GroupBy(p => p.Language)
.Select(g => new{ Language = g.Key, Size = g.Count(), Names = g });
作为函数的结果,允许:
- 适应函数。
- 部分申请(currying)。
使用头等函数,每个n元函数都可以转换为n个一元函数的组合,即成为一个curried函数:
Func<int, int, int> lam1 = (x, y) => x + y;
Func<int, Func<int, int>> lam2 = x => (y => x + y);
Func<int, int> lam3 = lam2(3); // partial application
Currying:
public static Func<T1, Func<T2, TRes>> Curry<T1, T2, TRes>(this Func<T1, T2, TRes> f){
return (x => (y => f(x, y)));
}
Func<int, int> lam4 = lam1.Curry()(3); // partial application
要存储在数据结构中,允许:
- 动态模块化:管理函数集,结构化列表,表格,树,图等。
- 将参数作为函数集(而不是简单函数)传递。
此外,其中一个缺点是数据驱动编程。
一些函数式编程技术使用纯度和头等:
- Mapreduce(C#中的Select+ Aggregate):广义迭代处理纯函数,便于并行化。
- 通过函数仿真控制评估:封装在没有参数的函数中的纯表达式(这些函数的按需逐个调用评估)。
函数编程和面向对象编程
为什么函数式编程通常集成到面向对象的编程中?
主要的面向对象编程语言基于类作为模块:C#,C ++,Java。
面向对象编程中开发的强大思想之一:维护、扩展和适应操作可以通过继承和类组合(这避免了对现有代码的任何修改)。函数式编程是这个问题的解决方案。
例如,战略设计模式。
策略模式是让算法独立于使用它的客户端而变化:
策略:只是在方法级别抽象代码的情况(不需要面向对象的封装和新的类层次结构)。例如,在.NET Framework中:
public delegate int Comparison<T>(T x, T y);
public void Sort(Comparison<T> comparison);
public delegate bool Predicate<T>(T obj);
public List<T> FindAll(Predicate<T> match);
其他设计模式,如命令,观察者,访问者和虚拟代理,可以使头等的函数受益:
集成函数编程
包含头等函数的语言应该促进函数编程并促进它。
总结
函数编程有利于开发:纯度和头等产生一定程度的稳定性、确定性、可测试性、分区、使用流动性、组合性、概括性、可扩展性等。
- 函数式编程适用于模块化对象。
- 函数式编程是可溶的:纯度和头等函数可以用任何语言来考虑,包含和促进。
- 函数/方法级别的代码抽象。
- 方便的通用迭代器/循环实现。
- 操作组合,序列/查询理解。
- 函数部分应用。
- 对象/类定义数量的限制。
- 在函数/方法级别命名抽象。
- 架构简化。
- 增加灵活性。
标签:函数,int,纯度,编程,Func,头等,Nutshell 来源: https://blog.csdn.net/mzl87/article/details/100184838