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

组合

顾名思义,组合就是将函数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. c# 文件夾操作

    #region 圖片對應異動           string newFilePath = "~/FileUpLoad/Book/" + bookModel.BookNo;     ...

  2. Setup Apache + PHP + MySql on Windows 10

    The below steps recorded my experiences to setup the Apache + PHP + MySql on my Windows 10. 1. Downl ...

  3. [简单]docx4j常用方法小结

    http://53873039oycg.iteye.com/blog/2194479?utm_source=tuicool&utm_medium=referral —————————————— ...

  4. 读取jar包资源(转)

    可能有不少初学者会有这样的困惑:在你的代码里调用了一些资源文件,如图片,音乐等,在调试环境或单独运行的时候可以正常显示或播放,而一旦打包到jar文件中,这些东东就再也出不来了,除非把这个jar放到原来 ...

  5. VC++ MFC 按钮的全部样式Style

    Button Styles BS_3STATE 与复选框一样本样式按钮可被单击变暗.变暗状态通常用于指示本样式的按键正处于禁用状态. BS_AUTO3STATE   与三状态的复选框一样当用户选中它本 ...

  6. wamp密码设置

    WAMP安装好后,mysql密码是为空的,那么要如何修改呢?其实很简单,通过几条指令就行了,下面我就一步步来操作. 首先,通过WAMP打开mysql控制台. 提示输入密码,因为现在是空,所以直接按回车 ...

  7. sqlserver表数据导出为insert into语句

    <1>select 'insert into table_name (name,code) values ('''+name+''','''+code+''');' sql_str  fr ...

  8. args

    java 中args一般存在main主类方法内,String args[ ]或者String[ ] args表示给主方法传一个字符串数组. 而args是一个字符串数组的变量名,不是关键字,是argum ...

  9. wget net-tools

    新安装的centos7 minimal 没有安装 wget 需要安装一下,才能安装lnmp yum -y install wget yum -y install net-tools

  10. 使用Innosetup制作安装包的一些技巧

    1. 选择安装界面上的图片 [Setup] ;设置界面上的两个图片 WizardImageFile=WizModernImage.bmp WizardSmallImageFile=WizSmallIm ...