C#函数式编程中的部分应用详解
何谓函数式编程
相信大家在实际的开发中,很多情况下完成一个功能都需要借助多个类,那么我们这里的基本单元就是类。而函数式编程则更加细化,致使我们解决一个功能的基本单元是函数,而不是类,每个功能都是由多个函数构成,并且函数之间没有直接的关系。如果简单的文字描述还不足以让你理解,下面我们就配以图来演示。
如下图所示,图左是我们设计好的三个函数,而右边则是我们需要实现的功能。而我们需要做的就是利用这三个函数去完成对应的三个功能,笔者在这里只是进行简单而又形象的表述,实际的开发过程可能需要更多的函数,并且需要使用不同的函数式编程的方式组合才能完成对应的功能。
后面我们假设F1和F2进行组合可以完成功能G1,那么结果就如下图所示:
对应的其他功能我们依然是按照上面的方式进行组合就可以完成对应的功能,这样做必然有其对应的优点,对笔者而言最大的优点就是函数不受外部环境的影响,这里我们不能与类中的方法相提并论,因为方法会受到类上下文变量的影响,特别是在多线程的情况下会出现共享读和写的问题,而函数则不会,因为他只是通过参数的方式接收外部的变量,还有一点就是复用性很强,如果前期设计的充分,在后期开发过程中函数可以发挥到最大的作用。说了这么多废话,下面我们就可以开始我们的函数式编程的第一部分——部分应用。
部分应用
各位不用被这个名词吓坏,他主要是将我们多个参数的函数进行拆分,拆成多个只有一个参数的函数,比如下面这个函数,我们正常写的话都是这样写的:
Func<int, int, int> Add = (x, y) => x + y;
怎么调用相信笔者就不需要过多介绍了,下面我们就要让他能够支持部分应用:
Func<int, Func<int, int>> Add = x => y => x + y;
这下就应该明白了吧,只是在接收了一个值之后返回了下一个函数,然后我们再调用这个返回的函数就完成整个调用,我们是不是部分使用了这个函数?所以叫部分应用。下面我们来看看怎么使用这个函数:
var Add2 = Add(2);
var result = Add2(4);
这样分成两行比较容易看懂,但是我们可以仅仅使用一行就可以了,比如下面这个方式:
var result = Add(2)(5);
哇,是不是瞬间感觉高大上了,如果我们这个方法的参数再多点,就是括号加括号,相信别人看到你这行代码后就会呵呵了,然后心里一万个“某某”马奔腾。
我去,看到这的人会可能会吹嘘这又没有什么太特别的东西,就是函数返回函数。对就是函数返回函数,但是实际运用起来你就会发现舒畅多了,下面笔者简单的举一个比较靠谱的例子来说明部分应用能够带给我们什么,比如我们经常需要执行SQL语句,当然需要使用SqlConnection,然后附加上对应的SQL语句,为此我们可以开发一个简单的函数,用来简化这一过程:
Func<SqlConnection, Func<String, DataSet>> ExecSql = x => y =>
{
using (x)
{
x.Open();
var com = x.CreateCommand();
DataSet ds = new DataSet();
com.CommandText = y;
SqlDataAdapter adapter = new SqlDataAdapter(com);
adapter.Fill(ds);
return ds;
}
};
然后调用起来就简单多了,我们只要传递给对应的SqlConnection对象,然后对应的返回值我们就可以用来执行我们的SQL语句了,具体的使用示例如下所示:
var esql = ExecSql(new SqlConnection("xxx"));
var rds = esql("select xxxx from xxx");
rds = esql("select ffff from ffff");
但是做到这还没有结束,面对那些总是想出奇怪问题的人,我们还有一个需要做,就是我们可能先要传递SQL语句,然后再传递对应的SqlConnection对象,没问题,我们专门为此写个函数:
Func<String, Func<SqlConnection, DataSet>> ExecSqlT = x => y => ExecSql(y)(x);
我们就继续该怎么调用就调用吧,但是上面都是从一开始就利用部分应用的方式来写,实际情况可能是已经写好的普通的方式,需要转换成部分应用的方式。那么下面我们可以自己先手动的写几个扩展,以便于以后的使用,首先我们来写存在两个参数和返回值的扩展:
public static class Functional
{
public static Func<T1, Func<T2, T3>> Currey<T1, T2, T3>(this Func<T1, T2, T3> func)
{
return x => y => func(x, y);
}
}
有了这个扩展之后我们再把上面的例子改写:
var ExecSql = Functional.Currey<SqlConnection, String, DataSet>((x, y) =>
{
using (x)
{
x.Open();
var com = x.CreateCommand();
DataSet ds = new DataSet();
com.CommandText = y;
SqlDataAdapter adapter = new SqlDataAdapter(com);
adapter.Fill(ds);
return ds;
}
});
这样我们就可以按照我们正常的形式来写,然后调用Functional的Currey就可以了,当然这里需要显示的传递泛型参数,有些情况下则不需要。
如果需要扩展更多参数的可以对应的写下去就可以了。当然上面仅仅只是针对没有参数的情况,我们也可以对Action也进行扩展:
public static Func<T1, Action<T2>> Currey<T1, T2>(this Action<T1, T2> func)
{
return x => y => func(x, y);
}
到此我们就解决了将普通函数转换成部分应用方式的函数,但是问题就来了。如果我们一开始写的是部分应用方式的函数,怎么将其转换成普通的函数呢?自然我们还需要下面的扩展能够将其转换回去:
public static Func<T1, T2, T3> UnCurrey<T1, T2, T3>(this Func<T1, Func<T2, T3>> func)
{
return (x, y) => func(x)(y);
}