C#3.5后,可以使用lambda表达式、拓展方法、yield关键字(这个C#2.0就有)等技术,非常自然的写出函数式风格的代码。最近看了一些资料,这里略写一些
映射
代码如下:
public static IEnumerableMap (this IEnumerable items, Func func) { if (func == null) throw new ArgumentNullException("func should not be null"); foreach (var item in items) { yield return func(item); } }
实际上就是Select方法(多个重载中的最简单的一个),把T类型映射成TResult类型,T和TResult可以相同,比如都为int;也可以不同,比如一个int,一个String,如下
List l = new List (){1,2,3,4,5};var result1 = l.Map(a => a.ToString());var result2 = l.Map(a => a*2);
result1是IEnumerable<String>类型,里面是{“1”,”2”,”3”,”4”,”5”},即把int 转为相应的String
result2是IEnumerable<int>类型,里面是{2,4,6,8,10};
Map方法实际就是把一个映射函数,应用到items上的每一个元素,然后返回结果的集合。
过滤
代码如下:
public static IEnumerableFilter (this IEnumerable items,Func filter ) { //检查ArgumentNullException,为了简便,下面都不写了 foreach (var item in items) { if (filter(item)) yield return item; } }
实际上就是Where方法,过滤掉集合中不满足条件的元素。这个就不举例~~
Fold
不知道该怎么翻译,直接上代码吧
public static T Fold(this IEnumerable items, T init, Func func){ T result = init; foreach (var item in items) { result = func(result, item); } return result;}
给一个初值,然后不断的迭代集合的每一元素,不好解释,大家自己好好体会一下吧。
这里返回值没必要和集合中的元素一样,不过一样的话,理解起来更简单一下。下面看一下两个用例:
List l = new List (){1,2,3,4,5};var sum = l.Fold(0, (a, b) => a+b);var factorial = l.Fold(1, (a, b) => a*b);IEnumerablels = l.Map(a => a.ToString());var result = ls.Fold("jzl:", (a, b) => a + b); //result 的值为 "jzl:12345"
Sum就是集合元素的和,结果是15,如果初值是1,那个结果就是16.
factorial 就是阶乘了。
Monad
这个是Haskell语言的一个让人非常费解的知识点大家可以google一下,函数签名是:
L a –> (a -> L b) -> L b
具体点,把L想象成List,a、b想象成int、String之类的类型,函数接受两个参数,List<a>和一个函数(这个函数接受a类型的参数,然后返回List<b>),然后返回List<b>
//L a -> (a -> L b) -> L b
//Monad(1) public static IEnumerable Monad (this IEnumerable items, Func > func ) { foreach (var item in items) { foreach (var i in func(item)) { yield return i; } } } //Monad(2) public static Nullable Monad (this Nullable nA,Func > func ) where A:struct where B:struct { if (!nA.HasValue) return null; else return func(nA.Value); }
对于Monad(1),其实就是SelectMany方法,不过C#编译器对SelectMany方法提供了一些语法糖,写起来会更自然。
对于Monad(2),首先对照着函数签名,好好体会一下。这里虚拟一个场景,来说明它的用法
有个人为了保持平衡(Balance),左手和右手拿东西的差值不能大于3,现在有2个操作,向这个人的左手添加东西和向右手添加东西。然后我不断添加,问最后他能不能保持平衡。代码模拟一下吧:
//Nullable,T必须是值类型 public struct Balance { private int _left; private int _right; public Balance(int left,int right) { this._left = left; this._right = right; } public Nullable AddLeft(int l) { if ( Math.Abs(_left + l - _right) > 3 ) return null; else return new Balance(_left+l,_right); } public Nullable AddRight(int r) { if (Math.Abs(_right + r - _left) > 3) return null; else return new Balance(_left,_right+r); } }//.... static void Main(string[] args) { Nullable balance = new Balance(0,0); var r = balance.Monad(b => b.AddLeft(1)) .Monad(b => b.AddRight(1)); var r1 = balance.Monad(b => b.AddLeft(10)) .Monad(b => b.AddRight(10)); Console.WriteLine(r.HasValue );//True Console.WriteLine(r1.HasValue);//False Console.ReadKey(); }
中间任何一个时刻,平衡被打破,即左右手之差大于3,那么就会产生Null,然后一直是Null,所以最终结果是Null的话,就知道中间有个过程出错了。如果只需要最终结果,这种调用非常直观有效。
这个例子的问题在于,我最终知道错了,却无法确定哪里错了。这需要在出错的时候,记录错误信息,并传下去。
大家注意理解思想,一个更实际的例子是编译器的Parser,可以一直解析,最终看一下结果,就可以知道中间出错了没有,当然需要传递出错的行号、列号、Error信息等。
编译的例子是听一位大神讲的,暂时还没能通过代码演示出来。不过可以想象一下,Parser实际就是一个状态机,根据输入,不断从当前状态转到下一个状态,如果状态是Error,那么就一直Error下去吧。Monad可以很好的体现这一过程。
挺绕吧,实际本人对Monad也是一知半解,如果有错,请大神们赐教。