C#函数式编程之由函数构建函数

在面向对象的编程中,如果我们需要复用其他的类,我们可以通过继承来实现。而在函数式编程中我们也可以采取不同的方式来复用这些函数。今天的教程将会讲述两种方式,其中一个就是组合,将多个函数组合成为一个函数,另一个则是之前我们介绍过的部分应用,当然我们将会讲述如何将其高级化,来符合我们的使用要求。

组合

顾名思义,组合就是将函数A的结果传递给函数B。但是我们并不关注函数A的结果,当然大多数一定会这样去做:

1 var r1 = funcA(1);
2 var r2 = funcB(r1);

这样显然不是我们希望的那样,假设我们后面需要经常利用到这样的函数。问题就出现了,所以我们就需要利用组合来将他们合成一个新的函数,首先我们先写出两个用来组合的函数:

1 public static int FuncA(int x)
2 {
3      return x + 3;
4 }
5
6 public static int FuncB(int x)
7 {
8     return x + 6;
9 }

如果我们不借助任何的自动化函数,我们可以通过这样的写法来进行组合:

Func<int,int> funcC = x => FuncB(FuncA(x));

但是我们这里无法使用var,因为C#的自动推断类型无法推断出这个类型。这样我们就有了一个新的函数funcC,我们可以试着执行这个函数看看最终的结果。上面我们通过手动的方式完成了组合,下面我们将编写一个自动化的函数来完成这个操作:

1 public static Func<T1, T3> Compose<T1, T2, T3>(Func<T1, T2> func1, Func<T2, T3> func2)
2 {
3     return x => func2(func1(x));
4 }

接着我们利用这个函数来实现上面的功能:

var funcC = Compose<int, int, int>(FuncA, FuncB);

但是我们发现我们需要提供泛型参数,而不能依赖类型推断。但如果FuncA和FuncB在此之前显式的声明过则不需要提供泛型参数,例如将FuncA和FuncB写成如下的方式:

Func<int, int> FuncA = x => x + 2;
Func<int, int> FuncB = x => x + 6;

这样在调用Compose函数就不需要提供泛型参数了,顺便在这里介绍下其他语言下如何实现相同的功能,在F#中通过 FuncB >> FuncA 来实现,而在Haskell中则是用过 FuncA . FuncB来实现,相比C#来说实现起来就非常的简单。通过上面的例子我们也发现了一个问题,就是函数A的返回类型必须和函数B的参数类型一致,并且在这个函数链中只有首个函数可以拥有多个参数,其他的函数只能拥有一个函数。当然函数链的最后一个函数可以是Action,就是说可以没有返回值,下面笔者写一个可以将三个函数进行组合的自动化函数:

1 public static Func<T1, T4> Compose<T1, T2, T3, T4>(Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3)
2 {
3      return x => func3(func2(func1(x)));
4 }

当然实际开发中我们并不需要写,可以直接利用FCSLib中提供的函数。

高级的部分应用

学习过《函数式编程之部分应用》的人一定知道,部分应用就是将需要多个参数的函数,拆成一个函数链,每个函数链都只需要一个参数,假如FuncA需要三个参数,则使用部分应用后调用这个函数就需要按照如下的方式来使用FuncA(2)(3)(2),所以下面的内容笔者不会重复的介绍已经介绍过的内容,如果读者没有学习过,可以进入到上面对应的页面中进行学习。

我们知道在C#中如果传入部分应用这个自动化函数中的参数是方法,类型推断会无法工作,那么我们就需要输入繁琐的类型参数,比如下面这种情况:

Functional.Curry<Converter<int,int>,Ienumerable<int>,Ienumerable<int>>(Functional.Map<int,int>);

读者会发现类型参数就占据的一半,上面我们也介绍了如何解决这个问题,所以我们可以写个已经显式声明过类型的函数来封装下Map函数:

public static Func<Converter<int, int>, IEnumerable<int>, IEnumerable<int>> MapDelegate<T1, T2>()
{
      return Map<T1, T2>;
}

这样我们在调用Curry函数就不需要提供类型参数了:

Functional.Curry(Functional.MapDelegate<int,int>());

