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

组合

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

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

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

 public static int FuncA(int x)
{
return x + ;
} public static int FuncB(int x)
{
return x + ;
}

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

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

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

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

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

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

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

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

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

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

当然实际开发中我们并不需要写,可以直接利用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函数的实现:

         public static IEnumerable<R> Filter<T,R>(Func<T,R> map,Func<T, bool> compare, IEnumerable<T> datas)
{
foreach (T item in datas)
{
if (compare(item))
{
yield return map(item);
}
}
}

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

             foreach (int x in Filter<int, int>(x => x, x => x <= , new int[] { , , , , , ,  }))
{
Console.WriteLine(x);
}
Console.ReadKey();

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

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

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

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

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

             var f = Currey(FilterDelegate<int, int>());
foreach (int x in f(x => x)(x => x <= )(new int[] { , , , , , , }))
{
Console.WriteLine(x);
}
Console.ReadKey();

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

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

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

             var f = Apply(Currey(FilterDelegate<int, int>()), x => x, x => x <= );

             foreach (int x in f(new int[] { , , , , , ,  }))
{
Console.WriteLine(x);
}
foreach (int x in f(new int[] { , , , , , , , , , , , , }))
{
Console.WriteLine(x);
}
Console.ReadKey();

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

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

  1. Python学习札记(二十) 函数式编程1 介绍 高阶函数介绍

    参考: 函数式编程 高阶函数 Note A.函数式编程(Functional Programming)介绍 1.函数是Python内建支持的一种封装,我们通过一层一层的函数调用把复杂任务分解成简单的任 ...

  2. C#中的函数式编程:递归与纯函数(二) 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    C#中的函数式编程:递归与纯函数(二)   在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential ...

  3. day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数

    本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...

  4. 函数与函数式编程(生成器 && 列表解析 && map函数 && filter函数)-(四)

    在学习python的过程中,无意中看到了函数式编程.在了解的过程中,明白了函数与函数式的区别,函数式编程的几种方式. 函数定义:函数是逻辑结构化和过程化的一种编程方法. 过程定义:过程就是简单特殊没有 ...

  5. Python函数式编程:内置filter函数使用说明

    filter操作是函数式编程中对集合的重要操作之一,其作用是从原集合中筛选符合条件的条目,组成一个新的集合. 这在我们日常编程中是非常常见的操作.我们通常的做法是通过循环语句来处理. 而使用filte ...

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

    在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential transparency)来定义的.如果一个 ...

  7. [学习] 从 函数式编程 到 lambda演算 到 函数的本质 到 组合子逻辑

    函数式编程 阮一峰 <函数式编程初探>,阮一峰是<黑客与画家>的译者. wiki <函数编程语言> 一本好书,<计算机程序的构造与解释>有讲到schem ...

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

    文件操作(续) 获得文件句柄位置,f.tell(),从0开始,按字符数计数 f.read(5),读取5个字符 返回文件句柄到某位置,f.seek(0) 文件在编辑过程中改变编码,f.detech() ...

  9. go 学习笔记之学习函数式编程前不要忘了函数基础

    在编程世界中向来就没有一家独大的编程风格,至少目前还是百家争鸣的春秋战国,除了众所周知的面向对象编程还有日渐流行的函数式编程,当然这也是本系列文章的重点. 越来越多的主流语言在设计的时候几乎无一例外都 ...

随机推荐

  1. 真机在wifi下调试android程序

    大家好,最近在学习android程序由于手机接口问题,调试程序的时候老是接触不良而不能正常调试,因此感到相当苦恼,于是在网上查找无线调试android的方法.经过研究和尝试现已成功无线调试程序,方法分 ...

  2. RedHat下安装OPENCV

    1.解压 unzip opencv-2.4.9.zip 2.进入目录,cmake CMakeLists.txt  生成build文件 3.使用命令 make 编译 4.使用命令 make instal ...

  3. nullcon HackIM 2016 -- Programming Question 1

    So you reached Delhi and now the noise in your head is not allowing you to think rationally. The Nos ...

  4. Oracle 取随机数(转)

    1.从表中随机取记录 select * from (select * from staff order by dbms_random.random)      where rownum < 4 ...

  5. myeclipse中Web App Libraries无法自动识别lib下的jar包

    在项目目录下找到.object文件修改 <natures> <nature>org.eclipse.jem.workbench.JavaEMFNature</nature ...

  6. timingFunction

    *  动画的开始与结束的快慢,有五个预置分别为(下同): *  kCAMediaTimingFunctionLinear            线性,即匀速 *  kCAMediaTimingFunc ...

  7. background-size的两个属性:cover和contain

    两种都不会造成图片失真,其中: (1)cover:相当于宽度等于元素的宽度,高度设为auto: (2)contain:相当于高度等于元素的高度,宽度设为auto: 例如:设置一个高度和宽度都为300p ...

  8. AngularJS学习--- AngularJS中数据双向绑定(two-way data-binding) orderBy step4

    1.切换工作目录 git checkout step- #切换分支,切换到第4步 npm start #启动项目 2.代码 app/index.html Search: <input ng-mo ...

  9. 防止刷新/后退引起的重复提交问题的Java Token代码,非Struts

    贴子转自http://hi.baidu.com/bobylou,转之前并没有验证文章里的方法是不是有效,估计原作者把它放到blog之前应该做过测试了吧. Struts本身有一套完善的防止重复提交表单的 ...

  10. spring访问静态资源文件

    用 Spring MVC 开发应用程序,对于初学者有一个很头疼的问题,那就是程序数据都已经查询出来了,但界面样式仍然十分丑陋,加载不了 css,js,图片等资源文件.当你在浏览器上直接输入某个css文 ...