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

组合

顾名思义,组合就是将函数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. Mac系统默认MAWP配置

    MAC系统是自带apache的,配置起来也很容易,但是本身是不支持php的需要手动开启一下,这里记录一下配置过程 1.apache配置文件在/etc/apache2/httpd.conf,把Docum ...

  2. 编译caffe报错:_ZN5boost16exception_detail10bad_alloc_D2Ev

    具体报错信息很长的. text._ZN5boost16exception_detail10bad_alloc_D2Ev[_ZN5boost16exception_detail10bad_alloc_D ...

  3. An attempt was made to load a program with an incorrect format

      用.net调用一个C++ 32位的DLL, 编译的时候选择x86, 在部署到一个64位的机器上的时候报错:"An attempt was made to load a program w ...

  4. DCOM中的APPID的用处,以及RemoteServerName的传递问题

      DCOM中的APPID的用处,以及RemoteServerName的传递问题  

  5. Linux相关文章

    1.linux 中特殊符号用法详解 2.linux之vim命令 3.linux各文件夹的作用 4.修改linux文件权限命令:chmod 5.CentOS 6.6下安装配置Tomcat环境 6.lin ...

  6. 开源GIS简介.学习

    开发者都希望自己的软件能够运行在尽可能多的计算机上.然而事与愿违,摆在 GIS开发者面前的仍然是对峙的平台.J2EE随着Java5.0的发布,已经正式更名为JavaEE,而微软也正式发布了.NET2. ...

  7. asp.net GDI+绘制矩形渐变

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  8. Cisco SG300系列交换机划分VLan与普通路由器连接配置

    思科SG300系列三层交换机是针对中小企业设计的一款产品,Marvell 主控和128M Ram,最大支持52个千兆RJ45端口和2个SFP端口,因公司业务需求,最近也进行了解和配置,具体型号为 SG ...

  9. Bridge(桥接)-对象结构型模式

    1.意图 将抽象部分与它的实现部分分离,使它们都可以独立地变化. 2.动机 在抽象类与它的实现之间起到桥梁作用,使它们可以独立地变化. 3.适用性 不希望在抽象和它的实现部分之间有一个固定的绑定关系. ...

  10. 持续获取可访问谷歌的hosts(已证实可用)

    @echo off REM 欢迎圈我,在顶栏的"查找人员"处输入Felix Hsu即可 REM Patched by logicmd REM 准备工作,先清一下DNS缓存,再备份h ...