至此,类型推断的问题我们就解决了。在实际开发中部分应用虽然十分有用,但是在某些情形下却十分的麻烦,比如函数Filter需要两个算法,最后一个参数为数据。在实际使用中我们都会将两个算法赋进去,而在后面的使用中仅仅只会改变对应的数据,但是在采用部分应用后就显得麻烦了,下面是Filter函数的实现:

 1         public static IEnumerable<R> Filter<T,R>(Func<T,R> map,Func<T, bool> compare, IEnumerable<T> datas)
 2         {
 3             foreach (T item in datas)
 4             {
 5                 if (compare(item))
 6                 {
 7                     yield return map(item);
 8                 }
 9             }
10         }

具体的功能就是通过compare函数判断是否符合条件,然后通过map函数返回需要的部分。我们可以通过如下的方式来调用这个函数:

1             foreach (int x in Filter<int, int>(x => x, x => x <= 10, new int[] { 2, 3, 1, 4, 5, 3, 34 }))
2             {
3                 Console.WriteLine(x);
4             }
5             Console.ReadKey();

在采用部分应用前,我们先写出这个函数的Delegate版本,这样我们就可以利用类型推断了:

1         public static Func<Func<T, R>, Func<T, bool>, IEnumerable<T>, IEnumerable<R>> FilterDelegate<T, R>()
2         {
3             return Filter<T, R>;
4         }

然后我们就可以轻松的使用Currey函数将其部分应用了,这里笔者直接自己实现了一个Currey函数,并没有使用FCSLib中提供的。读者可以参考下:

1         public static Func<T1,Func<T2,Func<T3,R>>> Currey<T1,T2,T3,R>(Func<T1,T2,T3,R> func)
2         {
3             return x => y => z => func(x, y, z);
4         }

最后我们通过实际的使用来看看:

1             var f = Currey(FilterDelegate<int, int>());
2             foreach (int x in f(x => x)(x => x <= 10)(new int[] { 2, 3, 1, 4, 5, 3, 34 }))
3             {
4                 Console.WriteLine(x);
5             }
6             Console.ReadKey();

即使这样也很繁琐,所以我们需要进行更高级的部分应用,这里我们需要另一个自动化函数来帮助我们实现:

1         public static Func<T3,R> Apply<T1, T2, T3, R>(Func<T1, Func<T2, Func<T3, R>>> func,T1 arg1,T2 arg2)
2         {
3             return x => func(arg1)(arg2)(x);
4         }

这个函数的作用就是将原本的部分应用的函数变成一个接收两个参数并返回一个只接收一个参数的函数,因为算法部分不会变动,但是数据会经常的变动。下面我们通过一个实际的运用来展示:

 1             var f = Apply(Currey(FilterDelegate<int, int>()), x => x, x => x <= 10);
 2
 3             foreach (int x in f(new int[] { 2, 3, 1, 4, 5, 3, 34 }))
 4             {
 5                 Console.WriteLine(x);
 6             }
 7             foreach (int x in f(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }))
 8             {
 9                 Console.WriteLine(x);
10             }
11             Console.ReadKey();

通过这样一番折腾后,我们就得到的我们真正需要的函数了,我们在一开始的时候确定算法。然后在后面的使用中我们就可以只传递数据即可。

时间: 2024-10-08 23:40:54

C#函数式编程之由函数构建函数的相关文章

C#函数式编程中的标准高阶函数详解

何为高阶函数 大家可能对这个名词并不熟悉,但是这个名词所表达的事物却是我们经常使用到的.只要我们的函数的参数能够接收函数,或者函数能够返回函数,当然动态生成的也包括在内.那么我们就将这类函数叫做高阶函数.但是今天我们的标题并不是高阶函数,而是标准高阶函数,既然加上了这个标准,就意味着在函数式编程中有一套标准的函数,便于我们每次调用.而今天我们将会介绍三个标准函数,分别为Map.Filter.Fold. Map 这个函数的作用就是将列表中的每项从A类型转换到B类型,并形成一个新的类型.下面我们可以

Python学习笔记八:文件操作(续),文件编码与解码,函数,递归,函数式编程介绍,高阶函数

文件操作(续) 获得文件句柄位置,f.tell(),从0开始,按字符数计数 f.read(5),读取5个字符 返回文件句柄到某位置,f.seek(0) 文件在编辑过程中改变编码,f.detech() 获取文件编码,f.encoding() 获取文件在内存中的编号,f.fileno() 获取文件终端类型(tty.打印机等),f.isatty() 获取文件名,f.name() 判断文件句柄是否可移动(tty等不可移动),f.seekable() 判断文件是否可读,f.readable() 判断文件是

