我们已经学习了怎样创建一个简单的Monad, MaybeMonad, 并且知道了它如何通过在 Bind函数里封装处理空值的逻辑来移除样板式代码. 正如之前所说的,我们可以在Bind函数中封装更复杂的逻辑. 下面给出一个更复杂更典型的Monad例子,一个解析器Monad. 在本篇将要介绍一个解析器,在之后的篇幅里将会把解析器转换成一个 Monad.

首先我们思考解析器要完成什么功能,它接受一个输入,通常是一些文本,然后输出期望的结果. 因此一个CSV解析器将会接受一个文本文件,输出行和列的数据,并带有数据类型. 我们可以把parser抽象成一个函数 ,它接受一个string,返回某种类型 T:

Func<string,T>

将一个大的任务分解成小的任务实现通常会更简单,所以如果我们能同坐组合很多小的解析器来构建我们的解析器会更好. 每个小的解析器可能会消耗部分字符串,所以我们可以定义函数接受一个 string, 返回 T 和匹配后剩余的字符串

Func<string, Tuple<T,string>>

Tuple是在.Net4里引入的新类型, 你可以用自定义类型替代。

我们的解析器可能并不能正确解析输入的字符串,因此我们还需要能处理解析失败的情况, 这里可以使用我们已定义的 Maybe 类型

Func<string, Mayb<Tuble<T,string>>>

现在来创建一个比较简单的解析器, 它匹配字符串"Hello":

public static Maybe<Tuple<T,string>>FindHello(string input)
{
return input.StartWith("hello") ?new Just<Tuple<string,string>>(Tuple.Create("Hello",input.Skip("Hello".Length).AsString())) :(Maybe<Tuple<string,string>>)new Nothing<Tuple<string,string>>();
}

如果输入的字符串中包含"Hello",将会返回"Hello"和剩余的字符串

var result=Parsers.FindHello("Hello world");

var justResult= result as Just<Tuple<string,string>>;

Console.WriteLine("justResult.Value.Item1={0}",justResult.Value.Item1);

//justResult.Value.Item1=Hello

Console.WriteLine("justResult.Value.Item2={2}",justResult.Value.Item2);

//justResult.Value.Item2=World

如果我们输入"GoodBye" ,它将会返回Nothing:

var result2=Parsers.FindHello("Goodbye world");

Console.WriteLine("resulte2={0}",result2);

//result2=Nothing

通过创建一个可以解析任何字符串的解析器工厂,我们可以使我们的解析器更优美, 首先定义一个delegate:

public delegate Maybe<Tuple<T,string>>Parser<T>(string input)

现在定义Find,写在扩展方法里:

public static Parser<string>Find(this string stringToFind)

{
return input=> input.StartsWith(stirngToFind) ?new Just<Tuple<string,string>>(Tuple.Create(stringToFind,input.Skip(stringToFind.Length).AsString())) :(Maybe<Tuple<string,string>>)new Nothing<Tuple<string,string>>();
}

这是一个高阶函数,它返回一个函数,就是我们的解析器, 注意我们的解析器是一个delegate。

现在我们可以用它创建一些解析器,比如一个"Hello" 解析器和一个"World"解析器

var helloParser= "Hello".Find();

var worldParser="World".Find();

我们加一个扩展方法方便把解析结果转换成string:

public static string AsString<T>(this Maybe<Tuple<T,string>>parseResult, Func<T,string>unwrap)
{
var justParseResult= parseResult as Just<Tuple<T,string>>; return (justParseResult != null ?unWrap(justParseResult.Value.Item1)) :"Nothing";
}

现在我们用我们的helloParser 和 worldParser来解析字符串 :

var result =helloParser("Hello World").AsString(s=>s);

Console.WriteLine("result = {0}", result);

//result = Hello

var result2= worldParser("World Hello").AsString(s=>s);

Console.WriteLine("result2={0}",result2);

//result=World

我们怎么能把这两个parser结合起来创建一个"HelloWorld"的parser呢,这里有一个生硬的实现:

Parser<Tuple<string,string>>helloWorldParser=input=>
{
var helloResult= helloParser(input) as Just<Tuple<string,string>>; if(helloResult == null) return new Noting<Tuple<Tuple<string,string>,string>>(); var worldResult= worldParser(helloResult.Value.Item2) as Just<Tuple<strin,string>>; if(worldResult == null) return new Noting<Tuple<Tuple<string,string>,string>>(); return new Just<Tuple<Tuple<string,string>,string>>(Tuple.Create( Tuple.Create(helloResult.Value.Item1,worldResult.Value.Item1),worldResult.Value.Item2)); }; var result3=helloWorldParser("HelloWorld").AsString(s=>s.Item1 + " " + s.Item2); Console.WriteLine("result3 = {0}",result3); //result=Hello World

这样写非常繁琐,假如我们要组合更复杂的解析器,比如CSV解析器,将会非常令人头疼.但是不要担心,在下一篇我们将会把我们的解析器转换成Monad, 并以非常简单的方式组合他们

最新文章

  1. 搬家到cnblogs
  2. 利用定时器实时显示&lt;input type=&quot;range&quot;/&gt;的值
  3. Android高仿微信(一)——如何消除启动时的白屏
  4. wap网站safari浏览器和微信cooke不能登录问题
  5. Linux内核抢占与中断返回【转】
  6. 如何成为apple开发者???
  7. [iOS基础控件 - 6.9.4] 抓取网页图片资源
  8. vmware能够ping通内网,上不了外网的解决方法
  9. .Net_用控制台程序打印指定行数的三角型(面试题)
  10. js 复制内容到剪切板
  11. Asp.net mvc 知多少(六)
  12. scrapy爬虫 快速入门
  13. python之django基础
  14. Beta阶段 - 博客链接合集
  15. Django之组件--cookie与session
  16. Flask图书管管理表
  17. Windows 使用windump进行循环抓包
  18. JAVA 静态方法和实例方法的区别 (图表)
  19. linux下精确替换某个字符串
  20. div 自动全屏高度

热门文章

  1. Altova MapForce AMS/ACI/ISF自定义模板
  2. nginx设置跳转https
  3. bzoj 4994: [Usaco2017 Feb]Why Did the Cow Cross the Road III 树状数组_排序
  4. 使用Selenium爬取网站表格类数据
  5. 在UEditor编辑器的工具栏上加一行文字
  6. sqlserver日志文件太大解决方法
  7. CentOS 6.3(x86_32)下安装Oracle 10g R2
  8. 网上有一种错误的做法是:因为每一个双连通分量内的点low[]值都是相同的,则dfs()时,对于一条边(u,v),只需low[u]=min(low[u],low[v]),这样就不用缩点,最后求度数的时候
  9. 基于Mybatis的Mysql数据库文档生成工具,支持生成docx(原创)
  10. 一个表空间使用率查询sql的优化