C#中的函数式编程:递归与纯函数(二)

在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential transparency)来定义的.如果一个表达式满足将它替换成它的值,而程序的行为不变,则称这个表达式是引用透明的. 现在,我们不妨进行一个尝试:我们来实现一些函数,但是这次有一个限制:只能用无副作用的表达式. 先以素数判定为例子,我们要写一个函数bool IsPrime(int n),它返回这个整数是不是素数.简单起见,我们采用最朴素的方

什么是函数式编程(副作用、纯函数、引用透明)

副作用的概念:一个带有副作用的函数不仅只是简单的返回一个值,还干了一些其他的事情,比如: 修改一个变量 直接修改数据结构 设置一个对象的成员 抛出一个异常或以一个错误终止 打印到终端或读取用户的输入 读取或写入一个文件 在屏幕上绘画 因此我们对于函数式程序的判定边界就在于:函数的副作用. 于是--当函数没有副作用,那么我们就说这个函数符合函数式编程(FP):再给出纯函数这个概念用来定义一个函数没有副作用,我们可以说纯函数构成的程序风格就是函数式的! buyCoffee的例子(p3):函数只不过是

JavaScript函数式编程(0):函数基础 arguments、this、apply()、call()

1 函数参数 函数的实参和形参个数可以不等,之所以会这样,原因是 ECMAScript 中的参数在内部是用一个数组来表示的.函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话).如果实参个数大于形参个数,多余的实参不传递值,但是在arguments中可以访问:如果形参个数大于实参,没有传递值的实参将自动被赋予 undefined 值. 2 arguments和this 所有的函数调用都会传递两个隐式参数:arguments和this.实际上,在函数体内可以通过 argume

《JS权威指南学习总结--8.8 函数式编程和8.8.1使用函数处理数组》

内容要点:    和Lisp.Haskell不同,JS并非函数式编程语言,但在JS中可以像操控对象一样操控函数,   也就是说可以在JS中应用函数式编程技术.ES5中的数组方法(诸如map()和reduce())就可以非常适用于函数式编程风格. 一.使用函数处理数组    假设有一个数组,数组元素都是数字,我们想要计算这些元素的平均值和标准差.    若用非函数式编程风格的话,代码是这样:         var data = [1,1,3,5,5]; //这里是待处理的数组        //

函数式编程 &amp; Python中的高阶函数map reduce filter 和sorted

1. 函数式编程 1)概念 函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念.wiki 我们知道,对象是面向对象的第一型,那么函数式编程也是一样,函数是函数式编程的第一型.在面向对象编程中,我们把对象传来传去,那在函数式编程中,我们要做的是把函数传来传去,而这个,说成术语,我们把他叫做高阶函数.飞林沙 2)特点 计算视为视为函数而非指令 纯函数式编程:不需变量,无副作用,测试简单(每次的执行结果是一样的) 支持高阶函数,代码简洁 2. python支持

C#函数式编程之标准高阶函数

何为高阶函数 大家可能对这个名词并不熟悉,但是这个名词所表达的事物却是我们经常使用到的.只要我们的函数的参数能够接收函数,或者函数能够返回函数,当然动态生成的也包括在内.那么我们就将这类函数叫做高阶函数.但是今天我们的标题并不是高阶函数,而是标准高阶函数,既然加上了这个标准,就意味着在函数式编程中有一套标准的函数,便于我们每次调用.而今天我们将会介绍三个标准函数,分别为Map.Filter.Fold. Map 这个函数的作用就是将列表中的每项从A类型转换到B类型,并形成一个新的类型.下面我们可以

《Python核心编程》第十一章:函数和函数式编程

本章大纲 介绍函数的创建.调用方式,内部函数.函数装饰器.函数参数的定义和传递.函数式编程.变量作用域.闭包. 知识点 11.1 什么是函数? 函数是对程序逻辑进行结构化或过程化的一种编程方法,以实现代码的复用. python 的过程就是函数,因为解释器会隐式地返回默认值 None. python 动态地确定函数返回类型,而不是进行直接的类型关联. 可以使用 type() 函数作为代理,处理有不同参数类型的函数的多重声明,以模拟其他编程语言的函数重载. 11.2 调用函数 11.2.1 关键字参