﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-λ-calculus（惊愕到手了欧耶，GetBlogPostIds.aspx）-随笔分类-启示</title><link>http://www.cppblog.com/vczh/category/7321.html</link><description>【QQ：343056143】【Email：vczh@163.com】【新浪微博：http://weibo.com/vczh】</description><language>zh-cn</language><lastBuildDate>Fri, 09 Jan 2015 07:09:47 GMT</lastBuildDate><pubDate>Fri, 09 Jan 2015 07:09:47 GMT</pubDate><ttl>60</ttl><item><title>2014年终总结</title><link>http://www.cppblog.com/vczh/archive/2015/01/09/209442.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Thu, 08 Jan 2015 22:58:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2015/01/09/209442.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/209442.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2015/01/09/209442.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/209442.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/209442.html</trackback:ping><description><![CDATA[ 本来我都忘记了，结果有网友提醒我要写，我就来写一写。

现在的年终总结已经没什么内容了，主要是GacUI （www.gaclib.net）的开发实在太漫长。我现在在做拖控件程序GacStudio，希望在做完之后可以有Blend编辑WPF的效果，在xml里面写data binding的时候不同的地方会有不同的intellisense。这当然是很难的，所以才想做。因此今年就把GacUI做到可以有Control Template、Item Template、Data Binding、XML Resource和加载，于是终于可以开始做GacStudio了。GacStudio整个会使用GacUI推荐的Data Binding来写。这有三个好处：1、检验Data Binding是否设计的好顺便抓bug；2、检验性能；3、证明GacUI是屌的，大家都可以用。

我还在github开了一个数据库项目，在https://github.com/vczh/herodb，不过因为现在没有台式机，笔记本太烂装不了Ubuntu的虚拟机，所以暂停开发，等到我设备到了我再继续写。这个东西写完之后会合并进GacUI作为扩展的一部分，可以开很多脑洞，这个我就不讲了。

我觉得2014年最伟大的成就就是，我终于搬去西雅图了，啊哈哈哈哈。<img src ="http://www.cppblog.com/vczh/aggbug/209442.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2015-01-09 06:58 <a href="http://www.cppblog.com/vczh/archive/2015/01/09/209442.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>靠谱的代码和DRY</title><link>http://www.cppblog.com/vczh/archive/2014/07/15/207658.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 15 Jul 2014 14:44:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2014/07/15/207658.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/207658.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2014/07/15/207658.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/207658.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/207658.html</trackback:ping><description><![CDATA[<div><p>   上次有人来要求我写一篇文章谈谈什么代码才是好代码，是谁我已经忘记了，好像是AutoHotkey还是啥的专栏的作者。撇开那些奇怪的条款不谈，靠谱的 代码有一个共同的特点，就是DRY。DRY就是Don't Repeat Yourself，其实已经被人谈了好多年了，但是几乎所有人都会忘记。</p><h2>什么是DRY（Don't Repeat Yourself） <br /></h2><p>DRY 并不是指你不能复制代码这么简单的。不能repeat的其实是信息，不是代码。要分析一段代码里面的什么东西时信息，就跟给物理题做受力分析一样，想每次 都做对其实不太容易。但是一份代码总是要不断的修补的，所以在这之前大家要先做好TDD，也就是Test Driven  Development。这里我对自己的要求是覆盖率要高达95%，不管用什么手段，总之95%的代码的输出都要受到检验。当有了足够多的测试做后盾的时 候，不管你以后发生了什么，譬如说你发现你Repeat了什么东西要改，你才能放心大胆的去改。而且从长远的角度来看，做好TDD可以将开发出<em><u><strong>相同质量</strong></u></em>的代码的时间缩短到30%左右（这是我自己的经验值） 。</p><h2>什么是信息</h2><p>信息这个词不太好用语言下定义，不过我可以举个例子。譬如说你要把一个配置文件里面的字符串按照分隔符分解成几个字符串，你大概就会写出这样的代码：</p><pre><div><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;name;parent;description</span><span style="color: #008000; "><br /></span><span style="color: #0000FF; ">void</span><span style="color: #000000; ">&nbsp;ReadConfig(</span><span style="color: #0000FF; ">const</span><span style="color: #000000; ">&nbsp;wchar_t</span><span style="color: #000000; ">*</span><span style="color: #000000; ">&nbsp;config)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;auto&nbsp;p&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;wcschr(config,&nbsp;L</span><span style="color: #000000; ">'</span><span style="color: #000000; ">;</span><span style="color: #000000; ">'</span><span style="color: #000000; ">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;1</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">(</span><span style="color: #000000; ">!</span><span style="color: #000000; ">p)&nbsp;</span><span style="color: #0000FF; ">throw</span><span style="color: #000000; ">&nbsp;ArgumentException(L</span><span style="color: #000000; ">"</span><span style="color: #000000; ">Illegal&nbsp;config&nbsp;string</span><span style="color: #000000; ">"</span><span style="color: #000000; ">);&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;2</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;DoName(wstring(config,&nbsp;p));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;3</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;auto&nbsp;q&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;wcschr(p&nbsp;</span><span style="color: #000000; ">+</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1</span><span style="color: #000000; ">,&nbsp;L</span><span style="color: #000000; ">'</span><span style="color: #000000; ">;</span><span style="color: #000000; ">'</span><span style="color: #000000; ">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;4</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">(</span><span style="color: #000000; ">!</span><span style="color: #000000; ">q)&nbsp;</span><span style="color: #0000FF; ">throw</span><span style="color: #000000; ">&nbsp;ArgumentException(L</span><span style="color: #000000; ">"</span><span style="color: #000000; ">Illegal&nbsp;config&nbsp;string</span><span style="color: #000000; ">"</span><span style="color: #000000; ">);&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;5</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;DoParent(wstring(p&nbsp;</span><span style="color: #000000; ">+</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1</span><span style="color: #000000; ">,&nbsp;q);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;6</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;auto&nbsp;r&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;wcschr(q&nbsp;</span><span style="color: #000000; ">+</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1</span><span style="color: #000000; ">,&nbsp;L</span><span style="color: #000000; ">'</span><span style="color: #000000; ">;</span><span style="color: #000000; ">'</span><span style="color: #000000; ">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;7</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">(r)&nbsp;</span><span style="color: #0000FF; ">throw</span><span style="color: #000000; ">&nbsp;ArgumentException(L</span><span style="color: #000000; ">"</span><span style="color: #000000; ">Illegal&nbsp;config&nbsp;string</span><span style="color: #000000; ">"</span><span style="color: #000000; ">);&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;8</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;DoDescription(q&nbsp;</span><span style="color: #000000; ">+</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1</span><span style="color: #000000; ">);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;9</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">}</span></div></div></pre><p>这段短短的代码重复了多少信息？</p><ul><li>分隔符用的是分号（1、4、7）</li><li>第二/三个片段的第一个字符位于第一/二个分号的后面（4、6、7、9）</li><li>格式检查（2、5、8） </li><li>异常内容（2、5、8）<br /></li></ul><p>除了DRY以外还有一个问题，就是处理description的方法跟name和parent不一样，因为他后面再也没有分号了。</p><p>那这段代码要怎么改呢？有些人可能会想到，那把重复的代码抽取出一个函数就好了：</p><pre><div><div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%; word-break: break-all;"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000; ">wstring&nbsp;Parse(</span><span style="color: #0000FF; ">const</span><span style="color: #000000; ">&nbsp;wchar_t</span><span style="color: #000000; ">&amp;</span><span style="color: #000000; ">&nbsp;config,&nbsp;</span><span style="color: #0000FF; ">bool</span><span style="color: #000000; ">&nbsp;end)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;auto&nbsp;next&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;wcschr(config,&nbsp;L</span><span style="color: #000000; ">'</span><span style="color: #000000; ">;</span><span style="color: #000000; ">'</span><span style="color: #000000; ">);<br />&nbsp;&nbsp;&nbsp;&nbsp;ArgumentException&nbsp;up(L</span><span style="color: #000000; ">"</span><span style="color: #000000; ">Illegal&nbsp;config&nbsp;string</span><span style="color: #000000; ">"</span><span style="color: #000000; ">);<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">&nbsp;(next)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">&nbsp;(end)&nbsp;</span><span style="color: #0000FF; ">throw</span><span style="color: #000000; ">&nbsp;up;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wstring&nbsp;result(config,&nbsp;next);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;config&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;next&nbsp;</span><span style="color: #000000; ">+</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1</span><span style="color: #000000; ">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">return</span><span style="color: #000000; ">&nbsp;result;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">else</span><span style="color: #000000; "><br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">&nbsp;(</span><span style="color: #000000; ">!</span><span style="color: #000000; ">end)&nbsp;</span><span style="color: #0000FF; ">throw</span><span style="color: #000000; ">&nbsp;up;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wstring&nbsp;result(config);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;config&nbsp;</span><span style="color: #000000; ">+=</span><span style="color: #000000; ">&nbsp;result.size();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">return</span><span style="color: #000000; ">&nbsp;result;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}<br /><br /></span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;name;parent;description</span><span style="color: #008000; "><br /></span><span style="color: #0000FF; ">void</span><span style="color: #000000; ">&nbsp;ReadConfig(</span><span style="color: #0000FF; ">const</span><span style="color: #000000; ">&nbsp;wchar_t</span><span style="color: #000000; ">*</span><span style="color: #000000; ">&nbsp;config)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;DoName(Parse(config,&nbsp;</span><span style="color: #0000FF; ">false</span><span style="color: #000000; ">));<br />&nbsp;&nbsp;&nbsp;&nbsp;DoParent(Parse(config,&nbsp;</span><span style="color: #0000FF; ">false</span><span style="color: #000000; ">));<br />&nbsp;&nbsp;&nbsp;&nbsp;DoDescription(Parse(config,&nbsp;</span><span style="color: #0000FF; ">true</span><span style="color: #000000; ">));<br />}</span></div></div></pre><p>是不是看起来还很别扭，好像把代码修改了之后只把事情搞得更乱了，而且就算config对了我们也会创建那个up变量，就仅仅是为了不 重复代码。而且这份代码还散发出了一些不好的味道，因为对于Name、Parent和Description的处理方法还是不能统一，Parse里面针对 end变量的处理看起来也是很重复，但实际上这是无法在这样设计的前提下消除的。所以这个代码也是不好的，充其量只是比第一份代码强一点点。<br /></p><p>实 际上，代码之所以要写的好，之所以不能repeat东西，是因为产品狗总是要改需求，不改代码你就要死，改代码你就要加班，所以为了减少修改代码的痛苦， 我们不能repeat任何信息。举个例子，有一天产品狗说，要把分隔符从分号改成空格！一下子就要改两个地方了。description后面要加tag！ 这样你处理description的方法又要改了因为他是以空格结尾不是0结尾。</p><p>因此针对这个片段，我们需要把它改成这样：</p><pre><div><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000; ">vector</span><span style="color: #000000; ">&lt;</span><span style="color: #000000; ">wstring</span><span style="color: #000000; ">&gt;</span><span style="color: #000000; ">&nbsp;SplitString(</span><span style="color: #0000FF; ">const</span><span style="color: #000000; ">&nbsp;wchar_t</span><span style="color: #000000; ">*</span><span style="color: #000000; ">&nbsp;config,&nbsp;wchar_t&nbsp;delimiter)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;vector</span><span style="color: #000000; ">&lt;</span><span style="color: #000000; ">wstring</span><span style="color: #000000; ">&gt;</span><span style="color: #000000; ">&nbsp;fragments;<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">while</span><span style="color: #000000; ">(auto&nbsp;next&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;wcschr(config,&nbsp;delimiter))<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fragments.push_back(wstring(config,&nbsp;next));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;config&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;next&nbsp;</span><span style="color: #000000; ">+</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1</span><span style="color: #000000; ">;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;fragments.push_back(wstring(config));<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">return</span><span style="color: #000000; ">&nbsp;fragments;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;C++11就是好！</span><span style="color: #008000; "><br /></span><span style="color: #000000; ">}<br /><br /></span><span style="color: #0000FF; ">void</span><span style="color: #000000; ">&nbsp;ReadConfig(</span><span style="color: #0000FF; ">const</span><span style="color: #000000; ">&nbsp;wchar_t</span><span style="color: #000000; ">*</span><span style="color: #000000; ">&nbsp;config)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;auto&nbsp;fragments&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;SplitString(config,&nbsp;L</span><span style="color: #000000; ">'</span><span style="color: #000000; ">;</span><span style="color: #000000; ">'</span><span style="color: #000000; ">);<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">if</span><span style="color: #000000; ">(fragments.size()&nbsp;</span><span style="color: #000000; ">!=</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">3</span><span style="color: #000000; ">)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">throw</span><span style="color: #000000; ">&nbsp;ArgumentException(L</span><span style="color: #000000; ">"</span><span style="color: #000000; ">Illegal&nbsp;config&nbsp;string</span><span style="color: #000000; ">"</span><span style="color: #000000; ">);<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;DoName(fragments[</span><span style="color: #000000; ">0</span><span style="color: #000000; ">]);<br />&nbsp;&nbsp;&nbsp;&nbsp;DoParent(fragments[</span><span style="color: #000000; ">1</span><span style="color: #000000; ">]);<br />&nbsp;&nbsp;&nbsp;&nbsp;DoDescription(fragments[</span><span style="color: #000000; ">2</span><span style="color: #000000; ">]);<br />}</span></div></div></pre><p>我们可以发现，分号（L';'）在这里只出现了一次，异常内容也只出现了一次，而且处理name、parent和 description的代码也没有什么区别了，检查错误也更简单了。你在这里还给你的Library增加了一个SplitString函数，说不定在以 后什么地方就用上了，比Parse这种专门的函数要强很多倍。<br /></p><p>大家可以发现，在这里重复的东西并不仅仅是复制了代码，而是由于你把 同一个信息散播在了代码的各个部分导致了有很多相近的代码也散播在各个地方，而且还不是那么好通过抽成函数的方法来解决。因为在这种情况下，就算你把重复 的代码抽成了Parse函数，你把函数调用了几次实际上也等于重复了信息。因此正确的方法就是把做事情的方法变一下，写成SplitString。这个 SplitString函数并不是通过把重复的代码简单的抽取成函数而做出来的。<u><strong>去掉重复的信息会让你的代码的结构发生本质的变化</strong></u>。</p><p>这个问题其实也有很多变体：</p><ul><li>不能有Magic Number。L';'出现了很多遍，其实就是个Magic Number。所以我们要给他个名字，譬如说delimiter。</li><li>不要复制代码。这个应该不用我讲了。 <br /></li><li>解耦要做成正交的。SplitString虽然不是直接冲着读config来写的，但是它反映了一个在其它地方也会遇到的常见的问题。如果用Parse的那个版本，显然只是看起来解决了问题而已，并没有给你带来任何额外的效益。</li></ul><p>信息一旦被你repeat了，你的代码就会不同程度的出现各种腐烂或者破窗，上面那三条其实只是我能想到的比较常见的表现形式。这件事情也告诉我们，当高手告诉你什么什么不能做的时候，得想一想背后的原因，不然跟封建迷信有什么区别。</p></div><img src ="http://www.cppblog.com/vczh/aggbug/207658.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2014-07-15 22:44 <a href="http://www.cppblog.com/vczh/archive/2014/07/15/207658.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2013年终总结</title><link>http://www.cppblog.com/vczh/archive/2014/01/04/205169.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 04 Jan 2014 13:52:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2014/01/04/205169.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/205169.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2014/01/04/205169.html#Feedback</comments><slash:comments>9</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/205169.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/205169.html</trackback:ping><description><![CDATA[<p>2013年我就干了两件事情。第一件是<a href="http://gaclib.net/">gaclib</a>，第二件是<a href="https://github.com/vczh/tinymoe/">tinymoe</a>。
</p><p>
&#160;</p><p>Gaclib终于做到安全的支持C++的反射、从XML加载窗口和控件了。现在在实现的东西则是一个给gaclib用的workflow小脚本，用来写一些简单的view的逻辑、定义viewmodel接口，还有跟WPF差不多的data binding。
</p><p>
&#160;</p><p>Tinymoe是我大二的时候就设计出来的东西，无奈以前对计算机的理论基础了解的太少，以至于没法实现，直到现在才能做出来。总的来说tinymoe是一个模仿英语语法的严肃的编程语言——也就是说它是不基于NLP的，语法是严格的，写错一个单词也会编译不过。因此所有的函数都要写成短语，包括控制流语句也是。所以我就想了一想，能不能让分支、循环、异常处理和异步处理等等其他语言的内置的功能在我这里都变成库？这当然是可以的，只要做全文的cps变换，然后要求这些控制流函数也写成cps的风格就可以了。
</p><p>
&#160;</p><p>目前的第一个想法是，等搞好了之后先生成javascript或者C#的代码，不太想写自己的VM了，然后就出一个系列文章叫做《看实例跟大牛学编译原理》，就以这个tinymoe作为例子，来把《如何设计一门语言》延续下去，啊哈哈哈哈哈。
</p><p>
&#160;</p><p>写博客是一件很难的事情。我大三开始经营这个cppblog/cnblogs的博客的时候，一天都可以写一篇，基本上是在记录我学到的东西和我造的轮子。现在都比较懒了，觉得整天说自己在开发什么也没意思了，于是想写一些别的，竟然不知如何下手，于是就出了各种没填完的系列。
</p><p>
&#160;</p><p>我觉得我学编程这13年来也是学到了不少东西的，除了纯粹的api和语言的知识以外，很多方法论都给我起到了十分重要的作用。一开始是面向对象，然后是数据结构算法，然后是面向方面编程，然后是函数式编程，后来还接触了各种跟函数式编程有关的概念，譬如说reactive programming啊，actor啊，异步啊，continuation等等。脑子里充满了各种各样的方法论和模型之后，现在无论写什么程序，几乎都可以拿这些东西往上套，然后做出一个维护也很容易（前提是有这些知识），代码也很简洁的程序了。
</p><p>
&#160;</p><p>工作的这四年半里，让我学习到了文档和自动化测试的重要性，于是利用这几年我把文档和测试的能力也锻炼的差不多了。现在我觉得，技术的话工作应付起来是超级简单，但是自己对技术的热情还是促使我不断的研究下去。2014年应该研究的技能就是嘴炮了。</p> <img src ="http://www.cppblog.com/vczh/aggbug/205169.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2014-01-04 21:52 <a href="http://www.cppblog.com/vczh/archive/2014/01/04/205169.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（十二）——设计可扩展的类型</title><link>http://www.cppblog.com/vczh/archive/2013/11/10/204189.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sun, 10 Nov 2013 09:06:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/11/10/204189.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/204189.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/11/10/204189.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/204189.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/204189.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在思考怎么写这一篇文章的时候，我又想到了以前讨论正交概念的事情。如果一个系统被设计成正交的，他的功能扩展起来也可以很容易的保持质量这是没错的，但是对于每一个单独给他扩展功能的个体来说，这个系统一点都不好用。所以我觉得现在的语言被设计成这样也是有那么点道理的。就算是设计Java的那谁，他也不是傻逼，那为什么Java会被设计成这样？我觉得这跟他刚开始想让金字塔的底层程序员也可以顺利使用Java是有关系...&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2013/11/10/204189.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/204189.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-11-10 17:06 <a href="http://www.cppblog.com/vczh/archive/2013/11/10/204189.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（十一）&amp;mdash;&amp;mdash;删减语言的功能</title><link>http://www.cppblog.com/vczh/archive/2013/10/19/203819.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 19 Oct 2013 13:51:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/10/19/203819.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/203819.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/10/19/203819.html#Feedback</comments><slash:comments>17</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/203819.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/203819.html</trackback:ping><description><![CDATA[<p>大家看到这个标题肯定会欢呼雀跃了，以为功能少的语言就容易学。其实完全不是这样的。功能少的语言如果还适用范围广，那所有的概念必定是正交的，最后就会变得跟数学一样。数学的概念很正交吧，正交的东西都特别抽象，一点都不直观的。不信？出门转左看Haskell，还有抽象代数。因此删减语言的功能是需要高超的技巧的，这跟大家想的，还有跟go那帮人想的，可以断定完全不一样。</p>
<p>首先，我们要知道到底为什么需要删减功能。在这里我们首先要达成一个共识&#8212;&#8212;人都是很贱的。一方面在发表言论的时候光面堂皇的表示，要以需求变更和可维护性位中心；另一方面自己写代码的时候又总是不惜&#8220;后来的维护者所支付的代价代价&#8221;进行偷懒。有些时候，人就是被语言惯坏的，所以需要对功能进行删减的同时，<strong>又不降低语言的表达能力</strong>，从而让做不好的事情变得更难（完全不让别人做不好的事情是不可能的），这样大家才会倾向于写出结构好的程序。</p>
<p>于是，语法糖到底是不是需要被删减的对象呢？显然不是。一个好的语言，采用的概念是正交的。尽管正交的概念可以在拼接处我们需要的概念的时候保持可维护性和解耦，但是往往这么做起来却不是那么舒服的，所以需要语法糖。那如果不是语法糖，到底需要删减什么呢？</p>
<p>这一集我们就来讨论面向对象的语言的事情，看看有什么是可以去掉的。</p>
<p>在面向对象刚刚流行起来的时候，大家就在讨论什么搭积木编程啊、is-a、has-a这些概念啊、面向接口编程啊、为什么硬件的互相插就这么容易软件就不行呢，然后就开始搞什么COM啊、SOA啊这些的确让插变得更容易，但是部署起来又很麻烦的东西。到底是什么原因造成OO没有想象中那么好用呢？</p>
<p>之所以会想起这个问题，其实是因为最近在我们研究院的工位上出现了一个相机的三脚架，这个太三脚架用来固定一个手机干点邪恶的事情，于是大家就围绕这个事情展开了讨论&#8212;&#8212;譬如说为什么手机和三脚架是正交的，中间只要一个前凸后凹的用来插的小铁块就可以搞定，而软件就不行呢？</p>
<p>于是我就在想，这不就是跟所谓的面向接口编程一样，只要你全部东西都用接口，那软件组合起来就很简单了吗。这样就算刚好对不上，只要写个adaptor，就可以搞定了。其实这种做法我们现在还是很常见的。举个例子，有些时候我们需要Visual C++ 2013这款全球最碉堡的C++ IDE来开发世界上最好的复杂的软件，不过自带的那个cl.exe实在是算不上最好的。那怎么办，为了用一款更好的编译器，放弃这个IDE吗？显然不是。正确的解决方法是，买intel的icc，然后换掉cl.exe，然后一切照旧。</p>
<p>其实那个面向接口编程就有点这个意思。有些时候一个系统大部分是你所需要的，别人又不能满足，但是刚好这个系统的一个重要部分你手上又有更好的零件可以代替。那你是选择更好的零件，还是选择大部分你需要的周边工具呢？<strong>为什么就非得二选一呢</strong>？如果大家都是面向接口编程，那你只需要跟cl.exe换成icc一样，写个adaptor就可以上了。</p>
<p>好了，那接口是什么？其实这并没有什么深奥的理解，接口指的就是java和C#里面的那个interface，是很直白的。不知道为什么后来传着传着这条建议就跟一些封装偶合在一起，然后各种非面向对象语言就把自己的某些部分曲解为interface，成功地把&#8220;面向接口编程&#8221;变成了一句废话。</p>
<p>不过在说interface之前，有一个更简单但是可以类比的例子，就是函数和lambda expression了。如果一个语言同时存在函数和lambda expression，那么其实有一个是多余的&#8212;&#8212;也就是函数了。一个函数总是可以被定义为初始化的时候给了一个lambda expression的只读变量。这里并不存在什么性能问题，因为这种典型的写法，编译器往往可以识别出来，最终把它优化成一个函数。当我们把一个函数名字当成表达式用，获得一个函数指针的时候，其实这个类型跟lambda expression并没有任何区别。一个函数就只有这两种用法，因此实际上把函数去掉，留下lambda expression，整个语言根本没有发生变化。<strong>于是函数在这种情况下就属于可以删减的功能</strong>。</p>
<p>那class和interface呢？跟上面的讨论类似，我主张class也是属于可以删减的功能之一，而且删减了的话，程序员会因为人类的本性而写出更好的代码。把class删掉其实并没有什么区别，我能想到的唯一的区别也就是class本身从此再也不是一个类型，而是一个函数了。这有关系吗？完全没有，你用interface就行了。</p>
<p>class和interface的典型区别就是，interface所有的函数都是virtual的，而且没有局部变量。class并不是所有的函数都是virtual的&#8212;&#8212;java的函数默认virtual但是可以改，C++和C#则默认不virtual但是可以改。就算你把所有的class的函数都改成virtual，那你也会因此留下一些状态变量。这有什么问题呢？假设C++编译器是一个接口，而Visual C++和周边的工具则是依赖于这个class所创造出来的东西。如果你想把cl.exe替换成icc，实际上只要new一个新的icc就可以了。而如果C++编译器是一个class的话，你就不能替换了&#8212;&#8212;就算class所有的函数都是virtual的，你也不可能给出一个规格相同而实现不同的icc&#8212;&#8212;<strong>因为你已经被class所声明的构造函数、析构函数以及写好的一些状态变量（成员变量）所绑架了</strong>！</p>
<p>那我们可以想到的一个迫使大家都写出<strong>倾向于比以前更可以组合</strong>的程序，要怎么改造语言才可以呢？其实很简单，只需要不把class的名字看成一个类型，而把他看成一个函数就可以了。class本身有多个构造函数，其实也就是这个意思。这样的话，所有原本要用到class的东西，我们就会去定义一个接口了。而且这个接口往往会是最小化的，因为完全没有必要去声明一些用不到的函数。</p>
<p>于是跟去掉函数而留下匿名函数（也就是lambda expression）类似，我们也可以去掉class而留下匿名class的。Java有匿名class，所以我们完全不会感到这个概念有多么的陌生。于是我们可以来检查一下，这样会不会让我们丧失什么表达方法。</p>
<p>首先，是关于类的继承。我们有四种方法来使用类的继承。</p>
<p>1、类似于C#的Control继承出Button。这完全是接口规格的继承。我们继承出一个Button，不是为了让他去实现一个Control，而是因为Button比Control多出了一些新东西，而且直接体现在成员函数上面。因此在这个框架下，<strong>我们需要做的是IControl继承出IButton</strong>。</p>
<p>2、类似于C#的TextReader继承出StreamReader。StreamReader并不是为了给TextReader添加新功能，而是为了给TextReader指定一个来源&#8212;&#8212;Stream。因此这更类似于接口和实现的区别。因此在这个框架下，<strong>我们需要的是用CreateStreamReader函数来创建一个ITextReader</strong>。</p>
<p>3、类似于C#的XmlNode继承出XmlElement。这纯粹是数据的继承关系。我们之所以这么做，不是因为class的用法是设计来这么用的，而是因为C++、Java或者C#并没有别的办法可以让我们来表达这些东西。在C里面我们可以用一个union加上一个enum来做，而且大家基本上都会这么做，所以我们可以看到这实际上是为了拿到一个tag来让我们知道如何解释那篇内存。但是C语言的这种做法只有大脑永远保持清醒的人可以使用，而且我们可以看到在函数式语言里面，Haskell、F#和Scala都有自己的一种独有的强类型的union。<strong>因此在这个框架下，我们需要做的是让struct可以继承，并且提供一个Nullable&lt;T&gt;（C#也可以写成T?）的类型</strong>&#8212;&#8212;等价于指向struct的引用&#8212;&#8212;来让我们表达&#8220;这里是一个关于数据的union：XmlNode，他只可能是XmlElement、XmlText、XmlCData等有限几种可能&#8221;。这完全不关class的事情。</p>
<p>4、在Base里面留几个纯虚函数，让Derived继承自Base并且填补他们充当回调使用&#8212;&#8212;卧槽都知道是回调了为什么还要用class？设计模式帮我们准备好了Template Method Pattern，<strong>我们完全可以把这几个回调写在一个interface里面，让Base的构造函数接受这个interface</strong>，效果完全没有区别。</p>
<p>因此我们可以看到，干掉class留下匿名class，根本不会对语言的表达能力产生影响。而且这让我们可以把所有需要的依赖都从class转成interface。interface是很好adapt的。还是用Visual C++来举例子。我们知道cl.exe和icc都可以装，那gcc呢？cl.exe和icc是兼容的，而gcc完全是另一套。我们只需要简单地adapt一下（尽管有可能不那么简单，但总比完全不能做强多了），就可以让VC++使用gcc了。class和interface的关系也是类似的。如果class A依赖于class B，那这个依赖是绑死的。尽管class A我们很欣赏，但是由于class B实现得太傻比从而导致我们必须放弃class A这种事情简直是不能接受的。如果class A依赖于interface IB，就算他的缺省实现CreateB()函数我们不喜欢，我们可以自己实现一个CreateMyB()，从而吧我们自己的IB实现给class A，这样我们又可以提供更好的B的同时不需要放弃我们很需要的A了。</p>
<p>不过其实每次CreateA(CreateMyB())这种事情来得到一个IA的实现也是很蠢得，优点化神奇为腐朽的意思。不过这里就是IoC&#8212;&#8212;Inverse of Control出场的地方了。这完全是另一个话题，而且Java和C#的一些类库（包括我的<a href="http://www.gaclib.net" target="_blank">GacUI</a>）已经深入的研究了IoC、<strong>正确</strong>使用了它并且发挥得淋漓尽致。这就是另一个话题了。如何用好interface，跟class是否必须是类型，没什么关系。</p>
<p>但是这样做还有一个小问题。假设我们在写一个UI库，定义了IControl并且有一个函数返回了一个IControl的实现，那我们在开发IButton和他的实现的时候，要如何利用IControl的实现呢？本质上来说，其实我们只需要创造一个IControl的实现x，然后把IButton里面所有原本属于IControl的函数都重定向到这个x上面去，就等价于继承了。不过这个写起来就很痛苦了，<strong>因此我们需要一个语法糖来解决它，传说中的Mixin就可以上场了</strong>。不知道Mixin？这种东西跟prototype很接近但是实际上他不是prototype，所以类似的想法经常在javascript和ruby等动态语言里面出现。相信大家也不会陌生。</p>
<p>上面基本上论证了把class换成匿名class的可能性（完全可能），和他对语言表达能力的影响（毫无影响），以及他对系统设计的好处（更容易通过人类的人性的弱点来引导我们写出<strong>比现在</strong>更加容易解耦的系统）。尽管这不是银弹，但显然比现在的做法要强多了。最重要的是，因为class不是一个类型，所以你没办法从IX强转成XImpl了，于是我们只能够设计出不需要知道到底谁实现了IX的算法，可靠性迅速提高。如果IY继承自IX的话，那IX可以强转成IY就类似于COM的QueryInterface一样，从&#8220;查看到底是谁实现的&#8221;升华到了&#8220;查看这个IX是否具有IY所描述的功能&#8221;，不仅B格提高了，而且会让你整个软件的质量都得到提高。</p>
<p>因此把class换成匿名class，让原本正确使用OO的人更容易避免无意识的偷懒，让原本不能正确使用OO的人迅速掌握如何正确使用OO，封死了一大堆因为偷懒而破坏质量的后门，具有相当的社会意义（哈哈哈哈哈哈哈哈）。</p>
<p>我之所以写这篇文章是为了告诉大家，通过删减语言的功能来让语言变得更好完全是可能的。但这并不意味着你能通过你自己的口味、偷懒的习惯、B格、<strong>因为智商低而学不会</strong>等各种奇怪的理由来衡量一个语言的功能是否应该被删除。只有冗余的东西在他带来危害的时候，我们应该果断删除它（譬如在有interface前提下的class）。而且通常我们为了避免正交的概念所本质上所不可避免的增加理解难度所带来的问题，我们还需要相应的往语言里面加入语法糖或者新的结构（匿名class、强类型union等）。让语言变得更简单从来不是我们的目标，让语言变得更好用才是。而且一个语言不容易学会的话，我们有各种方法可以解决&#8212;&#8212;譬如说增加常见情况下可以解决问题的语法糖、免费分享知识、通过努力提高自己的智商（虽然有一部分人会因此感到绝望不过反正社会上有那么多职业何必非得跟死程死磕）等等有效做法。</p>
<p>于是在我自己设计的脚本里面，我打算全面实践这个想法。</p><img src ="http://www.cppblog.com/vczh/aggbug/203819.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-10-19 21:51 <a href="http://www.cppblog.com/vczh/archive/2013/10/19/203819.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（十）&amp;mdash;&amp;mdash;正则表达式与领域特定语言（DSL）</title><link>http://www.cppblog.com/vczh/archive/2013/09/16/203249.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 16 Sep 2013 01:26:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/09/16/203249.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/203249.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/09/16/203249.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/203249.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/203249.html</trackback:ping><description><![CDATA[<p>几个月前就一直有博友关心DSL的问题，于是我想一想，我在<a href="https://gac.codeplex.com" target="_blank">gac.codeplex.com</a>里面也创建了一些DSL，于是今天就来说一说这个事情。</p> <p>创建DSL恐怕是很多人第一次设计一门语言的经历，很少有人一开始上来就设计通用语言的。我自己第一次做这种事情是在高中写这个<a href="http://bbs.gameres.com/thread_5391_1_1.html" target="_blank">傻逼ARPG</a>的时候了。当时做了一个超简单的脚本语言，长的就跟汇编差不多，虽然每一个指令都写成了调用函数的形态。虽然这个游戏需要脚本在剧情里面控制一些人物的走动什么的，但是所幸并不复杂，于是还是完成了任务。一眨眼10年过去了，现在在写<a href="http://www.gaclib.net/" target="_blank">GacUI</a>，为了开发的方便，我自己做了一些DSL，或者实现了别人的DSL，渐渐地也明白了一些设计DSL的手法。不过在讲这些东西之前，我们先来看一个令我们又爱（对所有人）又恨（反正我不会）的DSL——正则表达式！</p> <p><font size="5"><strong>一、正则表达式</strong></font></p> <p>正则表达式可读性之差我们人人都知道，而且正则表达式之难写好都值得O&#8217;reilly出一本两厘米厚的书了。根据我的经验，只要先学好编译原理，然后按照.net的规格自己撸一个自己的正则表达式，基本上这本书就不用看了。因为正则表达式之所以要用奇怪的方法去写，只是因为你手上的引擎是那么实现的，所以你需要顺着他去写而已，没什么特别的原因。而且我自己的正则表达式拥有DFA和NFA两套解析器，我的正则表达式引擎会通过检查你的正则表达式来检查是否可以用DFA，从而可以优先使用DFA来运行，省去了很多其实不是那么重要的麻烦（譬如说a**会傻逼什么的）。这个东西我自己用的特别开心，代码也放在<a href="https://gac.codeplex.com/" target="_blank">gac.codeplex.com</a>上面。</p> <p>正则表达式作为一门DSL是当之无愧的——因为它用了一种紧凑的语法来让我们可以定义一个字符串的集合，并且取出里面的特征。大体上语法我还是很喜欢的，我唯一不喜欢的是正则表达式的括号的功能。括号作为一种指定优先级的方法，几乎是无法避免使用的。但是很多流行的正则表达式的括号竟然还带有捕获的功能，实在是令我大跌眼镜——因为大部分时候我是不需要捕获的，这个时候只会浪费时间和空间去做一些多余的事情而已。所以在我自己的正则表达式引擎里面，括号是不捕获的。如果要捕获，就得用特殊的语法，譬如说(&lt;name&gt;pattern)把pattern捕获到一个叫做name的组里面去。</p> <p>那我们可以从正则表达式的语法里面学到什么DSL的设计原则呢？我认为，DSL的原则其实很简单，只有以下三个：</p> <ol> <li>短的语法要分配给常用的功能  <li>语法要么可读性特别好（从而比直接用C#写直接），要么很紧凑（从而比直接用C#写短很多）  <li>API要容易定义（从而用C#调用非常方便，还可以确保DSL的目标是明确又简单的）</li></ol> <p>很多DSL其实都满足这个定义。SQL就属于API简单而且可读性好的那一部分（想想ADO.NET），而正则表达式就属于API简单而且语法紧凑的那一部分。为什么正则表达式可以设计的那么紧凑呢？现在让我们来一一揭开它神秘的面纱。</p> <p>正则表达式的基本元素是很少的，只有连接、分支和循环，还有一些简单的语法糖。连接不需要字符，分支需要一个字符&#8220;|&#8221;，循环也只需要一个字符&#8220;+&#8221;或者&#8220;*&#8221;，还有代表任意字符的&#8220;.&#8221;，还有代表多次循环的{5,}，还有代表字符集合的[a-zA-Z0-9_]。对于单个字符的集合来讲，我们甚至不需要[]，直接写就好了。除此之外因为我们用了一些特殊字符所以还得有转义（escaping）的过程。那让我们数数我们定义了多少字符：&#8220;|+*[]-\{},.()&#8221;。用的也不多，对吧。</p> <p>尽管看起来很乱，但是正则表达式本身也有一个严谨的语法结构。关于我的正则表达式的语法树定义可以看这里：<a title="https://gac.codeplex.com/SourceControl/latest#Common/Source/Regex/RegexExpression.h" href="https://gac.codeplex.com/SourceControl/latest#Common/Source/Regex/RegexExpression.h" target="_blank">https://gac.codeplex.com/SourceControl/latest#Common/Source/Regex/RegexExpression.h</a>。在这里我们可以整理出一个语法：  <div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>DIGIT ::= [<span style="color: #800080">0</span>-<span style="color: #800080">9</span><span style="color: #000000">]
LITERAL ::</span>= [^|+*\[\]\-\\{}\^<span style="color: #000000">,.()]
ANY_CHAR ::</span>= LITERAL | <span style="color: #800000">"</span><span style="color: #800000">^</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">|</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">+</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">*</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">[</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">]</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">-</span><span style="color: #800000">"</span> | <span style="color: #800000">"</span><span style="color: #800000">\" | </span><span style="color: #800000">"</span>{<span style="color: #800000">"</span><span style="color: #800000"> | </span><span style="color: #800000">"</span>}<span style="color: #800000">"</span><span style="color: #800000"> | </span><span style="color: #800000">"</span>,<span style="color: #800000">"</span><span style="color: #800000"> | </span><span style="color: #800000">"</span>.<span style="color: #800000">"</span><span style="color: #800000"> | </span><span style="color: #800000">"</span>(<span style="color: #800000">"</span><span style="color: #800000"> | </span><span style="color: #800000">"</span>)<span style="color: #800000">"
</span><span style="color: #000000">
CHAR
    ::</span>=<span style="color: #000000"> LITERAL
    ::</span>= <span style="color: #800000">"</span><span style="color: #800000">\" ANY_CHAR</span>
<span style="color: #000000">
CHARSET_COMPONENT
    ::</span>=<span style="color: #000000"> CHAR
    ::</span>= CHAR <span style="color: #800000">"</span><span style="color: #800000">-</span><span style="color: #800000">"</span><span style="color: #000000"> CHAR

CHARSET
    ::</span>=<span style="color: #000000"> CHAR
    ::</span>= <span style="color: #800000">"</span><span style="color: #800000">[</span><span style="color: #800000">"</span> [<span style="color: #800000">"</span><span style="color: #800000">^</span><span style="color: #800000">"</span>] { CHARSET_COMPONENT } <span style="color: #800000">"</span><span style="color: #800000">]</span><span style="color: #800000">"</span><span style="color: #000000">

REGEX_0
    ::</span>=<span style="color: #000000"> CHARSET
    ::</span>= REGEX_0 <span style="color: #800000">"</span><span style="color: #800000">+</span><span style="color: #800000">"</span><span style="color: #000000">
    ::</span>= REGEX_0 <span style="color: #800000">"</span><span style="color: #800000">*</span><span style="color: #800000">"</span><span style="color: #000000">
    ::</span>= REGEX_0 <span style="color: #800000">"</span><span style="color: #800000">{</span><span style="color: #800000">"</span> { DIGIT } [<span style="color: #800000">"</span><span style="color: #800000">,</span><span style="color: #800000">"</span> [ { DIGIT } ]] <span style="color: #800000">"</span><span style="color: #800000">}</span><span style="color: #800000">"</span><span style="color: #000000">
    ::</span>= <span style="color: #800000">"</span><span style="color: #800000">(</span><span style="color: #800000">"</span> REGEX_2 <span style="color: #800000">"</span><span style="color: #800000">)</span><span style="color: #800000">"</span><span style="color: #000000">

REGEX_1
    ::</span>=<span style="color: #000000"> REGEX_0
    ::</span>=<span style="color: #000000"> REGEX_1 REGEX_0

REGEX_2
    ::</span>=<span style="color: #000000"> REGEX_1
    ::</span>= REGEX_2 <span style="color: #800000">"</span><span style="color: #800000">|</span><span style="color: #800000">"</span><span style="color: #000000"> REGEX_1

REGULAR_EXPRESSION
    ::</span>= REGEX_2</pre></div>
<p>这只是随手写出来的语法，尽管可能不是那么严谨，但是代表了正则表达式的所有结构。为什么<strong>我们要熟练掌握EBNF的阅读和编写</strong>？因为当我们用EBNF来看待我们的语言的时候，我们就不会被愈发的表面所困扰，我们会投过语法的外衣，看到语言本身的结构。脱别人衣服总是很爽的。</p>
<p>于是我们也要透过EBNF来看到正则表达式本身的结构。其实这是一件很简单的事情，只要把EBNF里面那些&#8220;fuck&#8221;这样的字符字面量去掉，然后规则就会分为两种：</p>
<p>1：规则仅由终结符构成——这是基本概念，譬如说上面的CHAR什么的。<br>2：规则的构成包含非终结符——这就是一个结构了。</p>
<p>我们甚至可以利用这种方法迅速从EBNF确定出我们需要的语法树长什么样子。具体的方法我就不说了，大家自己联系一下就会悟到这个简单粗暴的方法了。但是，我们在设计DSL的时候，是要反过来做的。<strong>首先确定语言的结构，翻译成语法树，再翻译成不带&#8220;fuck&#8221;的&#8220;骨架EBNF&#8221;，再设计具体的细节写成完整的EBNF</strong>。</p>
<p>看到这里大家会觉得，其实正则表达式的结构跟四则运算式子是没有区别的。正则表达式的*是后缀操作符，|是中缀操作符，连接也是中最操作符——而且操作符是隐藏的！我猜perl系正则表达式的作者当初在做这个东西的时候，肯定纠结过&#8220;隐藏的中缀操作符&#8221;应该给谁的问题。不过其实我们可以通过收集一些素材，用不同的方案写出正则表达式，最后经过统计发现——隐藏的中缀操作符给连接操作是最靠谱的。</p>
<p>为什么呢？我们来举个例子，如果我们把连接和分支的语法互换的话，那么原本&#8220;fuck|you&#8221;就要写成&#8220;(f|u|c|k)(y|o|u)&#8221;了。写多几个你会发现，的确连接是比分支更常用的，所以短的那个要给连接，所以连接就<strong>被分配</strong>了一个隐藏的中缀操作符了。</p>
<p>上面说了这么多废话，只是为了说明白一个道理——要先从结构入手然后才设计语法，并且要把最短的语法分配给最常用的功能。因为很多人设计DSL都反着来，然后做成了屎。</p>
<p><strong><font size="5">二、Fpmacro</font></strong></p>
<p>第二个要讲的是Fpmacro。简单来说，Fpmacro和C++的宏是类似的，但是C++的宏是从外向内展开的，这意味着dynamic scoping和call by name。Fpmacro是从内向外展开的，这意味着lexical scoping和call by value。这些概念我在<a href="http://www.cppblog.com/vczh/archive/2013/07/05/201541.html" target="_blank">第七篇文章</a>已经讲了，大家也知道C++的宏是一件多么不靠谱的事情。但是为什么我要设计Fpmacro呢？因为有一天我终于需要类似于Boost::Preprocessor那样子的东西了，因为我要生成<a href="https://gac.codeplex.com/SourceControl/latest#Common/Source/Tuple.h" target="_blank">类似这样的代码</a>。但是C++的宏实在是太他妈恶心了，恶心到连我都不能驾驭它。最终我就做出了Fpmacro，于是我可以用<a href="https://gac.codeplex.com/SourceControl/latest#Common/Source/Tuple.h.fpm" target="_blank">这样的宏</a>来生成上面提到的文件了。</p>
<p>我来举个例子，如果我要生成下面的代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> a1 = <span style="color: #800080">1</span><span style="color: #000000">;
</span><span style="color: #0000ff">int</span> a2 = <span style="color: #800080">2</span><span style="color: #000000">;
</span><span style="color: #0000ff">int</span> a3 = <span style="color: #800080">3</span><span style="color: #000000">;
</span><span style="color: #0000ff">int</span> a4 = <span style="color: #800080">4</span><span style="color: #000000">;
cout</span>&lt;&lt;a1&lt;&lt;a2&lt;&lt;a3&lt;&lt;a4&lt;&lt;endl;</pre></div>
<p>就要写下面的Fpmacro代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>$$define $COUNT <span style="color: #800080">4</span> <span style="color: #008000">/*</span><span style="color: #008000">定义数量：4</span><span style="color: #008000">*/</span><span style="color: #000000">
$$define $USE_VAR($index) a$index </span><span style="color: #008000">/*</span><span style="color: #008000">定义变量名字，这样$USE_VAR(10)就会生成&#8220;a10&#8221;</span><span style="color: #008000">*/</span><span style="color: #000000">

$$define $DEFINE_VAR($index) $$begin </span><span style="color: #008000">/*</span><span style="color: #008000">定义变量声明，这样$DEFINE_VAR(10)就会生成&#8220;int a10 = 10;&#8221;</span><span style="color: #008000">*/</span>
<span style="color: #0000ff">int</span> $USE_VAR($index) =<span style="color: #000000"> $index;
$( ) <span style="color: #008000">/*</span><span style="color: #008000">用来换行——会多出一个多余的空格不过没关系</span><span style="color: #008000">*/</span><span style="color: #000000"> </span>
$$end

$loop($COUNT,</span><span style="color: #800080">1</span>,$DEFINE_VAR) <span style="color: #008000">/*</span><span style="color: #008000">首先，循环生成变量声明</span><span style="color: #008000">*/</span><span style="color: #000000">
cout</span>&lt;&lt;$loopsep($COUNT,<span style="color: #800080">1</span>,$USE_VAR,&lt;&lt;)&lt;&lt;endl; <span style="color: #008000">/*</span><span style="color: #008000">其次，循环使用这些变量</span><span style="color: #008000">*/</span></pre></div>
<p>顺便，Fpmacro的语法在<a href="https://gac.codeplex.com/SourceControl/latest#Common/Tools/Fpmacro/Fpmacro/FpmacroParser.parser.txt" target="_blank">这里</a>，FpmacroParser.h/cpp是由这个语法生成的，剩下的几个文件就是C++的源代码了。不过因为今天讲的是如何设计DSL，那我就来讲一下，我当初为什么要把Fpmacro设计成这个样子。</p>
<p>在设计之前，首先我们需要知道Fpmacro的目标——设计一个没有坑的宏，而且这个宏还要支持分支和循环。那如何避免坑呢？最简单的方法就是把宏看成函数，真正的函数。当我们把一个宏的名字当成参数传递给另一个宏的时候，这个名字就成为了函数指针。这一点C++的宏是不可能完全的做到的，这里的坑实在是太多了。而且Boost::Preprocessor用来实现循环的那个技巧实在是我操太他妈难受了。</p>
<p>于是，我们就可以把需求整理成这样：</p>
<ol>
<li>Fpmacro的代码由函数组成，每一个函数的唯一目的都是生成C++代码的片段。 
<li>函数和函数之间的空白可以用来写代码。把这些代码收集起来就可以组成&#8220;main函数&#8221;了，从而构成Fpmacro代码的主体。 
<li>函数可以有内部函数，在代码复杂的时候可以充当一些namespace的功能，而且内部函数都是私有的。 
<li>Fpmacro代码可以include另一份Fpmacro代码，可以实现全局配置的功能。 
<li>Fpmacro必须支持分支和循环，而且他们的语法和函数调用应该一致。 
<li>用来代表C++代码的部分需要的转义应该降到最低。 
<li>即使是非功能代码部分，括号也必须配对。这是为了定义出一个清晰的简单的语法，而且因为C++本身也是括号配对的，所以这个规则并没有伤害。 
<li>C++本身对空格是有很高的容忍度的，因此Fpmacro作为一个以换行作为分隔符的语言，并不需要具备特别精确的控制空格的功能。</li></ol>
<p>为什么要强调转义呢？因为如果用Fpmacro随便写点什么代码都要到处转义的话，那还怎么写得下去呀！</p>
<p>这个时候我们开始从结构入手。Fpmacro的结构是简单的，只有下面几种：</p>
<ol>
<li>普通C++代码 
<li>宏名字引用 
<li>宏调用 
<li>连接 
<li>括号 
<li>表达数组字面量（最后这被证明是没有任何意义的功能）</li></ol>
<p>根据上面提到的DSL三大原则，我们要给最常用的功能配置最短的语法。那最短的功能是什么呢？跟正则表达式一样，是连接。所以要给他一个隐藏的中缀运算符。其次就要考虑到转义了。如果Fpmacro大量运用的字符与C++用到的字符一样，那么我们在C++里面用这个字符的时候，就得转义了。这个是绝对不能接受的。我们来看看键盘，C++没用到的也就只有@和$了。这里我因为个人喜好，选择了$，它的功能大概跟C++的宏里面的#差不多。</p>
<p>那我们如何知道我们的代码片段是访问一个C++的名字，还是访问一个Fpmacro的名字呢？为了避免转义，而且也顺便可以突出Fpmacro的结构本身，我让所有的Fpmacro名字都要用$开头，无论是函数名还是参数都一样。于是定义函数就用$$define开始，而且多行的函数还要用$$begin和$$end来提示（见上面的例子）。函数调用就可以这么做：$名字(一些参数)。因为不管是参数名还是函数名都是$开头的，所以函数调用肯定也是$开头的。那写出来的代码真的需要转义怎么办呢？直接用$(字符)就行了。<strong>这个时候我们可以来检查一下这样做是不是会定义出歧义的语法</strong>，答案当然是不会。</p>
<p>我们定义了$作为Fpmacro的名字前缀之后，是不是一个普通的C++代码（因此没有$），直接贴上去就相当于一个Fpmacro代码呢？结论当然是成立的。仔细选择这些语法可以让我们在只想写C++的时候可以专心写C++而不会被各种转义干扰到（想想在C++里面写正则表达式的那一堆斜杠卧槽）。</p>
<p>到了这里，就到了最关键的一步了。那我们把一个Fpmacro的名字传递给参数的时候，究竟是什么意思呢？一个Fpmacro的名字，要么就是一个字符串，要么就是一个Fpmacro函数，不会有别的东西了（其实还可能是数组，但是最后证明没用）。这个纯洁性要一直保持下去。就跟我们在C语言里面传递一个函数指针一样，不管传递到了哪里，我们都可以随时调用它。</p>
<p>那Fpmacro的函数到底有没有包括上下文呢？因为Fpmacro和pascal一样有&#8220;内部函数&#8221;，所以当然是要有上下文的。但是Fpmacro的名字都是只读的，所以只用shared_ptr来记录就可以了，不需要出动GC这样的东西。关于为什么带变量的闭包就必须用GC，这个大家可以去想一想。这是Fpmacro的函数像函数式语言而不是C语言的一个地方，这也是为什么我把名字写成了Fpmacro的原因了。</p>
<p>不过Fpmacro是不带lambda表达式的，因为这样只会把语法搞得更糟糕。再加上Fpmacro允许定义内部函数和Fpmacro名字是只读的这两条规则，所有的lambda表达式都可以简单的写成一个内部函数然后赋予它一个名字。因此这一点没有伤害。那什么时候需要传递一个Fpmacro函数呢进另一个函数呢？当然就只有循环了。Fpmacro的内置函数有分支循环还有简单的数值计算和比较功能。</p>
<p>我们来做一个小实验，生成下面的代码：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">void</span> Print(<span style="color: #0000ff">int</span><span style="color: #000000"> a1)
{
    cout</span>&lt;&lt;<span style="color: #800000">"</span><span style="color: #800000">1st</span><span style="color: #800000">"</span>&lt;&lt;a1&lt;&lt;<span style="color: #000000">endl;
}

</span><span style="color: #0000ff">void</span> Print(<span style="color: #0000ff">int</span> a1, <span style="color: #0000ff">int</span><span style="color: #000000"> a2)
{
    cout</span>&lt;&lt;<span style="color: #800000">"</span><span style="color: #800000">1st</span><span style="color: #800000">"</span>&lt;&lt;a1&lt;&lt;<span style="color: #800000">"</span><span style="color: #800000">, </span><span style="color: #800000">"</span>&lt;&lt;<span style="color: #800000">"</span><span style="color: #800000">2nd</span><span style="color: #800000">"</span>&lt;&lt;a2&lt;&lt;<span style="color: #000000">endl;
}

....

</span><span style="color: #0000ff">void</span> Print(<span style="color: #0000ff">int</span> a1, <span style="color: #0000ff">int</span> a2, ... <span style="color: #0000ff">int</span><span style="color: #000000"> a10)
{
    cout</span>&lt;&lt;...&lt;&lt;<span style="color: #800000">"</span><span style="color: #800000">10th</span><span style="color: #800000">"</span>&lt;&lt;a10&lt;&lt;<span style="color: #000000">endl;
}

....</span></pre></div></p>
<p>我们需要两重循环，第一重是生成Print，第二重是里面的cout。cout里面还要根据数字来产生st啊、nd啊、rd啊、这些前缀。于是我们可以开始写了。Fpmacro的写法是这样的，因为没有lambda表达式，所以循环体都是一些独立的函数。于是我们来定义一些函数来生成变量名、参数定义和cout的片段：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>$$define $VAR_NAME($index) a$index <span style="color: #008000">/*</span><span style="color: #008000">$VAR_NAME(3) -&gt; a3</span><span style="color: #008000">*/</span><span style="color: #000000">
$$define $VAR_DEF($index) </span><span style="color: #0000ff">int</span> $VAR_NAME($index) <span style="color: #008000">/*</span><span style="color: #008000">$VAR_DEF(3) -&gt; int a3</span><span style="color: #008000">*/</span><span style="color: #000000">
$$define $ORDER($index) $$begin </span><span style="color: #008000">/*</span><span style="color: #008000">$ORDER(3) -&gt; 3rd</span><span style="color: #008000">*/</span><span style="color: #000000">
    $$define $LAST_DIGIT $mod($index,</span><span style="color: #800080">10</span><span style="color: #000000">)
    $index$</span><span style="color: #0000ff">if</span>($eq($LAST_DIGIT,<span style="color: #800080">1</span>),st,$<span style="color: #0000ff">if</span>($eq($LAST_DIGIT,<span style="color: #800080">2</span>),nd,$<span style="color: #0000ff">if</span>($eq($LAST_DIGIT,<span style="color: #800080">3</span><span style="color: #000000">),rd,th)))
$$end
$$define $OUTPUT($index) $(</span><span style="color: #800000">"</span><span style="color: #800000">)$ORDER($index)$(</span><span style="color: #800000">"</span>)&lt;&lt;$VAR_NAME($index) <span style="color: #008000">/*</span><span style="color: #008000">$OUTPUT(3) -&gt; "3rd"&lt;&lt;a3</span><span style="color: #008000">*/</span></pre></div></p>
<p>接下来就是实现Print函数的宏：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">$$define $PRINT_FUNCTION($count) $$begin
</span><span style="color: #0000ff">void</span> Print($loopsep($count,<span style="color: #800080">1</span><span style="color: #000000">,$VAR_DEF,$(,)))
{
    cout</span>&lt;&lt;$loopsep($count,<span style="color: #800080">1</span>,$OUTPUT,&lt;&lt;)&lt;&lt;<span style="color: #000000">endl;
}<br>$( )
$$end</span></pre></div></p>
<p>最后就是生成整片代码了：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>$define $COUNT <span style="color: #800080">10</span> <span style="color: #008000">/*</span><span style="color: #008000">就算是20，那上面的代码的11也会生成11st，特别方便</span><span style="color: #008000">*/</span><span style="color: #000000">
$loop($COUNT,</span><span style="color: #800080">1</span>,$PRINT_FUNCTION)</pre></div></p>
<p>注意：<strong>注释其实是不能加的</strong>，因为如果你加了注释，这些注释最后也会被生成成C++，所以上面那个$COUNT就会变成10+空格+注释，他就不能放进$loop函数里面了。Fpmacro并没有添加&#8220;Fpmacro注释&#8221;的代码，因为我觉得没必要</p>
<p>为什么我们不需要C++的宏的#和##操作呢？因为在这里，A(x)##B(x)被我们处理成了$A(x)$B(x)，而L#A(x)被我们处理成了L$(&#8220;)$A(x)$(&#8220;)。虽然就这么看起来好像Fpmacro长了一点点，但是实际上用起来是特别方便的。$这个前缀恰好帮我们解决了A(x)##B(x)的##的问题，写的时候只需要直接写下去就可以了，譬如说$ORDER里面的$index$if&#8230;。</p>
<p>那么这样做到底行不行呢？看在Fpmacro可以用<a href="https://gac.codeplex.com/SourceControl/latest#Common/Source/Reflection/GuiTypeDescriptorBuilder_Gen.h.fpm" target="_blank">这个宏</a>来生成<a href="https://gac.codeplex.com/SourceControl/latest#Common/Source/Reflection/GuiTypeDescriptorBuilder_Gen.h" target="_blank">这么复杂的代码</a>的份上，我认为&#8220;简单紧凑&#8221;和&#8220;C++代码几乎不需要转义&#8221;和&#8220;没有坑&#8221;这三个目标算是达到了。DSL之所以为DSL就是因为我们是用它来完成特殊的目的的，不是general purpose的，因此不需要太复杂。因此设计DSL要有一个习惯，就是<strong>时刻审视一下，我们是不是设计了多余的东西</strong>。现在我回过头来看，Fpmacro支持数组就是多余的，而且实践证明，根本没用上。</p>
<p>大家可能会说，代码遍地都是$看起来也很乱啊？没关系，最近我刚刚搞定了一个基于语法文件驱动的自动着色和智能提示的算法，只需要简单地写一个Fpmacro的编辑器就可以了，啊哈哈哈哈。</p>
<p><strong><font size="5">三、尾声</font></strong></p>
<p>本来我是想举很多个例子的，还有语法文件啊，GUI配置啊，甚至是SQL什么的。不过其实<strong>设计一个DSL首先要求你对领域本身有着足够的理解，在长期的开发中已经在这个领域里面感受到了极大的痛苦</strong>，这样你才能真的设计出一个专门根除痛点的DSL来。</p>
<p>像正则表达式，我们都知道手写字符串处理程序经常要人肉做错误处理和回溯等工作，正则表达式帮我们自动完成了这个功能。</p>
<p>C++的宏生成复杂代码的时候，动不动就会因为dynamic scoping和call by name掉坑里而且还没有靠谱的工具来告诉我们究竟要怎么做，Fpmacro就解决了这个问题。</p>




<p>开发DSL需要语法分析器，而且带Visitor模式的语法树可扩展性好但是定义起来特别的麻烦，所以我定义了一个语法文件的格式，写了一个ParserGen.exe（代码在<a href="https://gac.codeplex.com/SourceControl/latest#Common/Tools/ParserGen/ParserGen/Main.cpp" target="_blank">这里</a>）来替我生成代码。Fpmacro的语法分析器就是这么生成出来的。</p>
<p>GUI的构造代码写起来太他妈烦了，所以还得有一个配置的文件。</p>
<p>查询数据特别麻烦，而且就算是只有十几个T的小型数据库也很难自己设计一个靠谱的容器，所以我们需要SQLServer。这个DSL做起来不简单，但是用起来简单。这也是一个成功的DSL。</p>
<p>类似的，Visual Studio为了生成代码还提供了T4这种模板文件。这个东西其实超好用的——除了用来生成C++代码，所以我还得自己撸一个Fpmacro&#8230;&#8230;</p>
<p>用MVC的方法来写HTML，需要从数据结构里面拼HTML。用过php的人都知道这种东西很容易就写成了屎，所以Visual Studio里面又在ASP.NET MVC里面提供了razor模板。而且他的IDE支持特别号，razor模板里面可以混着HTML+CSS+Javascript+C#的代码，智能提示从不出错！</p>
<p>还有各种数不清的配置文件。我们都知道，一个强大的配置文件最后都会进化成为lisp，哦不，DSL的。</p>
<p>这些都是DSL，用来解决我们的痛点的东西，而且他本身又不足以复杂到用来完成程序所有的功能（除了连http service都能写的SQLServer我们就不说了=_=）。设计DSL的时候，首先要找到痛点，其次要理清楚DSL的结构，然后再给他设计一个要么紧凑要么可读性特别高的语法，然后再给一个简单的API，用起来别提多爽了。</p> <img src ="http://www.cppblog.com/vczh/aggbug/203249.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-09-16 09:26 <a href="http://www.cppblog.com/vczh/archive/2013/09/16/203249.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（九）&amp;mdash;&amp;mdash;类型</title><link>http://www.cppblog.com/vczh/archive/2013/08/17/202605.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 17 Aug 2013 08:26:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/08/17/202605.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/202605.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/08/17/202605.html#Feedback</comments><slash:comments>16</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/202605.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/202605.html</trackback:ping><description><![CDATA[<p>类型是了解编程语言的重要一环。就算是你喜欢动态类型语言，为了想实现一个靠谱的东西，那也必须了解类型。举个简单的例子，我们都知道+和-是对称的&#8212;&#8212;当然这只是我们的愿望了，在javascript里面，"1"+2和"1"-2就不是一回事。这就是由于不了解类型的操作而犯下的一些滑稽的错误。什么，你觉得因为"1"的类型是string所以"1"+2就应该是"12"？啐！"1"的类型是(string | number)，这才是正确的做法。</p>
<p>了解编程语言的基本原理并不意味着你一定要成为一名编译器的前端，正如同学习Haskell可以让你的C++写得更好一样，如果你知道怎么设计一门语言，那遇到语言里面的坑，你十有八九可以当场看到，不会跳进去。当然了，了解编程语言的前提是你是一个优秀的程序员，至少要写程序，对吧。于是我这里推荐几门语言是在此之前要熟悉的。编程语言有好多种，每一种都有其代表作，为了开开眼界，知道编程语言可以设计成什么样子，你至少应该学会：</p>
<ol><li>C++</li><li>C#</li><li>F#</li><li>Haskell</li><li>Ruby</li><li>Prolog</li></ol>
<p>其实这一点也不多，因为只是学会而已，知道那些概念就好了，并不需要你成为一个精通xx语言的人。那为了了解类型你应该学会什么呢？没错&#8212;&#8212;就是C++了！很多人可能不明白，为什么长得这么难看的C++竟然有这么重要的作用呢？其实如果详细了解了程序设计语言的基本原理之后，你会发现，C++在除了兼容那个可怜的C语言之外的那些东西，是设计的非常科学的。当然现在讲这些还太早，今天的重点是类型。</p>
<p>如果你们去看相关的书籍或者论文的话，你们会发现类型这个领域里面有相当多的莫名其妙的类型系统，或者说名词。对于第一次了解这个方面的人来说，熟练掌握Haskell和C++是很有用的，因为Haskell可以让你真正明白类型在程序里面的重要做哟的同时。几乎所有流行的东西都可以在C++里面找到，譬如说：</p>
<ol><li>面向对象&#8594;class</li><li>polymorphic type&#8594;template</li><li>intersection type&#8594;union / 函数重载</li><li>dependent type&#8594;带数字的模板类型</li><li>System F&#8594;在泛型的lambda表达式里面使用decltype（看下面的例子）</li><li>sub typing的规则&#8594;泛型lambda表达式到函数指针的隐式类型转换</li></ol>
<p>等等等等，因有尽有，取之不尽，用之不竭。你先别批判C++，觉得他东西多所以糟糕。事实是，只要编译器不用你写，那一门语言是不可能通过拿掉feature来使它对你来说变得更牛逼的。不知道为什么有那么多人不了解这件事情，需要重新去念一念《形式逻辑》，早日争取做一个靠谱的人。</p>
<p>泛型lambda表达式是C++14（没错，是14，已经基本敲定了）的内容，应该会有很多人不知道，我在这里简单地讲一下。譬如说要写一个lambda表达式来计算一个容器里所有东西的和，但是你却不知道容器和容器里面装的东西是什么。当然这种情况也不多，但是有可能你需要把这个lambda表达使用在很多地方，对吧，特别是你#include &lt;algorithm&gt;用了里面超好用的函数之后，这种情况就变得常见了。于是这个东西可以这么写： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>auto lambda = [](<span style="color: #0000ff">const</span> auto&amp;<span style="color: #000000"> xs)
{
    decltype(</span>*xs.begin()) sum = <span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">for</span><span style="color: #000000">(auto x : xs)
    {
        sum </span>+=<span style="color: #000000"> x;
    }
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> sum;
};</span></pre></div>
<p>于是你就可以这么用了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>vector&lt;<span style="color: #0000ff">int</span>&gt; a =<span style="color: #000000"> { ... };
list</span>&lt;<span style="color: #0000ff">float</span>&gt; b =<span style="color: #000000"> { ... };
deque</span>&lt;<span style="color: #0000ff">double</span>&gt; c =<span style="color: #000000"> { ... };

</span><span style="color: #0000ff">int</span> sumA =<span style="color: #000000"> lambda(a);
</span><span style="color: #0000ff">float</span> sumB =<span style="color: #000000"> lambda(b);
</span><span style="color: #0000ff">double</span> sumC = lambda(c);</pre></div>
<p>然后还可以应用sub typing的规则把这个lambda表达式转成一个函数指针。C++里面所有中括号不写东西的lambda表达式都可以被转成一个函数指针的，因为他本来就可以当成一个普通函数，只是你为了让业务逻辑更紧凑，选择把这个东西写在了你的代码里面而已： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>doube(*summer)(<span style="color: #0000ff">const</span> vector&lt;<span style="color: #0000ff">double</span>&gt;&amp;<span style="color: #000000">);
summer </span>= lambda;</pre></div>
<p>只要搞明白了C++之后，那些花里胡俏的类型系统的论文的概念并不难理解。他们深入研究了各种类型系统的主要原因是要做系统验证，证明这个证明那个。其实编译器的类型检查部分也可以当成是一个系统验证的程序，他要检查你的程序是不是有问题，于是首先检查系统。不过可惜的是，除了Haskell以外的其他程序语言，就算你过了类型系统检查，也不见得你的程序就是对的。当然了，对于像javascript这种动态类型就罢了还那么多坑（ruby在这里就做得很好）的语言，得通过大量的自动化测试来保证。没有类型的帮助，要写出同等质量的程序，需要花的时间要更多。什么？你不关心质量？你不要当程序员了！是因为老板催得太紧？我们Microsoft最近有招聘了，快来吧，可以慢慢写程序！</p>
<p>不过正因为编译器会检查类型，所以我们其实可以把一个程序用类型武装起来，使得错误的写法会变成错误的语法被检查出来了。这种事情在C++里面做尤为方便，因为它支持dependent type&#8212;&#8212;好吧，就是可以在模板类型里面放一些不是类型的东西。我来举一个正常人都熟练掌握的例子&#8212;&#8212;单位。</p>
<p><strong><font size="5">一、类型检查（type rich programming）</font></strong></p>
<p>我们都知道物理的三大基本单位是米、秒和千克，其它东西都可以从这些单位拼出来（大概是吧，我忘记了）。譬如说我们通过F=ma可以知道力的单位，通过W=FS可以知道功的单位，等等。然后我们发现，单位之间的关系都是乘法的关系，每个单位还带有自己的幂。只要弄清楚了这一点，那事情就很好做了。现在让我们来用C++定义单位： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;
<span style="color: #0000ff">struct</span><span style="color: #000000"> unit
{
    </span><span style="color: #0000ff">double</span><span style="color: #000000"> value;

    unit():value(</span><span style="color: #800080">0</span><span style="color: #000000">){}
    unit(</span><span style="color: #0000ff">double</span><span style="color: #000000"> _value):value(_value){}
};</span></pre></div>
<p>好了，现在我们要通过类型系统来实现几个操作的约束。对于乘除法我们要自动计算出单位的同时，加减法必须在相同的单位上才能做。其实这样做还不够完备，因为对于任何的单位x来讲，他们的差单位&#916;x还有一些额外的规则，就像C#的DateTime和TimeSpan一样。不过这里先不管了，我们来做出加减乘除几个操作： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>+(unit&lt;m, s, kg&gt; a, unit&lt;m, s, kg&gt;<span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">return</span> a.value +<span style="color: #000000"> b.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>-(unit&lt;m, s, kg&gt; a, unit&lt;m, s, kg&gt;<span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">return</span> a.value -<span style="color: #000000"> b.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>+(unit&lt;m, s, kg&gt;<span style="color: #000000"> a)
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> a.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>-(unit&lt;m, s, kg&gt;<span style="color: #000000"> a)
{
    </span><span style="color: #0000ff">return</span> -<span style="color: #000000">a.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m1, <span style="color: #0000ff">int</span> s1, <span style="color: #0000ff">int</span> kg1, <span style="color: #0000ff">int</span> m2, <span style="color: #0000ff">int</span> s2, <span style="color: #0000ff">int</span> kg2&gt;<span style="color: #000000">
unit</span>&lt;m1+m2, s1+s2, kg1+kg2&gt;<span style="color: #0000ff">operator*</span>(unit&lt;m1, s1, kg1&gt; a, unit&lt;m2, s2, kg2&gt;<span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">return</span> a.value *<span style="color: #000000"> b.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m1, <span style="color: #0000ff">int</span> s1, <span style="color: #0000ff">int</span> kg1, <span style="color: #0000ff">int</span> m2, <span style="color: #0000ff">int</span> s2, <span style="color: #0000ff">int</span> kg2&gt;<span style="color: #000000">
unit</span>&lt;m1-m2, s1-s2, kg1-kg2&gt;<span style="color: #0000ff">operator/</span>(unit&lt;m1, s1, kg1&gt; a, unit&lt;m2, s2, kg2&gt;<span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">return</span> a.value /<span style="color: #000000"> b.value;
}</span></pre></div>
<p>但是这个其实还不够，我们还需要带单位的值乘以或除以一个系数的代码。为什么不能加减呢？因为不同单位的东西本来就不能加减。系数其实是可以描写成unit&lt;0, 0, 0&gt;的，但是为了让代码更紧凑，于是多定义了下面的四个函数： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>*(<span style="color: #0000ff">double</span> v, unit&lt;m, s, kg&gt;<span style="color: #000000"> a)
{
    </span><span style="color: #0000ff">return</span> v *<span style="color: #000000"> a.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>*(unit&lt;m, s, kg&gt; a, <span style="color: #0000ff">double</span><span style="color: #000000"> v)
{
    </span><span style="color: #0000ff">return</span> a.value *<span style="color: #000000"> v;
}

template</span>&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>/(<span style="color: #0000ff">double</span> v, unit&lt;m, s, kg&gt;<span style="color: #000000"> a)
{
    </span><span style="color: #0000ff">return</span> v /<span style="color: #000000"> a.value;
}

template</span>&lt;<span style="color: #0000ff">int</span> m, <span style="color: #0000ff">int</span> s, <span style="color: #0000ff">int</span> kg&gt;<span style="color: #000000">
unit</span>&lt;m, s, kg&gt; <span style="color: #0000ff">operator</span>/(unit&lt;m, s, kg&gt; a, <span style="color: #0000ff">double</span><span style="color: #000000"> v)
{
    </span><span style="color: #0000ff">return</span> a.value /<span style="color: #000000"> v;
}</span></pre></div>
<p>我们已经用dependent type之间的变化来描述了带单位的量的加减乘除的规则。这看起来好像很复杂，但是一旦我们加入了下面的新的函数，一切将变得简单明了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>constexpr unit&lt;<span style="color: #800080">1</span>, <span style="color: #800080">0</span>, <span style="color: #800080">0</span>&gt; <span style="color: #0000ff">operator</span><span style="color: #800000">""</span>_meter(<span style="color: #0000ff">double</span><span style="color: #000000"> value)
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> value;
}

constexpr unit</span>&lt;<span style="color: #800080">0</span>, <span style="color: #800080">1</span>, <span style="color: #800080">0</span>&gt; <span style="color: #0000ff">operator</span><span style="color: #800000">""</span>_second(<span style="color: #0000ff">double</span><span style="color: #000000"> value)
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> value;
}

constexpr unit</span>&lt;<span style="color: #800080">0</span>, <span style="color: #800080">0</span>, <span style="color: #800080">1</span>&gt; <span style="color: #0000ff">operator</span><span style="color: #800000">""</span>_kilogram(<span style="color: #0000ff">double</span><span style="color: #000000"> value)
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> value;
}

constexpr unit</span>&lt;<span style="color: #800080">1</span>, -<span style="color: #800080">2</span>,<span style="color: #800080">1</span>&gt; <span style="color: #0000ff">operator</span><span style="color: #800000">""</span>_N(<span style="color: #0000ff">double</span> value) <span style="color: #008000">//</span><span style="color: #008000"> 牛不知道怎么写-_-</span>
<span style="color: #000000">{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> value;
}

constexpr unit</span>&lt;<span style="color: #800080">2</span>, -<span style="color: #800080">2</span>,<span style="color: #800080">1</span>&gt; <span style="color: #0000ff">operator</span><span style="color: #800000">""</span>_J(<span style="color: #0000ff">double</span> value) <span style="color: #008000">//</span><span style="color: #008000"> 焦耳也不知道怎么写-_-</span>
<span style="color: #000000">{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> value;
}</span></pre></div>
<p>然后我们就可以用来写一些神奇的代码了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>auto m = 16_kilogram; <span style="color: #008000">//</span><span style="color: #008000"> unit&lt;0, 0, 1&gt;(16)</span>
auto s = 3_meter; <span style="color: #008000">//</span><span style="color: #008000"> unit&lt;1, 0, 0&gt;(3)</span>
auto t = 2_second; <span style="color: #008000">//</span><span style="color: #008000"> unit&lt;0, 1, 0&gt;(2)</span>
auto a = s / (t*t); <span style="color: #008000">//</span><span style="color: #008000"> unit&lt;1, -2, 0&gt;(3/4)</span>
auto F = m * a; <span style="color: #008000">//</span><span style="color: #008000"> unit&lt;1, -2, 1&gt;(12)</span></pre></div>
<p>下面的代码虽然也神奇，但因为违反了物理定律，所以C++编译器决定不让他编译通过： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>auto W = F * s; <span style="color: #008000">//</span><span style="color: #008000"> unit&lt;2, -2, 1&gt;(36)</span>
auto x = F + W; <span style="color: #008000">//</span><span style="color: #008000"> bang!</span></pre></div>
<p>这样你还怕你在物理引擎里面东西倒腾来倒腾去然后公式手抖写错了吗？<strong><font size="4">类似的错误是不可能发生的</font></strong>！除非系数被你弄错了&#8230;&#8230;如果没有unit，要用原始的方法写出来： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">double</span> m = <span style="color: #800080">16</span><span style="color: #000000">;
</span><span style="color: #0000ff">double</span> s = <span style="color: #800080">3</span><span style="color: #000000">;
</span><span style="color: #0000ff">double</span> t = <span style="color: #800080">2</span><span style="color: #000000">;
</span><span style="color: #0000ff">double</span> a = s / (t*<span style="color: #000000">t);
</span><span style="color: #0000ff">double</span> F = m *<span style="color: #000000"> a;
</span><span style="color: #0000ff">double</span> W = F *<span style="color: #000000"> s;
</span><span style="color: #0000ff">double</span> x = F + W; <span style="color: #008000">//</span><span style="color: #008000">????</span></pre></div>
<p>时间过得久了以后，根本不知道是什么意思了。所以为了解决这个问题，我们得用应用匈牙利命名法（这个不是那个臭名昭著的你们熟悉的傻逼（系统）匈牙利命名法）。我举个例子： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">string</span> dogName = <span style="color: #800000">"</span><span style="color: #800000">kula</span><span style="color: #800000">"</span><span style="color: #000000">;
Person person;
person.name </span>= dogName;</pre></div>
<p>这个代码大家一看就知道不对对吧，这就是应用匈牙利命名法了。我们通过给名字一个单位&#8212;&#8212;狗的&#8212;&#8212;来让person.name = dogName;这句话显得很滑稽，从而避免低级错误的发生。上面的unit就更进一步了，把这个东西带进了类型系统里面，就算写出来不滑稽，编译器都会告诉你，错误的东西就是错误的。</p>
<p>然后大家可能会问，用unit这么写程序的性能会不会大打折扣呀？如今已经是2013年了，靠谱的C++编译器编译出来的代码，跟你直接用几个double倒腾来倒腾去的代码其实是一样的。C++比起其他语言的抽象的好处就是，就算你要用来做高性能的程序，也不怕因为抽象而丧失性能。当然如果你使用了面向对象的技术，那就另当别论了。</p>
<blockquote>
<p><em>注，上面这段话我写完之后贴到了粉丝群里面，然后九姑娘跟我讲了很多量纲分析的故事，然后升级到航空领域的check list，最后讲到了医院把这一技术引进了之后有效地阻止了手术弄错人等严重事故。那些特别靠谱的程序还得用C++来写，譬如说洛克希德马丁的战斗机，NASA的卫星什么的。</em></p></blockquote>
<p>人的精力是有限的，需要一些错误规避来防止引进低级的错误或者负担，保留精力解决最核心的问题。很多软件都是这样的。譬如说超容易配置的MSBuild、用起来巨爽无比的Visual Studio，出了问题反正用正版安装程序点一下repair就可以恢复的windows，给我们带来的好处就是&#8212;&#8212;保留精力解决最核心的问题。编程语言也是如此，类型系统也是如此，人类发明出的所有东西，都是为了让你可以把更多的精力放在更加核心的问题上，更少的精力放在周边的问题上。</p>
<p>但是类型到处都出现其实也会让我们程序写起来很烦的，所以现代的语言都有第二个功能，就是类型推导了。</p>
<p><strong><font size="5">二、类型推导</font></strong></p>
<p>这里讲的类型推导可不是Go语言那个半吊子的:=赋值操作符。真正的类型推导，就要跟C++的泛型lambda表达式、C#的linq语法糖，或者Haskell的函数一样，要可以自己计算出模板的类型参数的位置或者内容，才能全方位的实现什么类型都不写，都还能使用强类型和type rich programming带来的好处。C++的lambda表达式上面已经看到了，所以还是从Haskell一个老掉牙的demo开始讲起吧。</p>
<p>今天，我们用Haskell来写一个merge sort： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>merge [] [] =<span style="color: #000000"> []
merge [] xs </span>=<span style="color: #000000"> xs
merge xs [] </span>=<span style="color: #000000"> xs
merge (x:xs) (y:ys) </span>= <span style="color: #0000ff">if</span> x&lt;y then x:(merge xs (y:ys)) <span style="color: #0000ff">else</span><span style="color: #000000"> y:(merge (x:xs) ys)

mergeSort [] </span>=<span style="color: #000000"> []
mergeSort xs </span>=<span style="color: #000000"> merge (mergeSort a) (mergeSort b)
    </span><span style="color: #0000ff">where</span><span style="color: #000000">
        len </span>=<span style="color: #000000"> length xs
        a </span>= take $ len `div` <span style="color: #800080">2</span><span style="color: #000000"> $ xs
        b </span>= drop $ len - len `div` <span style="color: #800080">2</span> $ xs</pre></div>
<p>我们可以很清楚的看出来，merge的类型是[a] &#8211;&gt; [a] &#8211;&gt; [a]，mergeSort的类型是[a] &#8211;&gt; [a]。到底编译器是怎么计算出类型的呢？</p>
<ol><li>首先，[]告诉我们，这是一个空列表，但是类型是什么不知道，所以他是forall a &#8211;&gt; [a]。所以merge [] [] = []告诉我们，merge的类型<strong>至少</strong>是[a] &#8211;&gt; [b] &#8211;&gt; [c]。</li><li>其次，merge []&nbsp; xs = xs告诉我们，merge的类型至少是[d] &#8211;&gt; e &#8211;&gt; e。这个类型跟[a]-&gt;[b]-&gt;[c]求一个交集就会得到merge的<strong>更准确</strong>的类型：[a] &#8211;&gt; [b] &#8211;&gt; [b]。</li><li>然后，merge xs [] = []告诉我们，merge的类型至少是f &#8211;&gt; [g] &#8211;&gt; f。这个类型跟[a] &#8211;&gt; [b] &#8211;&gt; [b]求一个交集就会得到merge的更准确的类型：[a] &#8211;&gt; [a] &#8211;&gt; [a]。</li><li>最后看到那个长长的式子，根据一番推导之后，会发现[a]-&gt;[a]-&gt;[a]就是我们要的最终类型了。</li><li>只要把相同的技术放在mergeSort上面，就可以得到mergeSort的类型是[a]-&gt;[a]了。</li></ol>
<p>当然对于Haskell这种Hindley-Milner类型系统来说，只要我们在代码里面计算出所有类型的方程，然后一个一个去解，最后就可以收敛到一个最准确的类型上面了。倘若我们在迭代的时候发现收敛之后无解了，那这个程序就是错的。这种简单粗暴的方法很容易构造出一些<strong>只要人够蛋定</strong>就很容易使用的语言，譬如说Haskell。</p>
<p>Haskell看完就可以来看C#了。C#的linq真是个好东西啊，只要不把它看成SQL，那很多事情都可以表达的。譬如说是个人都知道的<a href="http://msdn.microsoft.com/en-us/library/bb397919.aspx" target="_blank">linq to object</a>啦，后面还有<a href="http://msdn.microsoft.com/en-us/library/bb387098.aspx" target="_blank">linq to xml</a>，<a href="http://msdn.microsoft.com/en-us/library/bb386976.aspx" target="_blank">linq to sql</a>，<a href="http://wenku.it168.com/d_000260231.shtml" target="_blank">reactive programming</a>，甚至是<a href="http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx" target="_blank">parser combinator</a>等等。一个典型的linq的程序是长这个样子的： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> w = 
    <span style="color: #0000ff">from</span> x <span style="color: #0000ff">in</span><span style="color: #000000"> xs
    </span><span style="color: #0000ff">from</span> y <span style="color: #0000ff">in</span><span style="color: #000000"> ys
    </span><span style="color: #0000ff">from</span> z <span style="color: #0000ff">in</span><span style="color: #000000"> zs
    </span><span style="color: #0000ff">select</span> f(x, y, z);</pre></div>
<p>&nbsp;</p>
<p>光看这个程序可能看不出什么来，因为xs、ys、zs和f这几个单词都是没有意义的。但是linq的魅力正在这里。如果from和select就已经强行规定了xs、ys、zs和f的意思的话。那可扩展性就全没有了。因此当我们看到一个这样的程序的时候，其实可以是下面这几种意思： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">W f(X x, Y y, Z z);

</span><span style="color: #0000ff">var</span> <span style="color: #008000">/*</span><span style="color: #008000">IEnumerable&lt;W&gt;</span><span style="color: #008000">*/</span>w = 
    <span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">X</span><span style="color: #008000">*/</span>x <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IEnumerable&lt;X&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">xs
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Y</span><span style="color: #008000">*/</span>y <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IEnumerable&lt;Y&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">ys
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Z</span><span style="color: #008000">*/</span>z <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IEnumerable&lt;Z&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">zs
    </span><span style="color: #0000ff">select</span><span style="color: #000000"> f(x, y, z);

</span><span style="color: #0000ff">var</span> <span style="color: #008000">/*</span><span style="color: #008000">IObservable&lt;W&gt;</span><span style="color: #008000">*/</span>w = 
    <span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">X</span><span style="color: #008000">*/</span>x <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IObservable&lt;X&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">xs
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Y</span><span style="color: #008000">*/</span>y <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IObservable&lt;Y&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">ys
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Z</span><span style="color: #008000">*/</span>z <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IObservable&lt;Z&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">zs
    </span><span style="color: #0000ff">select</span><span style="color: #000000"> f(x, y, z);

</span><span style="color: #0000ff">var</span> <span style="color: #008000">/*</span><span style="color: #008000">IParser&lt;W&gt;</span><span style="color: #008000">*/</span>w = 
    <span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">X</span><span style="color: #008000">*/</span>x <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IParser&lt;X&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">xs
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Y</span><span style="color: #008000">*/</span>y <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IParser&lt;Y&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">ys
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Z</span><span style="color: #008000">*/</span>z <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">IParser&lt;Z&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">zs
    </span><span style="color: #0000ff">select</span><span style="color: #000000"> f(x, y, z);</span></pre><pre><span style="color: #0000ff">var</span> <span style="color: #008000">/*</span><span style="color: #008000">IQueryable&lt;W&gt;</span><span style="color: #008000">*/</span>w =<br /><span style="color: #0000ff">    from</span> <span style="color: #008000">/*</span><span style="color: #008000">X</span><span style="color: #008000">*/</span>x <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000"><span style="color: #008000">IQueryable</span>&lt;X&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">xs<br />    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Y</span><span style="color: #008000">*/</span>y <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000"><span style="color: #008000">IQueryable</span>&lt;Y&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">ys<br />    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Z</span><span style="color: #008000">*/</span>z <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000"><span style="color: #008000">IQueryable</span>&lt;Z&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">zs<br />    </span><span style="color: #0000ff">select</span><span style="color: #000000"> f(x, y, z);</span></pre><pre><span style="color: #000000"></span><span style="color: #0000ff">var</span> <span style="color: #008000">/*</span><span style="color: #008000">?&lt;W&gt;</span><span style="color: #008000">*/</span>w = 
<span style="color: #0000ff">    from</span> <span style="color: #008000">/*</span><span style="color: #008000">X</span><span style="color: #008000">*/</span>x <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">?&lt;X&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">xs
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Y</span><span style="color: #008000">*/</span>y <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">?&lt;Y&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">ys
    </span><span style="color: #0000ff">from</span> <span style="color: #008000">/*</span><span style="color: #008000">Z</span><span style="color: #008000">*/</span>z <span style="color: #0000ff">in</span> <span style="color: #008000">/*</span><span style="color: #008000">?&lt;Z&gt;</span><span style="color: #008000">*/</span><span style="color: #000000">zs
    </span><span style="color: #0000ff">select</span> f(x, y, z);</pre></div>
<p>&nbsp;</p>
<p>相信大家已经看到了里面的pattern了。只要你有一个?&lt;T&gt;类型，而它又支持<a href="http://msdn.microsoft.com/en-us/library/vstudio/bb546158.aspx" target="_blank">linq provider</a>的话，你就可以把代码写成这样了。</p>
<p>不过我们知道，<strong>把程序写成这样</strong>并不是我们编程的目的，我们的目的是要让程序写得让具有同样知识背景的人可以很快就看懂。为什么要看懂？因为总有一天你会不维护这个程序的，这样就可以让另一个合格的人来继续维护了。一个软件是要做几十年的，那些只有一两年甚至只有半年生命周期的，只能叫垃圾了。</p>
<p>那现在让我们看一组有意义的linq代码。首先是linq to object的，求一个数组里面出现最多的数字是哪个： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> theNumber =<span style="color: #000000"> (
    </span><span style="color: #0000ff">from</span> n <span style="color: #0000ff">in</span><span style="color: #000000"> numbers
    group by n into g
    </span><span style="color: #0000ff">select</span><span style="color: #000000"> g.ToArray() into gs
    order by gs.Length descending
    </span><span style="color: #0000ff">select</span> gs[<span style="color: #800080">0</span><span style="color: #000000">]
    ).First()</span></pre></div>
<p>&nbsp;</p>
<p>其次是一个parser，这个parser用来得到一个函数调用表达式： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>IParser&lt;FunctionCallExpression&gt;<span style="color: #000000"> Call()
{
    </span><span style="color: #0000ff">return</span>
        <span style="color: #0000ff">from</span> name <span style="color: #0000ff">in</span><span style="color: #000000"> PrimitiveExpression()
        </span><span style="color: #0000ff">from</span> _1 <span style="color: #0000ff">in</span> Text(<span style="color: #800000">"</span><span style="color: #800000">(</span><span style="color: #800000">"</span><span style="color: #000000">)
        </span><span style="color: #0000ff">from</span> arguments <span style="color: #0000ff">in</span><span style="color: #000000">
            many(
                Expression().
                Text(</span><span style="color: #800000">"</span><span style="color: #800000">,</span><span style="color: #800000">"</span><span style="color: #000000">)
            )
        </span><span style="color: #0000ff">from</span> _2 <span style="color: #0000ff">in</span> Text(<span style="color: #800000">"</span><span style="color: #800000">)</span><span style="color: #800000">"</span><span style="color: #000000">)
        </span><span style="color: #0000ff">select</span> <span style="color: #0000ff">new</span><span style="color: #000000"> FunctionCallExpression
        {
            Name </span>=<span style="color: #000000"> name,
            Arguments </span>=<span style="color: #000000"> arguments.ToArray(),
        };
}</span></pre></div>
<p>&nbsp;</p>
<p>我们可以看到，一旦linq表达式里面的元素都有了自己的名字，就不会跟上面的xyz的例子一样莫名其妙了。那这两个例子到底为什么要用linq呢？</p>
<p>第一个例子很简单，因为linq to object就是设计来解决这种问题的。</p>
<p>第二个例子就比较复杂一点了，为什么好好地parser要写成这样呢？我们知道，parser时可能会parse失败的。一个大的parser，里面的一些小部件失败了，那么大parser就要回滚，token stream的当前光标也要回滚，等等需要类似的一系列的操作。如果我们始终都让这些逻辑都贯穿在整个parser里面，那代码根本不能看。于是我们可以写一个linq provider，让SelectMany函数来处理所有的回滚操作，然后把parser写成上面这个样子。上面这个parser的所有的in左边是临时变量，所有的in右边刚好组成了一个EBNF文法： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>PrimitiveExpression <span style="color: #800000">"</span><span style="color: #800000">(</span><span style="color: #800000">"</span> [Expression {<span style="color: #800000">"</span><span style="color: #800000">,</span><span style="color: #800000">"</span> Expression}] <span style="color: #800000">"</span><span style="color: #800000">)</span><span style="color: #800000">"</span></pre></div>
<p>&nbsp;</p>
<p>最后的select语句告诉我们在所有小parser都parse成功之后，如何用parse得到的临时变量来返回一颗语法树。整个parsing的代码就会非常的容易看懂。当然，前提是你必须要懂的EBNF。不过一个不懂EBNF的人，又如何能写语法分析器呢。</p>
<p>那这跟类型推导有什么关系呢？我们会发现上面的所有linq的例子里面，除了函数签名以外，根本没有出现任何类型的声明。而且更重要的是，这些类型尽管没有写出来，<strong>但是每一个中间变量的类型都是自明的</strong>。当然这有一部分归功于好的命名方法&#8212;&#8212;也就是应用匈牙利命名法了。剩下的部分是跟业务逻辑相关的。譬如说，一个FunctionCallExpression所调用的函数当然也是一个Expression了。如果这是唯一的选择，那为什么要写出来呢？</p>
<p>我们可以看到，正是因为有了类型推导，我们可以在写出清晰的代码的同时，还不需要花费那么多废话来指定各种类型。程序员都是怕麻烦的，无论复杂的方法有多好，他总是会选择简单的（废话，用复杂的那个不仅要加班修bug，还没有涨工资。用简单的那个，先让他过了，bug留给下一任程序员去头疼就好了&#8212;&#8212;某web程序员如是说）。类型推导让type rich programming的程序写起来简单了许多。所以我们一旦有了类型推导，就可以放心大胆的使用type rich programming了。</p>
<p><strong><font size="5">三、大道理</font></strong></p>
<p>有了type rich programming，就可以让编译器帮我们检查一些模式化的手都会犯的错误。让我们重温一下这篇文章前面的一段话：</p>
<blockquote>
<p>人的精力是有限的，需要一些错误规避来防止引进低级的错误或者负担，保留精力解决最核心的问题。很多软件都是这样的。譬如说超容易配置的MSBuild、用起来巨爽无比的Visual Studio，出了问题反正用正版安装程序点一下repair就可以恢复的windows，给我们带来的好处就是&#8212;&#8212;保留精力解决最核心的问题。编程语言也是如此，类型系统也是如此，人类发明出的所有东西，都是为了让你可以把更多的精力放在更加核心的问题上，更少的精力放在周边的问题上。</p></blockquote>
<p>这让我想起了一个在微博上看到的故事：NASA的员工在推一辆装了卫星的小车的时候，因为忘记看check list，没有固定号卫星，结果卫星一推倒在了地上摔坏了，一下子没了两个亿的美元。</p>
<p>写程序也一样。一个代表力的变量，只能跟另一个代表力的变量相加，这就是check list。但是我们知道，每一个程序都相当复杂，check list需要检查的地方遍布所有文件。那难道我们在code review的时候可以一行一行仔细看吗？这是不可能的。正因为如此，我们要把程序写成&#8220;让<strong>编译器可以检查很多我们可能会手抖犯的错误</strong>&#8221;的形式，让我们从这些琐碎的事情里面解放出来。</p>
<p>银弹这种东西是不存在的，所以type rich programming能解决的事情就是防止手抖而犯错误。有一些错误不是手抖可以抖出来的，譬如说错误的设计，这并不是type rich programming能很好地处理的范围。为了解决这些事情，我们就需要更多可以遵守的best practice了。</p>
<p>当然，其中的一个将是DSL&#8212;&#8212;domain specific language，领域特定语言了。敬请关注下一篇，《如何设计一门语言（十）&#8212;&#8212;DSL与建模》。</p> <img src ="http://www.cppblog.com/vczh/aggbug/202605.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-08-17 16:26 <a href="http://www.cppblog.com/vczh/archive/2013/08/17/202605.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（八）&amp;mdash;&amp;mdash;异步编程和CPS变换</title><link>http://www.cppblog.com/vczh/archive/2013/07/27/202154.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 27 Jul 2013 03:12:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/07/27/202154.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/202154.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/07/27/202154.html#Feedback</comments><slash:comments>13</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/202154.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/202154.html</trackback:ping><description><![CDATA[<p>关于这个话题，其实在<a href="http://www.cppblog.com/vczh/archive/2013/06/10/200920.html" target="_blank">（六）</a>里面已经讨论了一半了。学过Haskell的都知道，这个世界上很多东西都可以用monad和comonad来把一些复杂的代码给抽象成简单的、一看就懂的形式。他们的区别，就像用js做一个复杂的带着几层循环的动画，直接写出来和用jquery的“回调”写出来的代码一样。前者能看不能用，后者能用不能看。那有没有什么又能用又能看的呢？我目前只能在Haskell、C#和F#里面看到。至于说为什么，当然是因为他们都支持了monad和comonad。只不过C#作为一门不把“用库来改造语言”作为重要特征的语言，并没打算让你们能跟haskell和F#一样，把东西抽象成monad，然后轻松的写出来。C#只内置了yield return和async await这样的东西。</p> <p>把“用库来改造语言”作为重要特征的语言其实也不多，大家熟悉的也就只有lisp和C++，不熟悉的有F#。F#除了computation expression以外，还有一个type provider的功能。就是你可以在你的当前的程序里面，写一小段代码，通知编译器在编译你的代码的时候执行以下（有点类似鸡生蛋的问题但其实不是）。这段代码可以生成新的代码（而不是跟lisp一样修改已有的代码），然后给你剩下的那部分程序使用。例子我就不举了，有兴趣的大家看这里：<a title="http://msdn.microsoft.com/en-us/library/vstudio/hh361034.aspx" href="http://msdn.microsoft.com/en-us/library/vstudio/hh361034.aspx" target="_blank">http://msdn.microsoft.com/en-us/library/vstudio/hh361034.aspx</a>。里面有一个例子讲的是如何在F#里面创造一个强类型的正则表达式库，而且并不像boost的spirit或者xpress那样，正则表达式仍然使用字符串来写的。这个正则表达式在编译的时候就可以知道你有没有弄错东西了，不需要等到运行才知道。</p> <p>Haskell和F#分别尝试了monad/comonad和computation expression，为的就是能用一种不会失控（lisp的macro就属于会失控的那种）方法来让用户自己表达属于自己的可以天然被continuation passing style变换处理的东西。在介绍C#的async await的强大能力之前，先来讲一下Haskell和F#的做法。为什么按照这个程序呢，因为Haskell的monad表达能力最低，其次是F#，最后是C#的那个。当然C#并不打算让你自己写一个支持CPS变换的类型。<font size="3"><font size="2">作为补充，我将在这篇文章的最后，</font><strong>讲一下我最近正在设计的一门语言，是如何把C#的yield return和async await都变成库</strong></font><font size="2">，而不是编译器的功能的</font>。</p> <p>下面我将抛弃所有跟学术有关的内容，只会留下跟实际开发有关系的东西。</p> <p><strong><font size="3">一、Haskell和Monad</font></strong></p> <p>Haskell面临的问题其实比较简单，第一是因为Haskell的程序都不能有隐式状态，第二是因为Haskell没有语句只有表达式。这意味着你所有的控制流都必须用递归或者CPS来做。从这个角度上来讲，Monad也算是CPS的一种应用了。于是我为了给大家解释一下Monad是怎么运作的，决定来炒炒冷饭，说error code的故事。这个故事已经在<a href="http://www.cppblog.com/vczh/archive/2013/07/05/201541.html" target="_blank">（七）</a>里面讲了，但是今天用的是Haskell，别有一番异域风情。</p> <p>大家用C/C++的时候都觉得处理起error code是个很烦人的事情吧。我也不知道为什么那些人放着exception不用，对error code那么喜欢，直到有一天，我听到有一个傻逼在微博上讲：“error code的意思就是我可以不理他”。我终于明白了，这个人是一个真正的傻逼。不过Haskell还是很体恤这些人的，就跟耶稣一样，凡是信他就可以的永生，傻逼也可以。可惜的是，傻逼是学不会Monad的，所以耶稣只是个传说。</p> <p>由于Haskell没有“引用参数”，所以所有的结果都必须出现在返回值里面。因此，倘若要在Haskell里面做error code，就得返回一个data。data就跟C语言的union一样，区别是data是强类型的，而C的union一不小心就会傻逼了：</p> <div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>data Unsure a = Sure a | Error <span style="color: #0000ff">string</span></pre></div>
<p>然后给一些必要的实现，首先是Functor： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>instance Functor Unsure <span style="color: #0000ff">where</span><span style="color: #000000">
    fmap f (Sure x) </span>=<span style="color: #000000"> Sure (f x)
    fmap f (Error e) </span>= Error e</pre></div>
<p>剩下的就是Monad了： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>instance Monad Unsure <span style="color: #0000ff">where</span>
    <span style="color: #0000ff">return</span> =<span style="color: #000000"> Sure
    fail </span>=<span style="color: #000000"> Error
    (Sure s) </span>&gt;&gt;= f =<span style="color: #000000"> f s
    (Error e) </span>&gt;&gt;= f = Error e</pre></div>
<p>看起来也不多，加起来才八行，就完成了error code的声明了。当然就这么看是看不出Monad的强大威力的，所以我们还需要一个代码。譬如说，给一个数组包含了分数，然后把所有的分数都转换成“牛逼”、“一般”和“傻逼”，重新构造成一个数组。一个真正的Haskell程序员，会把这个程序分解成两半，第一半当然是一个把分数转成数字的东西： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> Tag :: integer -&gt; Unsure string</span>
Tag f = 
    <span style="color: #0000ff">if</span> f &lt; <span style="color: #800080">0</span> then Error <span style="color: #800000">"</span><span style="color: #800000">分数必须在0-100之间</span><span style="color: #800000">"</span> <span style="color: #0000ff">else</span>
    <span style="color: #0000ff">if</span> f&lt;<span style="color: #800080">60</span> then Sure <span style="color: #800000">"</span><span style="color: #800000">傻逼</span><span style="color: #800000">"</span> <span style="color: #0000ff">else</span>
    <span style="color: #0000ff">if</span> f&lt;<span style="color: #800080">90</span> then Sure <span style="color: #800000">"</span><span style="color: #800000">一般</span><span style="color: #800000">"</span> <span style="color: #0000ff">else</span>
    <span style="color: #0000ff">if</span> f&lt;=<span style="color: #800080">100</span> then Sure <span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span> <span style="color: #0000ff">else</span><span style="color: #000000">
    Error </span><span style="color: #800000">"</span><span style="color: #800000">分数必须在0-100之间</span><span style="color: #800000">"</span></pre></div>
<p>后面就是一个循环了： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> TagAll :: [integer] -&gt; Unsure [string]</span>
<span style="color: #000000">
TagAll [] </span>=<span style="color: #000000"> []
TagAll (x:xs) </span>= <span style="color: #0000ff">do</span><span style="color: #000000">
    first </span>&lt;-<span style="color: #000000"> Tag x
    remains </span>&lt;-<span style="color: #000000"> TagAll xs
    </span><span style="color: #0000ff">return</span> first:remains</pre></div>
<p>TagAll是一个循环，把输入的东西每一个都用Tag过一遍。如果有一次Tag返回失败了，整个TagAll函数都会失败，然后返回错误。如果全部成功了，那么TagAll函数会返回整个处理后的数组。</p>
<p>当然一个循环写成了非尾递归不是一个真正的Haskell程序员会做的事情，真正的Haskell程序员会把事情做成这样（把&gt;&gt;=展开之后你们可能会觉得这个函数不是尾递归，但是因为Haskell是call by need的，所以实际上会成为一个尾递归的函数）： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> TagAll :: [integer] -&gt; Unsure [string]</span>
TagAll xs = reverse $ TagAll_ xs [] <span style="color: #0000ff">where</span><span style="color: #000000">
    TagAll [] ys </span>=<span style="color: #000000"> Sure ys
    TagAll (x:xs) ys </span>= <span style="color: #0000ff">do</span><span style="color: #000000">
        y </span>&lt;-<span style="color: #000000"> Tag x
        TagAll xs (y:ys)</span></pre></div>
<p>为什么代码里面一句“检查Tag函数的返回值”的代码都没有呢？这就是Haskell的Monad的表达能力的威力所在了。Monad的使用由do关键字开始，然后这个表达式可以被这么定义： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">MonadExp
    ::</span>= <span style="color: #800000">"</span><span style="color: #800000">do</span><span style="color: #800000">"</span><span style="color: #000000"> FragmentNotNull

FragmentNotNull
    ::</span>= [Pattern <span style="color: #800000">"</span><span style="color: #800000">&lt;-</span><span style="color: #800000">"</span><span style="color: #000000">] Expression EOL FragmentNull

FragmentNull
    ::</span>=<span style="color: #000000"> FragmentNotNull
    ::</span>= ε</pre></div>
<p>意思就是说，do后面一定要有“东西”，然后这个“东西”是这么组成的： <br>1、第一样要是一个a&lt;-e这样的东西。如果你不想给返回值命名，就省略“a&lt;-”这部分 <br>2、然后重复</p>
<p>这表达的是这样的一个意思： <br>1、先做e，然后把结果保存进a <br>2、然后做下面的事情</p>
<p>看到了没有，“然后做下面的事情”是一个典型的continuation passing style的表达方法。但是我们可以看到，在例子里面所有的e都是Unsure T类型的，而a相应的必须为T。那到底是谁做了这个转化呢？</p>
<p>聪明的，哦不，正常的读者一眼就能看出来，“&lt;-”就是调用了我们之前在上面实现的一个叫做“&gt;&gt;=”的函数了。我们首先把“<font color="#ff0000"><strong>e</strong></font>”和“<strong><font color="#0000ff">然后要做的事情</font></strong>”这两个参数传进了&gt;&gt;=，然后&gt;&gt;=去解读<strong><font color="#ff0000">e</font></strong>，得到<strong><font color="#00ff00">a</font></strong>，把<strong><font color="#00ff00">a</font></strong>当成“<strong><font color="#0000ff">然后要做的事情</font></strong>”的参数调用了一下。如果<strong><font color="#ff0000">e</font></strong>解读失败的到了错误，“<strong><font color="#0000ff">然后要做的事情</font></strong>”自然就不做了，于是整个函数就返回错误了。</p>
<p>Haskell一下就来尾递归还是略微复杂了点，我们来写一个简单点的例子，写一个函数判断一个人的三科成绩里面，有多少科是牛逼的： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> Count牛逼 :: integer -&gt; integer -&gt; integer –&gt; Unsure integer</span>
<span style="color: #000000">
Count牛逼 chinese math english </span>= <span style="color: #0000ff">do</span><span style="color: #000000">
    a </span>&lt;-<span style="color: #000000"> Tag chinese
    b </span>&lt;-<span style="color: #000000"> Tag math
    c </span>&lt;-<span style="color: #000000"> Tag english
    </span><span style="color: #0000ff">return</span> length [x | x &lt;- [a, b, c], x == <span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span>]</pre></div>
<p>根据上文的描述，我们已经知道，这个函数实际上会被处理成： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> Count牛逼 :: integer -&gt; integer -&gt; integer –&gt; Unsure integer</span>
<span style="color: #000000">
Count牛逼 chinese math english
    Tag chinese </span>&gt;&gt;= \a-&gt;<span style="color: #000000">
    Tag math </span>&gt;&gt;= \b-&gt;<span style="color: #000000">
    Tag english </span>&gt;&gt;= \c-&gt;
    <span style="color: #0000ff">return</span> length [x | x &lt;- [a, b, c], x == <span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span>]</pre></div>
<p>&gt;&gt;=函数的定义是 </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>instance Monad Unsure <span style="color: #0000ff">where</span>
    <span style="color: #0000ff">return</span> =<span style="color: #000000"> Sure
    fail </span>=<span style="color: #000000"> Error
<font size="3"><font size="2">    </font><strong>(Sure s) </strong></font></span><font size="3"><strong>&gt;&gt;= f =</strong></font><font size="3"><span style="color: #000000"><strong> f s
</strong><font size="3"><font size="2">    </font><strong>(Error e)</strong></font><strong> </strong></span><strong>&gt;&gt;= f = Error e</strong></font></pre></div>
<p>这是一个运行时的pattern matching。一个对参数带pattern matching的函数用Haskell的case of写出来是很难看的，所以Haskell给了这么个语法糖。但这个时候我们要把&gt;&gt;=函数展开在我们的“Count牛逼”函数里面，就得老老实实地用case of了： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> Count牛逼 :: integer -&gt; integer -&gt; integer –&gt; Unsure integer</span>
<span style="color: #000000">
Count牛逼 chinese math english
    </span><span style="color: #0000ff">case</span><span style="color: #000000"> Tag chinese of {
        Sure a </span>-&gt; <span style="color: #0000ff">case</span><span style="color: #000000"> Tag math of {
            Sure b </span>-&gt; <span style="color: #0000ff">case</span><span style="color: #000000"> Tag english of {
                Sure c </span>-&gt; Sure $ length [x | x &lt;- [a, b, c], x == <span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span><span style="color: #000000">]
                Error e </span>-&gt;<span style="color: #000000"> Error e
            }
            Error e </span>-&gt;<span style="color: #000000"> Error e
        }
        Error e </span>-&gt;<span style="color: #000000"> Error e
    }</span></pre></div>
<p>是不是又回到了我们在C语言里面被迫做的，还有C++不喜欢用exception的人（包含一些觉得error code可以忽略的傻逼）做的，到处检查函数返回值的事情了？我觉得只要是一个正常人，都会选择这种写法的： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> Count牛逼 :: integer -&gt; integer -&gt; integer –&gt; Unsure integer</span>
<span style="color: #000000">
Count牛逼 chinese math english
    Tag chinese </span>&gt;&gt;= \a-&gt;<span style="color: #000000">
    Tag math </span>&gt;&gt;= \b-&gt;<span style="color: #000000">
    Tag english </span>&gt;&gt;= \c-&gt;
    <span style="color: #0000ff">return</span> length [x | x &lt;- [a, b, c], x == <span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span>]</pre></div>
<p>于是我们用Haskell的Monad，活生生的把“每次都检查函数返回值”的代码压缩到了Monad里面，然后就可以把代码写成try-catch那样的东西了。error code跟exception本来就是一样的嘛，只是一个写起来复杂所以培养了很多觉得错误可以忽略的傻逼，而一个只需要稍微训练一下就可以把代码写的很简单罢了。</p>
<p>不过Haskell没有变量，那些傻逼们可能会反驳：C/C++比Haskell复杂多了，你怎么知道exception就一定没问题呢？这个时候，我们就可以看F#的computation expression了。</p>
<p><strong><font size="3">二、F#和computation expression</font></strong></p>
<p>F#虽然被设计成了一门函数式语言，但是其骨子里还是跟C#一样带状态的，而且编译成MSIL代码之后，可以直接让F#和C#互相调用。一个真正的Windows程序员，从来不会拘泥于让一个工程只用一个语言来写，而是不同的大模块，用其适合的最好的语言。微软把所有的东西都设计成可以<strong>强类型地互操作</strong>的，所以在Windows上面从来不存在什么“如果我用A语言写了，B就用不了”的这些事情。这是跟Linux的一个巨大的区别。Linux是没有强类型的互操作的（字符串信仰者们再见），而Windows有。什么，Windows不能用来做Server？那Windows Azure怎么做的，bing怎么做的。什么，只有微软才知道怎么正确使用Windows Server？你们喜欢玩的EVE游戏的服务器是怎么做的呢？</p>
<p>在这里顺便黑一下gcc。<strong>钱（区别于财产）对于一个程序员是很重要的</strong>。VC++和clang/LLVM都是领着工资写的，gcc不知道是谁投资的（这也就意味着写得好也涨不了工资）。而且我们也都知道，gcc在windows上编译的慢出来的代码还不如VC++，gcc在linux上编译的慢还不如clang，在mac/ios上就不说了，下一个版本的xcode根本没有什么gcc了。理想主义者们醒醒，gcc再见。</p>
<p>为什么F#有循环？答案当然是因为F#有变量了。一个没有变量的语言是写不出循环退出条件的，只能写出递归退出条件。有了循环的话，就会有各种各样的东西，那Monad这个东西就不能很好地给“东西”建模了。于是F#本着友好的精神，既然大家都那么喜欢Monad，那他做出一个computation expression，学起来肯定就很容易了。</p>
<p>于是在F#下面，那个TagAll终于可以读入一个真正的列表，写出一个真正的循环了： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">let</span><span style="color: #000000"> TagAll xs = unsure
{
    </span><span style="color: #0000ff">let</span> r = Array.create xs.length <span style="color: #800000">""</span>
    <span style="color: #0000ff">for</span> i <span style="color: #0000ff">in</span> <span style="color: #800080">0</span> .. xs.length-<span style="color: #800080">1</span> <span style="color: #0000ff">do</span>
        <span style="color: #0000ff">let</span><span style="color: #000000">! tag = Tag xs.[i]
        r.[i]</span>&lt;-<span style="color: #000000">tag
    return r
}</span></pre></div>
<p>注意那个let!，其实就是Haskell里面的&lt;-。只是因为这些东西放在了循环里，那么那个“Monad”表达出来就没有Haskell的Monad那么纯粹了。为了解决这个问题，F#引入了computation expression。所以为了让那个unsure和let!起作用，就得有下面的代码，做一个名字叫做unsure的computation expression： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">type</span><span style="color: #000000"> UnsureBuilder() =
    </span><span style="color: #0000ff">member</span> this.Bind(m, f) = <span style="color: #0000ff">match</span> m <span style="color: #0000ff">with</span>
        | Sure a -&gt;<span style="color: #000000"> f a
        </span>| Error s -&gt;<span style="color: #000000"> Error s
    </span><span style="color: #0000ff">member</span><span style="color: #000000"> this.For(xs, body) =unsure
    {
         </span><span style="color: #0000ff">match</span> xs <span style="color: #0000ff">with</span>
        | [] -&gt;<span style="color: #000000"> Sure ()
        </span>| x::xs -&gt; 
            <span style="color: #0000ff">let</span><span style="color: #000000">! r = Tag x
            body r
            return this.For xs body
    }
    .... </span><span style="color: #008000">//</span><span style="color: #008000"> 还有很多别的东西</span></pre></div>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">let</span> unsure = <span style="color: #0000ff">new</span> UnsureBuilder()</pre></div>
<p>所以说带有副作用的语言写出来的代码又长，不带副作用的语言写出来的代码又难懂，这之间很难取得一个平衡。</p>
<p>如果输入的分数数组里面有一个不在0到100的范围内，那么for循环里面的“let! tag = Tag xs.[i]”这句话就会引发一个错误，导致TagAll函数失败。这是怎么做到的？</p>
<p>首先，Tag引发的错误是在for循环里面，也就是说，实际运行的时候是调用UnsuerBuilder类型的unsure.For函数来执行这个循环的。For函数内部使用“let! r = Tag x”，这个时候如果失败，那么let!调用的Bind函数就会返回Error s。于是unsure.Combine函数判断第一个语句失败了，那么接下来的语句“body r ; return this.For xs body”也就不执行了，直接返回错误。这个时候For函数的递归终止条件就产生作用了，由一层层的return（F#自带尾递归优化，所以那个For函数最终会被编译成一个循环）往外传递，导致最外层的For循环以Error返回值结束。TagAll里面的unsure,Combine函数看到for循环完蛋了，于是return r也不执行了，返回错误。</p>
<p>这个过程跟Haskell的那个版本做的事情完全是一样的，只是由于F#多了很多语句，所以Monad展开成computation expression之后，表面上看起来就会复杂很多。如果明白Haskell的Monad在干什么事情的话，F#的computation expression也是很容易就学会的。</p>
<p>当然，觉得“error code可以忽略”的傻逼是没有可能的。</p>
<p><strong><font size="3">三、C#的yield return和async await</font></strong></p>
<p>如果大家已经明白了Haskell的&gt;&gt;=和F#的Bind（其实也是let!）就是一回事的话，而且也明白了我上面讲的如何把do和&lt;-变成&gt;&gt;=的方法的话，大家应该对CPS在实际应用的样子心里有数了。不过，这种理解的方法实际上是相当有限的。为什么呢？让我们来看C#的两个函数： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>IEnumerable&lt;T&gt; Concat(<span style="color: #0000ff">this</span> IEnumerable&lt;T&gt; a, IEnumerable&lt;T&gt;<span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">foreach</span>(<span style="color: #0000ff">var</span> x <span style="color: #0000ff">in</span><span style="color: #000000"> a)
        </span><span style="color: #0000ff">yield</span> <span style="color: #0000ff">return</span><span style="color: #000000"> x;
    </span><span style="color: #0000ff">foreach</span>(<span style="color: #0000ff">var</span> x <span style="color: #0000ff">in</span><span style="color: #000000"> b)
        </span><span style="color: #0000ff">yield</span> <span style="color: #0000ff">return</span><span style="color: #000000"> x;
}</span></pre></div>
<p>上面那个是关于yield return和IEnumerable&lt;T&gt;的例子，讲的是Linq的Concat函数是怎么实现的。下面还有一个async await和Task&lt;T&gt;的例子： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">async</span> Task&lt;T[]&gt; SequencialExecute(<span style="color: #0000ff">this</span> Task&lt;T&gt;<span style="color: #000000">[] tasks)
{
    </span><span style="color: #0000ff">var</span> ts = <span style="color: #0000ff">new</span><span style="color: #000000"> T[tasks.Length];
    </span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> i=<span style="color: #800080">0</span>;i&lt;tasks.Length;i++<span style="color: #000000">)
        ts[i]</span>=<span style="color: #0000ff">await</span><span style="color: #000000"> tasks[i];
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> ts;
}</span></pre></div>
<p>这个函数讲的是，如果你有一堆Task&lt;T&gt;，如何构造出一个内容来自于异步地挨个执行tasks里面的每个Task&lt;T&gt;的Task&lt;T[]&gt;的方法。</p>
<p>大家可能会注意到，C#的yield return和await的“<strong><font size="3">味道</font></strong>”，就跟Haskell的&lt;-和&gt;&gt;=、F#的Bind和let!一样。在处理这种语言级别的事情的时候，千万不要去管代码它实际上在干什么，这其实是次要的。最重要的是形式。什么是形式呢？也就是说，同样一个任务，是如何被不同的方法表达出来的。上面说的“味道”就都在“表达”的这个事情上面了。</p>
<p>这里我就要提一个问题了。</p>
<ol>
<li>Haskell有Monad，所以我们可以给自己定义的类型实现一个Monad，从而让我们的类型可以用do和&lt;-来操作。 
<li>F#有computation expression，所以我们可以给自己定义的类型实现一个computation expression，从而让我们的类型可以用let!来操作。 
<li>C#有【什么】，所以我们可以给自己定义的类型实现一个【什么】，从而让我们的类型可以用【什么】来操作？ </li></ol>
<p>熟悉C#的人可能很快就说出来了，答案是Linq、Linq Provider和from in了。这篇《Monadic Parser Combinator using C# 3.0》<a title="http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx" href="http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx">http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx</a> 介绍了一个如何把语法分析器（也就是parser）给写成monad，并且用Linq的from in来表达的方法。</p>
<p>大家可能一下子不明白什么意思。Linq Provider和Monad是这么对应的：</p>
<ol>
<li>fmap对应于Select 
<li>&gt;&gt;=对应于SelectMany 
<li>&gt;&gt;= + return也对应与Select（回忆一下Monad这个代数结构的几个定理，就有这么一条） </li></ol>
<p>然后诸如这样的Haskell代码： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> Count牛逼 :: integer -&gt; integer -&gt; integer –&gt; Unsure integer</span>
<span style="color: #000000">
Count牛逼 chinese math english </span>= <span style="color: #0000ff">do</span><span style="color: #000000">
    a </span>&lt;-<span style="color: #000000"> Tag chinese
    b </span>&lt;-<span style="color: #000000"> Tag math
    c </span>&lt;-<span style="color: #000000"> Tag english
    </span><span style="color: #0000ff">return</span> length [x | x &lt;- [a, b, c], x == <span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span>]</pre></div>
<p>就可以表达成： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>Unsure&lt;<span style="color: #0000ff">int</span>&gt; Count牛逼(<span style="color: #0000ff">int</span> chinese, <span style="color: #0000ff">int</span> math, <span style="color: #0000ff">int</span><span style="color: #000000"> english)
{
    </span><span style="color: #0000ff">return</span>
        <span style="color: #0000ff">from</span> a <span style="color: #0000ff">in</span><span style="color: #000000"> Tag(chinese)
        </span><span style="color: #0000ff">from</span> b <span style="color: #0000ff">in</span><span style="color: #000000"> Tag(math)
        </span><span style="color: #0000ff">from</span> c <span style="color: #0000ff">in</span><span style="color: #000000"> Tag(english)
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> <span style="color: #0000ff">int</span>[]{a, b, c}.Where(x=&gt;x==<span style="color: #800000">"</span><span style="color: #800000">牛逼</span><span style="color: #800000">"</span><span style="color: #000000">).Count();
}</span></pre></div>
<p>不过Linq的这个表达方法跟yield return和async await一比，就有一种Monad和computation expression的感觉了。Monad只能一味的递归一个一个往下写，而computation expression则还能加上分支循环异常处理什么的。C#的from in也是一样，没办法表达循环异常处理等内容。</p>
<p>于是上面提到的那个问题</p>
<blockquote>
<p>C#有【什么】，所以我们可以给自己定义的类型实现一个【什么】，从而让我们的类型可以用【什么】来操作？</p></blockquote>
<p>其实并没有回答完整。我们可以换一个角度来体味。假设IEnumerable&lt;T&gt;和Task&lt;T&gt;都是我们自己写的，而不是.net framework里面的内容，那么C#究竟要加上一个什么样的（类似于Linq Provider的）功能，从而让我们可以写出接近yield return和async await的效果的代码呢？如果大家对我的那篇《<a href="http://www.cppblog.com/vczh/archive/2013/06/26/201310.html" target="_blank">时隔多年我又再一次体验了一把跟大神聊天的感觉</a>》还有点印象的话，其实我当时也对我自己提出了这么个问题。</p>
<p>我那个时候一直觉得，F#的computation expression才是正确的方向，但是我怎么搞都搞不出来，所以我自己就有点动摇了。于是我跑去问了Don Syme，他很斩钉截铁的告诉我说，computation expression是做不到那个事情的，但是需要怎么做他也没想过，让我自己research。后来我就得到了一个结论。</p>
<p><strong><font size="3">四、Koncept（我正在设计的语言）的yield return和async await（问题）</font></strong></p>
<p>Koncept主要的特征是concept mapping和interface。这两种东西的关系就像函数和lambda表达式、instance和class一样，是定义和闭包的关系，所以相处起来特别自然。首先我让函数只能输入一个参数，不过这个参数可以是一个tuple，于是f(a, b, c)实际上是f.Invoke(Tuple.Create(a, b, c))的语法糖。然后所有的overloading都用类似C++的偏特化来做，于是C++11的不定模板参数（variadic template argument）在我这里就成为一个“推论”了，根本不是什么需要特殊支持就自然拥有的东西。这也是concept mapping的常用手法。最后一个跟普通语言巨大的变化是我删掉了class，只留下interface。反正你们写lambda表达时也不会给每个闭包命名字（没有C++11的C++除外），那为什么写interface就得给每一个闭包（class）命名字呢？所以我给删去了。剩下的就是我用类似mixin的机制可以把函数和interface什么的给mixin到普通的类型里面去，这样你也可以实现class的东西，就是写起特别来麻烦，于是我在语法上就鼓励你不要暴露class，改为全部暴露function、concept和interface。</p>
<p>不过这些都不是重点，因为除了这些差异以外，其他的还是有浓郁的C#精神在里面的，所以下面在讲Koncept的CPS变换的时候，我还是把它写成C#的样子，Koncept长什么样子以后我再告诉你们，因为Koncept的大部分设计都跟CPS变换是没关系的。</p>
<p>回归正题。之前我考虑了许久，觉得F#的computation expression又特别像是一个正确的解答，但是我怎么样都找不到一个可以把它加入Koncept地方法。这个问题我从NativeX（<a href="http://www.cppblog.com/vczh/archive/2010/11/07/132876.html" target="_blank">这里</a>、<a href="http://www.cppblog.com/vczh/archive/2010/12/05/135505.html" target="_blank">这里</a>、<a href="http://www.cppblog.com/vczh/archive/2011/02/25/140618.html" target="_blank">这里</a>和<a href="http://www.cppblog.com/vczh/archive/2011/03/20/142261.html" target="_blank">这里</a>）的时候就一直在想了，中间兜了一个大圈，整个就是试图山寨F#结果失败的过程。为什么F#的computation expression模型不能用呢，归根结底是因为，<strong>F#的循环没有break和continue</strong>。C#的跳转是自由的，不仅有break和continue，你还可以从循环里面return，甚至goto。因此一个for循环无论如何都表达不成F#的那个函数：M&lt;U&gt; For(IEnumerable&lt;T&gt; container, Func&lt;T, M&lt;U&gt;&gt; body);。break、continue、return和goto没办法表达在类型上。</p>
<p>伟大的先知<a href="http://channel9.msdn.com/search?term=eric+meijer&amp;type=All" target="_blank">Eric Meijer</a>告诉我们：“<strong>一个函数的类型表达了关于函数的业务的一切</strong>”。为什么我们还要写函数体，是因为编译器还没有聪明到看着那个类型就可以帮我们把代码填充完整。所以其实当初看着F#的computation expression的For的定义的时候，是因为我脑筋短路，没有想起Eric Meijer的这句话，导致我浪费了几个月时间。当然我到了后面也渐渐察觉到了这个事情，产生了动摇，自己却无法确定，所以去问了Don Syme。于是，我就得到了关于这个问题的结论的一半：在C#（其实Koncept也是）支持用户可以自由添加的CPS变换（譬如说用户添加IEnumerable&lt;T&gt;的时候添加yield return和yield break，用户添加Task&lt;T&gt;的时候添加await和return）的话，<strong>使用CPS变换的那段代码，必须用控制流图（control flow graph）处理完之后生成一个状态机来做</strong>，而不能跟Haskell和F#一样拆成一个一个的小lambda表达式。</p>
<p>其实C#的yield return和async await，从一开始就是编译成状态机的。只是C#没有开放那个功能，所以我一直以为这并不是必须的。想来微软里面做语言的那帮牛逼的人还是有牛逼的道理的，一下子就可以找到问题的正确方向，跟搞go的二流语言专家（尽管他也牛逼但是跟语言一点关系也没有）是完全不同的。连Mozilla的Rust的设计都比go强一百倍。</p>
<p>那另一半的问题是什么呢？为了把问题看得更加清楚，我们来看两个长得很像的yield return和async await的例子。为了把本质的问题暴露出来，我决定修改yield return的语法：</p>
<ol>
<li>首先把yield return修改成yield 
<li>其次吧yield break修改成return 
<li>然后再给函数打上一个叫做seq的东西，跟async对称，就当他是个关键字 
<li>给所有CPS operator加上一个感叹号，让他变得更清楚（这里有yield、await和return）。为什么return也要加上感叹号呢？因为如果我们吧seq和aysnc摘掉的话，我们会发现return的类型是不匹配的。所以这不是一个真的return。</li></ol>
<p>然后就可以来描述一个类似Linq的TakeWhile的事情了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>seq IEnumerable&lt;T&gt; TakeWhile(<span style="color: #0000ff">this</span> IEnumerable&lt;T&gt; source, Predicate&lt;T&gt;<span style="color: #000000"> predicate)
{
    </span><span style="color: #0000ff">foreach</span>(<span style="color: #0000ff">var</span> x <span style="color: #0000ff">in</span><span style="color: #000000"> source)
    {
        </span><span style="color: #0000ff">if</span>(!<span style="color: #000000">predicate(x))
            </span><span style="color: #0000ff">return</span>!<span style="color: #000000">;
        </span><span style="color: #0000ff">yield</span>!<span style="color: #000000"> x
    }
}

</span><span style="color: #0000ff">async</span> Task&lt;T[]&gt; TakeWhile(<span style="color: #0000ff">this</span> Task&lt;T&gt;[] source, Predicate&lt;T&gt;<span style="color: #000000"> predicate)
{
    List</span>&lt;T&gt; result=<span style="color: #0000ff">new</span> List&lt;T&gt;<span style="color: #000000">();
    </span><span style="color: #0000ff">foreach</span>(<span style="color: #0000ff">var</span> t <span style="color: #0000ff">in</span><span style="color: #000000"> source)
    {
        </span><span style="color: #0000ff">var</span> x = <span style="color: #0000ff">await</span>!<span style="color: #000000"> t;
        </span><span style="color: #0000ff">if</span>(!<span style="color: #000000">predicate(x))
            </span><span style="color: #0000ff">return</span>!<span style="color: #000000"> result.ToArray();
        result.Add(x);
    }
    </span><span style="color: #0000ff">return</span>!<span style="color: #000000"> result.ToArray();
}</span></pre></div>于是问题就很清楚了。如果我们想让用户自己通过类库的方法来实现这些东西，那么yield和await肯定是两个函数，因为这是C#里面唯一可以用来写代码的东西，就算看起来再奇怪，也不可能是别的。

<ol>
<li>seq和async到底是什么？ 
<li>seq下面的yield和return的类型分别是什么？ 
<li>async下面的await和return的类型分别是什么？</li></ol>
<p>其实这里还有一个谜团。其实seq返回的东西应该是一个IEnumerator&lt;T&gt;，只是因为C#觉得IEnumerable&lt;T&gt;是更好地，所以你两个都可以返回。那么，是什么机制使得，函数可以构造出一个IEnumerable&lt;T&gt;，而整个状态机是在IEnumerator&lt;T&gt;的MoveNext函数里面驱动的呢？而async和Task&lt;T&gt;就没有这种情况了。</p>
<p>首先解答第一个问题。因为yield、return和await都是函数，是函数就得有个namespace，那我们可以拿seq和async做namespace。所以seq和async，设计成两个static class<strong>也是没有问题的</strong>。</p>
<p>其次，seq的yield和return修改了某个IEnumerator&lt;T&gt;的状态，而async的await和return修改了某个Task&lt;T&gt;的状态。而seq和async的返回值分别是IEnumerable&lt;T&gt;和Task&lt;T&gt;。因此对于一个CPS变换来说，一共需要两个类型，第一个是返回值，第二个是实际运行状态机的类。</p>
<p>第三，CPS变换还需要有一个启动函数。IEnumerator&lt;T&gt;的第一次MoveNext调用了那个启动函数。而Task&lt;T&gt;的Start调用了那个启动函数。启动函数自己维护着所有状态机的内容，而状态机本身是CPS operator们看不见的。为什么呢？因为一个状态机也是一个类，这些状态机类是没有任何公共的contract的，也就是说无法抽象他们。因此<strong>CPS operator必须不能知道状态机类</strong>。</p>
<p>而且yield、return和await都叫CPS operator，那么他们不管是什么类型，本身肯定看起来像一个CPS的函数。之前已经讲过了，CPS函数就是把普通函数的返回值去掉，转而添加一个lambda表达式，用来代表“拿到返回之后的下一步计算”。</p>
<p>因此总的来说，我们拿到了这四个方程，就可以得出一个解了。解可以有很多，我们选择最简单的部分。</p>
<p>那现在就开始来解答上面两个TakeWhile最终会被编译成什么东西了。</p>
<p><strong><font size="3">五、Koncept（我正在设计的语言）的yield return和async await（seq答案）</font></strong></p>
<p>首先来看seq和yield的部分。上面讲到了，yield和return都是在修改某个IEnumerator&lt;T&gt;的状态，但是编译器自己肯定不能知道一个合适的IEnumerator&lt;T&gt;是如何被创建出来的。所以这个类型必须由用户来创建。而为了第一次调用yield的时候就已经有IEnumerator&lt;T&gt;可以用，所以CPS的启动函数就必须看得到那个IEnumerator&lt;T&gt;。但是CPS的启动函数又不可能去创建他，所以，这个IEnumerator&lt;T&gt;对象肯定是一个continuation的参数了。</p>
<p>看，<strong>其实写程序都是在做推理的</strong>。尽管我们现在还不知道整个CPS要怎么运作，但是随着这些线索，我们就可以先把类型搞出来。搞出了类型之后，就可以来填代码了。</p>
<ol>
<li>对于yield，yield接受了一个T，没有返回值。一个没有返回值的函数的continuation是什么呢？当然就是一个没有参数的函数了。 
<li>return则连输入都没有。 
<li>而且yield和return都需要看到IEnumerator&lt;T&gt;。所以他们肯定有一个参数包含这个东西。</li></ol>
<p>那么这三个函数的类型就都确定下来了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">class</span><span style="color: #000000"> seq
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IEnumerator&lt;T&gt; CreateCps&lt;T&gt;(Action&lt;seq_Enumerator&lt;T&gt;&gt;<span style="color: #000000">);
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> <span style="color: #0000ff">yield&lt;T&gt;</span>(seq_Enumerator&lt;T&gt;<span style="color: #000000"> state, T value, Action continuation);
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> exit&lt;T&gt;(seq_Enumerator&lt;T&gt; state <span style="color: #008000">/*</span><span style="color: #008000">没有输入</span><span style="color: #008000">*/</span> <span style="color: #008000">/*</span><span style="color: #008000">exit代表return，函数结束的意思就是不会有一个continuation</span><span style="color: #008000">*/</span><span style="color: #000000">);
}</span></pre></div>

<p>什么是seq_Enumerator&lt;T&gt;呢？当然是我们那个“某个IEnumerator&lt;T&gt;”的真是类型了。</p>
<p>于是看着类型，<strong>唯一可能的有意义又简单的实现</strong>如下： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> seq_Enumerable&lt;T&gt; : IEnumerable&lt;T&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">public</span> Action&lt;seq_Enumerator&lt;T&gt;&gt;<span style="color: #000000"> startContinuation;

    </span><span style="color: #0000ff">public</span> IEnumerator&lt;T&gt;<span style="color: #000000"> CreateEnumerator()
    {
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span> seq_Enumerator&lt;T&gt;<span style="color: #000000">
        {
            startContinuation</span>=<span style="color: #0000ff">this</span><span style="color: #000000">.startContinuation)
        };
    }
}

</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> seq_Enumerator&lt;T&gt; : IEnumerator&lt;T&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">public</span><span style="color: #000000"> T current;
    </span><span style="color: #0000ff">bool</span><span style="color: #000000"> available;
    Action</span>&lt;seq_Enumerator&lt;T&gt;&gt;<span style="color: #000000"> startContinuation;
    Action continuation;

    </span><span style="color: #0000ff">public</span><span style="color: #000000"> T Current
    {
        </span><span style="color: #0000ff">get</span><span style="color: #000000">
        {
            </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">this</span><span style="color: #000000">.current;
        }
    }

    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">bool</span><span style="color: #000000"> MoveNext()
    {
        </span><span style="color: #0000ff">this</span>.available=<span style="color: #0000ff">false</span><span style="color: #000000">;
        </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">this</span>.continuation==<span style="color: #0000ff">null</span><span style="color: #000000">)
        {
            </span><span style="color: #0000ff">this</span>.startContinuation(<span style="color: #0000ff">this</span><span style="color: #000000">);
        }
        </span><span style="color: #0000ff">else</span><span style="color: #000000">
        {
            </span><span style="color: #0000ff">this</span><span style="color: #000000">.continuation();
        }
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">this</span><span style="color: #000000">.available;
    }
}

</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">class</span><span style="color: #000000"> seq
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IEnumerable&lt;T&gt; CreateCps&lt;T&gt;(Action&lt;seq_Enumerator&lt;T&gt;&gt;<span style="color: #000000"> startContinuation)
    {
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">new</span><span style="color: #000000"> seq_Enumerable
        {
            startContinuation</span>=<span style="color: #000000">startContinuation
        };
    }

    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> <span style="color: #0000ff">yield</span>&lt;T&gt;(seq_Enumeartor&lt;T&gt;<span style="color: #000000"> state, T value, Action continuation)
    {
        state.current</span>=<span style="color: #000000">value;
        state.available</span>=<span style="color: #0000ff">true</span><span style="color: #000000">;
        state.continuation</span>=<span style="color: #000000">continuation;
    }

    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> exit&lt;T&gt;(seq_Enumeartor&lt;T&gt;<span style="color: #000000"> state)
    {
    }
}</span></pre></div>

<p>那么那个TakeWhile函数最终会变成： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> _TakeWhile&lt;T&gt;<span style="color: #000000">
{
    seq_Enumerator</span>&lt;T&gt;<span style="color: #000000"> _controller;
    Action _output_continuation_0= <span style="color: #0000ff">this</span><span style="color: #000000">.RunStateMachine</span>;
    </span><span style="color: #0000ff">int</span><span style="color: #000000"> _state;
    IEnumerable</span>&lt;T&gt;<span style="color: #000000"> _source;

    IEnumerator</span>&lt;T&gt;<span style="color: #000000"> _source_enumerator;
    Predicate</span>&lt;T&gt;<span style="color: #000000"> _predicate;
    T x;

    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span><span style="color: #000000"> RunStateMachine()
    {</span><span style="color: #000000">
        </span><span style="color: #0000ff">while</span>(<span style="color: #0000ff">true</span><span style="color: #000000">)
        {
            </span><span style="color: #0000ff">switch</span>(<span style="color: #0000ff">this</span><span style="color: #000000">.state)
            {
            </span><span style="color: #0000ff">case</span> <span style="color: #800080">0</span><span style="color: #000000">:
                {
                    </span><span style="color: #0000ff">this</span>._source_enumerator = <span style="color: #0000ff">this</span><span style="color: #000000">._source.CreateEnumerator();
                    </span><span style="color: #0000ff">this</span>._state=<span style="color: #800080">1</span><span style="color: #000000">;
                }
                </span><span style="color: #0000ff">break</span><span style="color: #000000">;
            </span><span style="color: #0000ff">case</span> <span style="color: #800080">1</span><span style="color: #000000">:
                {
                    </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">this</span><span style="color: #000000">._state_enumerator.MoveNext())
                    {
                        </span><span style="color: #0000ff">this</span>.x=<span style="color: #0000ff">this</span><span style="color: #000000">._state_enumerator.Current;
                        </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">this</span>._predicate(<span style="color: #0000ff">this</span><span style="color: #000000">.x))
                        {
                            </span><span style="color: #0000ff">this</span>._state=<span style="color: #800080">2</span><span style="color: #000000">;
                            </span><span style="color: #0000ff">var</span> input=<span style="color: #0000ff">this</span><span style="color: #000000">.x;
                            seq.</span><span style="color: #0000ff">yield</span>(<span style="color: #0000ff">this</span>._controller. input, <span style="color: #0000ff">this</span><span style="color: #000000">._output_continuation_0);
                            </span><span style="color: #0000ff">return</span><span style="color: #000000">;
                        }
                        </span><span style="color: #0000ff">else</span><span style="color: #000000">
                        {
                            seq.exit(</span><span style="color: #0000ff">this</span><span style="color: #000000">._controller);
                        }
                    }
                    </span><span style="color: #0000ff">else</span><span style="color: #000000">
                    {
                        state._state</span>=<span style="color: #800080">3</span><span style="color: #000000">;
                    }
                }
                </span><span style="color: #0000ff">break</span><span style="color: #000000">;
            </span><span style="color: #0000ff">case</span> <span style="color: #800080">2</span><span style="color: #000000">:
                {
                    </span><span style="color: #0000ff">this</span>.state=<span style="color: #800080">1</span><span style="color: #000000">;
                }
                </span><span style="color: #0000ff">break</span><span style="color: #000000">;
            </span><span style="color: #0000ff">case</span> <span style="color: #800080">3</span><span style="color: #000000">:
                {
                    seq.exit(</span><span style="color: #0000ff">this</span><span style="color: #000000">._controller);
                }
                </span><span style="color: #0000ff">break</span><span style="color: #000000">;
            }
        }
    }
}</span></pre></div>

<p>但是TakeWhile这个函数是真实存在的，所以他也要被改写： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>IEnumerable&lt;T&gt; TakeWhile(<span style="color: #0000ff">this</span> IEnumerable&lt;T&gt; source, Predicate&lt;T&gt;<span style="color: #000000"> predicate)
{
    </span><span style="color: #0000ff">return</span> seq.CreateCps(controller=&gt;<span style="color: #000000">
    {
        </span><span style="color: #0000ff">var</span> sm = <span style="color: #0000ff">new</span> _Where&lt;T&gt;<span style="color: #000000">
        {
            _controller</span>=<span style="color: #000000">controller,
            _source</span>=<span style="color: #000000">source,
            _predicate</span>=<span style="color: #000000">predicate,
        };

        sm.RunStateMachine();
    });
}</span></pre></div>

<p>最终生成的TakeWhile会调用哪个CreateCps函数，然后把原来的函数体经过CFG的处理之后，得到一个状态机。在状态机内所有调用CPS operator的地方（就是yield!和return!），都把“接下来的事情”当成一个参数，连同那个原本写上去的CPS operator的参数，还有controller（在这里是seq_Enumeartor&lt;T&gt;）一起传递过去。而return是带有特殊的寓意的，所以它调用一次exit之后，就没有“然后——也就是continuation”了。</p>
<p>现在回过头来看seq类型的声明</p>
<p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">class</span><span style="color: #000000"> seq
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> IEnumerator&lt;T&gt; CreateCps&lt;T&gt;(Action&lt;seq_Enumerator&lt;T&gt;&gt;<span style="color: #000000">);
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> <span style="color: #0000ff">yield</span>&lt;T&gt;(seq_Enumerator&lt;T&gt;<span style="color: #000000"> state, T value, Action continuation);
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> exit&lt;T&gt;(seq_Enumerator&lt;T&gt; state <span style="color: #008000">/*</span><span style="color: #008000">没有输入</span><span style="color: #008000">*/</span> <span style="color: #008000">/*</span><span style="color: #008000">exit代表return，函数结束的意思就是不会有一个continuation</span><span style="color: #008000">*/</span><span style="color: #000000">);
}</span></pre></div>

<p>其实想一想，CPS的自然属性决定了，基本上就只能这么定义它们的类型。而他们的类型唯一定义了一个最简单有效的函数体。再次感叹一下，<strong>写程序就跟在做推理完全是一摸一样的</strong>。</p>
<p><strong><font size="3">六、Koncept（我正在设计的语言）的yield return和async await（async答案）</font></strong></p>
<p>因为CPS operator都是一样的，所以在这里我给出async类型的声明，然后假设Task&lt;T&gt;的样子长的就跟C#的System.Tasks.Task&lt;T&gt;一摸一样，看看大家能不能得到async下面的几个函数的实现，以及上面那个针对Task&lt;T&gt;的TakeWhile函数最终会被编译成什么： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">class</span> <span style="color: #0000ff">async</span><span style="color: #000000">
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> Task&lt;T&gt; CreateCps&lt;T&gt;(Action&lt;FuturePromiseTask&lt;T&gt;&gt;<span style="color: #000000"> startContinuation);
    {
        </span><span style="color: #008000">/*</span><span style="color: #008000">请自行填补</span><span style="color: #008000">*/</span><span style="color: #000000">
    }

    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> <span style="color: #0000ff">await</span>&lt;T&gt;(FuturePromiseTask&lt;T&gt; task, Task&lt;T&gt; source, Action&lt;T&gt;<span style="color: #000000"> continuation);
    {
        </span><span style="color: #008000">/*</span><span style="color: #008000">请自行填补</span><span style="color: #008000">*/</span><span style="color: #000000">
    }

    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> exit&lt;T&gt;(FuturePromiseTask&lt;T&gt; task, T source); <span style="color: #008000">/*</span><span style="color: #008000">在这里async的return是有参数的，所以跟seq的exit不一样</span><span style="color: #008000">*/</span><span style="color: #000000">
    {
        </span><span style="color: #008000">/*</span><span style="color: #008000">请自行填补</span><span style="color: #008000">*/</span><span style="color: #000000">
    }
}

</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> FuturePromiseTask&lt;T&gt; : Task&lt;T&gt;<span style="color: #000000">
{
    </span><span style="color: #008000">/*</span><span style="color: #008000">请自行填补</span><span style="color: #008000">*/</span><span style="color: #000000">
}</span></pre></div><img src ="http://www.cppblog.com/vczh/aggbug/202154.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-07-27 11:12 <a href="http://www.cppblog.com/vczh/archive/2013/07/27/202154.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（七）&amp;mdash;&amp;mdash;闭包、lambda和interface</title><link>http://www.cppblog.com/vczh/archive/2013/07/05/201541.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 05 Jul 2013 14:31:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/07/05/201541.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/201541.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/07/05/201541.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/201541.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/201541.html</trackback:ping><description><![CDATA[<p>人们都很喜欢讨论闭包这个概念。其实这个概念对于写代码来讲一点用都没有，写代码只需要掌握好lambda表达式和class+interface的语义就行了。基本上只有在写编译器和虚拟机的时候才需要管什么是闭包。不过因为系列文章主题的缘故，在这里我就跟大家讲一下闭包是什么东西。在理解闭包之前，我们得先理解一些常见的argument passing和symbol resolving的规则。</p>
<p>首先第一个就是<strong>call by value</strong>了。这个规则我们大家都很熟悉，因为流行的语言都是这么做的。大家还记得刚开始学编程的时候，书上总是有一道题目，说的是： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">void</span> Swap(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span><span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">int</span> t =<span style="color: #000000"> a;
    a </span>=<span style="color: #000000"> b;
    b </span>=<span style="color: #000000"> t;
}

</span><span style="color: #0000ff">int</span><span style="color: #000000"> main()
{
    </span><span style="color: #0000ff">int</span> a=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">int</span> b=<span style="color: #800080">1</span><span style="color: #000000">;
    Swap(a, b);
    printf(</span><span style="color: #800000">"</span><span style="color: #800000">%d, %d</span><span style="color: #800000">"</span><span style="color: #000000">, a, b);
}</span></pre></div>
<p>&nbsp;</p>
<p>然后问程序会输出什么。当然我们现在都知道，a和b仍然是0和1，没有受到变化。这就是call by value。如果我们修改一下规则，让参数总是通过引用传递进来，因此Swap会导致main函数最后会输出1和0的话，那这个就是<strong>call by reference</strong>了。</p>
<p>除此之外，一个不太常见的例子就是<strong>call by need</strong>了。call by need这个东西在某些著名的实用的函数式语言（譬如Haskell）是一个重要的规则，说的就是如果一个参数没被用上，那传进去的时候就不会执行。听起来好像有点玄，我仍然用C语言来举个例子。 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> Add(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span><span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">return</span> a +<span style="color: #000000"> b;
}

</span><span style="color: #0000ff">int</span> Choose(<span style="color: #0000ff">bool</span> first, <span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span><span style="color: #000000"> b)
{
    </span><span style="color: #0000ff">return</span> first ?<span style="color: #000000"> a : b;
}

</span><span style="color: #0000ff">int</span><span style="color: #000000"> main()
{
    </span><span style="color: #0000ff">int</span> r = Choose(<span style="color: #0000ff">false</span>, Add(<span style="color: #800080">1</span>, <span style="color: #800080">2</span>), Add(<span style="color: #800080">3</span>, <span style="color: #800080">4</span><span style="color: #000000">));
    printf(</span><span style="color: #800000">"</span><span style="color: #800000">%d</span><span style="color: #800000">"</span><span style="color: #000000">, r);
}</span></pre></div>
<p>&nbsp;</p>
<p>这个程序Add会被调用多少次呢？大家都知道是两次。但是在Haskell里面这么写的话，就只会被调用一次。为什么呢？因为Choose的第一个参数是false，所以函数的返回值只依赖与b，而不依赖与a。所以在main函数里面它<strong>感觉到了</strong>这一点，于是只算Add(3, 4)，不算Add(1, 2)。不过大家别以为这是因为编译器优化的时候内联了这个函数才这么干的，Haskell的这个机制是在运行时起作用的。所以如果我们写了个快速排序的算法，然后把一个数组排序后只输出第一个数字，那么整个程序是<strong>O(n)时间复杂度</strong>的。因为快速排序的average case在把第一个元素确定下来的时候，只花了O(n)的时间。再加上整个程序只输出第一个数字，所以后面的他就不算了，于是整个程序也是O(n)。</p>
<p>于是大家知道call by name、call by reference和call by need了。现在来给大家讲一个<strong>call by name</strong>的神奇的规则。这个规则神奇到，我觉得根本没办法驾驭它来写出一个正确的程序。我来举个例子： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> Set(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span> b, <span style="color: #0000ff">int</span> c, <span style="color: #0000ff">int</span><span style="color: #000000"> d)
{
    a </span>+=<span style="color: #000000"> b;
    a </span>+=<span style="color: #000000"> c;
    a </span>+=<span style="color: #000000"> d;
}

</span><span style="color: #0000ff">int</span><span style="color: #000000"> main()
{
    </span><span style="color: #0000ff">int</span> i = <span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">int</span> x[<span style="color: #800080">3</span>] = {<span style="color: #800080">1</span>, <span style="color: #800080">2</span>, <span style="color: #800080">3</span><span style="color: #000000">};
    Set(x[i</span>++], <span style="color: #800080">10</span>, <span style="color: #800080">100</span>, <span style="color: #800080">1000</span><span style="color: #000000">);
    printf(</span><span style="color: #800000">"</span><span style="color: #800000">%d, %d, %d, %d</span><span style="color: #800000">"</span>, x[<span style="color: #800080">0</span>], x[<span style="color: #800080">1</span>], x[<span style="color: #800080">2</span><span style="color: #000000">], i);
}</span></pre></div>
<p>&nbsp;</p>
<p>学过C语言的都知道这个程序其实什么都没做。如果把C语言的call by value改成了call by reference的话，那么x和i的值分别是{1111, 2, 3}和1。但是我们知道，人类的想象力是很丰富的，于是发明了一种叫做call by name的规则。call by name也是call by reference的，但是区别在于你<strong>每一次使用</strong>一个参数的时候，程序都会把计算这个参数的表达式执行一遍。因此，如果把C语言的call by value换成call by name，那么上面的程序做的事情实际上就是： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>x[i++] += <span style="color: #800080">10</span><span style="color: #000000">;
x[i</span>++] += <span style="color: #800080">100</span><span style="color: #000000">;
x[i</span>++] += <span style="color: #800080">1000</span>;</pre></div>
<p>&nbsp;</p>
<p>程序执行完之后x和i的值就是{11, 102, 1003}和3了。</p>
<p>很神奇对吧，稍微不注意就会中招，是个大坑，基本没法用对吧。<strong>那你们还整天用C语言的宏来代替函数干什么呢</strong>。我依稀记得<strike>Ada</strike>（<span style="background-color: yellow">有网友指出这是Algol 60</span>）还是什么语言就是用这个规则的，印象比较模糊。</p>
<p>讲完了argument passing的事情，在理解lambda表达式之前，我们还需要知道两个流行的symbol resolving的规则。所谓的symbol resolving讲的就是解决程序在看到一个名字的时候，如何知道这个名字到底指向的是谁的问题。于是我又可以举一个简单粗暴的例子了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>Action&lt;<span style="color: #0000ff">int</span>&gt;<span style="color: #000000"> SetX()
{
    </span><span style="color: #0000ff">int</span> x = <span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">return</span> (<span style="color: #0000ff">int</span> n)=&gt;<span style="color: #000000">
    {
        x </span>=<span style="color: #000000"> n;
    };
}

</span><span style="color: #0000ff">void</span><span style="color: #000000"> Main()
{
    </span><span style="color: #0000ff">int</span> x = <span style="color: #800080">10</span><span style="color: #000000">;
    </span><span style="color: #0000ff">var</span> setX =<span style="color: #000000"> SetX();
    setX(</span><span style="color: #800080">20</span><span style="color: #000000">);
    Console.WriteLine(x);
}</span></pre></div>
<p>&nbsp;</p>
<p>弱智都知道这个程序其实什么都没做，就输出10。这是因为C#用的symbol resolving地方法是<strong>lexical scoping</strong>。对于SetX里面那个lambda表达式来讲，那个x是SetX的x而不是Main的x，因为lexical scoping的含义就是，在定义的地方向上查找名字。那为什么不能在运行的时候向上查找名字从而让SetX里面的lambda表达式实际上访问的是Main函数里面的x呢？其实是有人这么干的。这种做法叫<strong>dynamic scoping</strong>。我们知道，著名的javascript语言的eval函数，字符串参数里面的所有名字就是在运行的时候查找的。</p>
<p>=======================我是背景知识的分割线=======================</p>
<p>想必大家都觉得，如果一个语言的lambda表达式在定义和执行的时候采用的是lexical scoping和call by value那该有多好呀。流行的语言都是这么做的。就算规定到这么细，那还是有一个分歧。到底一个lambda表达式抓下来的外面的符号是只读的还是可读写的呢？python告诉我们，这是只读的。C#和javascript告诉我们，这是可读写的。C++告诉我们，你们自己来决定每一个符号的规则。作为一个对语言了解得很深刻，知道自己每一行代码到底在做什么，而且还很有自制力的程序员来说，我还是比较喜欢C#那种做法。因为其实C++就算你把一个值抓了下来，大部分情况下还是不能优化的，那何苦每个变量都要我自己说明我到底是想只读呢，还是要读写都可以呢？函数体我怎么用这个变量不是已经很清楚的表达出来了嘛。</p>
<p>那说到底闭包是什么呢？<strong>闭包</strong>其实就是那个被lambda表达式抓下来的<strong>&#8220;上下文&#8221;加上函数本身</strong>了。像上面的SetX函数里面的lambda表达式的闭包，就是x变量。一个语言有了带闭包的lambda表达式，意味着什么呢？我下面给大家展示一小段代码。现在要从动态类型的的lambda表达式开始讲，就凑合着用那个无聊的javascript吧： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">function</span><span style="color: #000000"> pair(a, b) {
    </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">function</span><span style="color: #000000">(c) {
        </span><span style="color: #0000ff">return</span><span style="color: #000000"> c(a, b);
    };
}

</span><span style="color: #0000ff">function</span><span style="color: #000000"> first(a, b) {
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> a;
}

</span><span style="color: #0000ff">function</span><span style="color: #000000"> second(a, b) {
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> b;
}

</span><span style="color: #0000ff">var</span> p = pair(1, pair(2, 3<span style="color: #000000">));
</span><span style="color: #0000ff">var</span> a =<span style="color: #000000"> p(first);
</span><span style="color: #0000ff">var</span> b =<span style="color: #000000"> p(second)(first);
</span><span style="color: #0000ff">var</span> c =<span style="color: #000000"> p(second)(second);
print(a, b, c);</span></pre></div>
<p>&nbsp;</p>
<p>这个程序的a、b和c到底是什么值呢？当然就算看不懂这个程序的人也可以很快猜出来他们是1、2和3了，因为变量名实在是定义的太清楚了。那么程序的运行过程到底是怎么样的呢？大家可以看到这个程序的任何一个值在创建之后都没有被第二次赋值过，于是这种程序就是没有副作用的，那就代表其实在这里call by value和call by need是没有区别的。call by need意味着函数的参数的求值顺序也是无所谓的。在这种情况下，程序就变得跟数学公式一样，可以推导了。那我们现在就来推导一下： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> p = pair(1, pair(2, 3<span style="color: #000000">));
</span><span style="color: #0000ff">var</span> a =<span style="color: #000000"> p(first);

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>

<span style="color: #0000ff">var</span> p = <span style="color: #0000ff">function</span><span style="color: #000000">(c) {
    </span><span style="color: #0000ff">return</span> c(1, pair(2, 3<span style="color: #000000">));
};
</span><span style="color: #0000ff">var</span> a =<span style="color: #000000"> p(first);

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>

<span style="color: #0000ff">var</span> a = first(1, pair(2, 3<span style="color: #000000">));

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>

<span style="color: #0000ff">var</span> a = 1;</pre></div>
<p>&nbsp;</p>
<p>这也算是个老掉牙的例子了啊。闭包在这里体现了他强大的作用，把参数保留了起来，我们可以在这之后进行访问。仿佛我们写的就是下面这样的代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> p =<span style="color: #000000"> {
    first </span>: 1<span style="color: #000000">,
    second </span>:<span style="color: #000000"> {
        first </span>: 1<span style="color: #000000">,
        second </span>: 2<span style="color: #000000">,
    }
};

</span><span style="color: #0000ff">var</span> a =<span style="color: #000000"> p.first;
</span><span style="color: #0000ff">var</span> b =<span style="color: #000000"> p.second.first;
</span><span style="color: #0000ff">var</span> c = p.second.second;</pre></div>
<p>&nbsp;</p>
<p>于是我们得到了一个结论，（带闭包的）lambda表达式可以代替一个成员为只读的struct了。那么，成员可以读写的struct要怎么做呢？做法当然跟上面的不一样。究其原因，就是因为javascript使用了call by value的规则，使得pair里面的return c(a, b);没办法将a和b的引用传递给c，这样就没有人可以修改a和b的值了。虽然a和b在那些c里面是改不了的，但是pair函数内部是可以修改的。如果我们要坚持只是用lambda表达式的话，就得要求c把修改后的所有&#8220;这个struct的成员变量&#8221;都拿出来。于是就有了下面的代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000">//</span><span style="color: #008000"> 在这里我们继续使用上面的pair、first和second函数</span>

<span style="color: #0000ff">function</span><span style="color: #000000"> mutable_pair(a, b) {
    </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">function</span><span style="color: #000000">(c) {
        </span><span style="color: #0000ff">var</span> x =<span style="color: #000000"> c(a, b);
        </span><span style="color: #008000">//</span><span style="color: #008000"> 这里我们把pair当链表用，一个(1, 2, 3)的链表会被储存为pair(1, pair(2, pair(3, null)))</span>
        a =<span style="color: #000000"> x(second)(first);
        b </span>=<span style="color: #000000"> x(second)(second)(first);
        </span><span style="color: #0000ff">return</span><span style="color: #000000"> x(first);
    };
}

</span><span style="color: #0000ff">function</span><span style="color: #000000"> get_first(a, b) {
    </span><span style="color: #0000ff">return</span> pair(a, pair(a, pair(b, <span style="color: #0000ff">null</span><span style="color: #000000">)));
}

</span><span style="color: #0000ff">function</span><span style="color: #000000"> get_second(a, b) {
    </span><span style="color: #0000ff">return</span> pair(b, pair(a, pair(b, <span style="color: #0000ff">null</span><span style="color: #000000">)));
}

</span><span style="color: #0000ff">function</span><span style="color: #000000"> set_first(value) {
    </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">function</span><span style="color: #000000">(a, b) {
        </span><span style="color: #0000ff">return</span> pair(<span style="color: #0000ff">undefined</span>, pair(value, pair(b, <span style="color: #0000ff">null</span><span style="color: #000000">)));
    };
}

</span><span style="color: #0000ff">function</span><span style="color: #000000"> set_second(value) {
    </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">function</span><span style="color: #000000">(a, b) {
        </span><span style="color: #0000ff">return</span> pair(<span style="color: #0000ff">undefined</span>, pair(a, pair(value, <span style="color: #0000ff">null</span><span style="color: #000000">)));
    };
}

</span><span style="color: #0000ff">var</span> p = mutable_pair(1, 2<span style="color: #000000">);
</span><span style="color: #0000ff">var</span> a =<span style="color: #000000"> p(get_first);
</span><span style="color: #0000ff">var</span> b =<span style="color: #000000"> p(get_second);
print(a, b);
p(set_first(</span>3<span style="color: #000000">));
p(set_second(</span>4<span style="color: #000000">));
</span><span style="color: #0000ff">var</span> c =<span style="color: #000000"> p(get_first);
</span><span style="color: #0000ff">var</span> d =<span style="color: #000000"> p(get_second);
print(c, d);</span></pre></div>
<p>&nbsp;</p>
<p>我们可以看到，因为get_first和get_second做了一个只读的事情，所以返回的链表的第二个值（代表新的a）和第三个值（代表新的b）都是旧的a和b。但是set_first和set_second就不一样了。因此在执行到第二个print的时候，我们可以看到p的两个值已经被更改成了3和4。</p>
<p>虽然这里已经涉及到了&#8220;绑定过的变量重新赋值&#8221;的事情，不过我们还是可以尝试推导一下，究竟p(set_first(3));的时候究竟干了什么事情： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> p = mutable_pair(1, 2<span style="color: #000000">);
p(set_first(</span>3<span style="color: #000000">));

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>
<span style="color: #000000">
p </span>= <span style="color: #0000ff">return</span> <span style="color: #0000ff">function</span><span style="color: #000000">(c) {
    </span><span style="color: #0000ff">var</span> x = c(1, 2<span style="color: #000000">);
    a </span>=<span style="color: #000000"> x(second)(first);
    b </span>=<span style="color: #000000"> x(second)(second)(first);
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> x(first);
};
p(set_first(</span>3<span style="color: #000000">));

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>

<span style="color: #0000ff">var</span> x = set_first(3)(1, 2<span style="color: #000000">);
p.a </span>=<span style="color: #000000"> x(second)(first); <span style="color: #008000">//</span><span style="color: #008000"> 这里的a和b是p的闭包内包含的上下文的变量了，所以这么写会清楚一点</span>
p.b </span>=<span style="color: #000000"> x(second)(second)(first);
</span><span style="color: #008000">//</span><span style="color: #008000"> return x(first);出来的值没人要，所以省略掉。</span></pre><pre><span style="color: #008000"></span><span style="color: #000000">
</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>

<span style="color: #0000ff">var</span> x = (<span style="color: #0000ff">function</span><span style="color: #000000">(a, b) {
    </span><span style="color: #0000ff">return</span> pair(undefined, pair(3, pair(b, <span style="color: #0000ff">null</span><span style="color: #000000">)));
})(</span>1, 2<span style="color: #000000">);
p.a </span>=<span style="color: #000000"> x(second)(first);
p.b </span>=<span style="color: #000000"> x(second)(second)(first);</span><span style="color: #000000">

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>
<span style="color: #000000">
x </span>= pair(undefined, pair(3, pair(2, <span style="color: #0000ff">null</span><span style="color: #000000">)));
p.a </span>=<span style="color: #000000"> x(second)(first);
p.b </span>=<span style="color: #000000"> x(second)(second)(first);</span><span style="color: #000000">

</span><span style="color: #008000">//</span><span style="color: #008000"> &#8595;&#8595;&#8595;&#8595;&#8595;</span>
<span style="color: #000000">
p.a </span>= 3<span style="color: #000000">;
p.b </span>= 2<span style="color: #000000">;</span></pre></div>
<p>&nbsp;</p>
<p>由于涉及到了上下文的修改，这个推导严格上来说已经不能叫推导了，只能叫解说了。不过我们可以发现，仅仅使用可以捕捉可读写的上下文的lambda表达式，已经可以实现可读写的struct的效果了。而且这个struct的读写是通过getter和setter来实现的，于是只要我们写的复杂一点，我们就得到了一个interface。于是那个mutable_pair，就可以看成是一个<strong>构造函数</strong>了。</p>
<p><font size="5"><strong>大括号不能换行的代码真他妈的难读啊，远远望去就像一坨屎！</strong><font size="2">go语言还把javascript自动补全分号的算法给抄去了，真是没品位。</font></font></p>
<p>所以，<strong>interface</strong>其实跟lambda表达是一样，<strong>也可以看成是一个闭包</strong>。只是interface的入口比较多，lambda表达式的入口只有一个（类似于C++的operator()）。大家可能会问，class是什么呢？class当然是interface内部不可告人的实现细节的。我们知道，<strong>依赖实现细节来编程是不对的，所以我们要依赖接口编程</strong>。</p>
<p>当然，即使是仓促设计出javascript的那个人，大概也是知道构造函数也是一个函数的，而且类的成员跟函数的上下文链表的节点对象其实没什么区别。于是我们会看到，javascript里面是这么做面向对象的事情的： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">function</span><span style="color: #000000"> rectangle(a, b) {
    </span><span style="color: #0000ff">this</span>.width =<span style="color: #000000"> a;
    </span><span style="color: #0000ff">this</span>.height =<span style="color: #000000"> height;
}

rectangle.prototype.get_area </span>= <span style="color: #0000ff">function</span><span style="color: #000000">() {
    </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">this</span>.width * <span style="color: #0000ff">this</span><span style="color: #000000">.height;
};

</span><span style="color: #0000ff">var</span> r = <span style="color: #0000ff">new</span> rectangle(3, 4<span style="color: #000000">);
print(r.get_area());</span></pre></div>
<p>&nbsp;</p>
<p>然后我们就拿到了一个3&#215;4的长方形的面积12了。不过javascript给我们带来的一点点小困惑是，函数的this参数其实是dynamic scoping的，也就是说，这个this到底是什么，要看你在哪如何调用这个函数。于是其实 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>obj.method(args)</pre></div>
<p>&nbsp;</p>
<p>整个东西是一个语法，它代表method的this参数是obj，剩下的参数是args。可惜的是，这个语法并不是由&#8220;obj.member&#8221;和&#8220;func(args)&#8221;组成的。那么在上面的例子中，如果我们把代码改为： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> x =<span style="color: #000000"> r.get_area;
print(x());</span></pre></div>
<p>&nbsp;</p>
<p>结果是什么呢？反正不是12。如果你在C#里面做这个事情，效果就跟javascript不一样了。如果我们有下面的代码： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">class Rectangle
{
    public </span><span style="color: #0000ff">int</span><span style="color: #000000"> width;
    public </span><span style="color: #0000ff">int</span><span style="color: #000000"> height;

    public </span><span style="color: #0000ff">int</span><span style="color: #000000"> GetArea()
    {
        </span><span style="color: #0000ff">return</span> width *<span style="color: #000000"> height;
    }
};</span></pre></div>
<p>&nbsp;</p>
<p>那么下面两段代码的意思是一样的： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> r = <span style="color: #0000ff">new</span><span style="color: #000000"> Rectangle
{
    width </span>= 3<span style="color: #000000">;
    height </span>= 4<span style="color: #000000">;
};

</span><span style="color: #008000">//</span><span style="color: #008000"> 第一段代码</span>
<span style="color: #000000">Console.WriteLine(r.GetArea());

</span><span style="color: #008000">//</span><span style="color: #008000"> 第二段代码</span>
Func&lt;<span style="color: #0000ff">int</span>&gt; x =<span style="color: #000000"> r.GetArea;
Console.WriteLine(x());</span></pre></div>
<p>&nbsp;</p>
<p>究其原因，是因为javascript把obj.method(a, b)解释成了GetMember(obj, &#8220;method&#8221;).Invoke(a, b, this = r);了。所以你做r.get_area的时候，你拿到的其实是定义在rectangle.prototype里面的那个东西。但是C#做的事情不一样，C#的第二段代码其实相当于： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>Func&lt;<span style="color: #0000ff">int</span>&gt; x = ()=&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> r.GetArea();
};
Console.WriteLine(x());</span></pre></div>
<p>&nbsp;</p>
<p>所以说C#这个做法比较符合直觉啊，为什么dynamic scoping（譬如javascript的this参数）和call by name（譬如C语言的宏）看起来都那么屌丝，总是让人掉坑里，就是因为违反了直觉。不过javascript那么做还是情有可原的。估计第一次设计这个东西的时候，收到了静态类型语言太多的影响，于是把obj.method(args)整个当成了一个整体来看。因为在C++里面，this的确就是一个参数，只是她不能让你obj.method，得写&amp;TObj::method，然后还有一个专门填this参数的语法&#8212;&#8212;没错，就是.*和-&gt;*操作符了。</p>
<p>假如说，javascript的this参数要做成lexical scoping，而不是dynamic scoping，那么能不能用lambda表达式来模拟interface呢？这当然是可以，只是如果不用prototype的话，那我们就会丧失javascript爱好者们千方百计绞尽脑汁用尽奇技淫巧锁模拟出来的&#8220;继承&#8221;效果了： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">function</span><span style="color: #000000"> mutable_pair(a, b) {
    _this </span>=<span style="color: #000000"> {
        get_first </span>= <span style="color: #0000ff">function</span>() { <span style="color: #0000ff">return</span><span style="color: #000000"> a; },
        get_second </span>= <span style="color: #0000ff">function</span>() { <span style="color: #0000ff">return</span><span style="color: #000000"> b; },
        set_first </span>= <span style="color: #0000ff">function</span>(value) { a =<span style="color: #000000"> value; },
        set_second </span>= <span style="color: #0000ff">function</span>(value) { b =<span style="color: #000000"> value; }
    };<br /></span><span style="color: #0000ff">    return</span><span style="color: #000000"> _this; </span><span style="color: #000000">
}

</span><span style="color: #0000ff">var</span> p = <span style="color: #0000ff">new </span>mutable_pair(1, 2<span style="color: #000000">);
</span><span style="color: #0000ff">var</span> a =<span style="color: #000000"> p.get_first();
</span><span style="color: #0000ff">var</span> b =<span style="color: #000000"> p.get_second();
print(a, b);
</span><span style="color: #0000ff">var</span> c = p.set_first(3<span style="color: #000000">);
</span><span style="color: #0000ff">var</span> d = p.set_second(4<span style="color: #000000">);
print(c, d);</span></pre></div>
<p>&nbsp;</p>
<p>这个时候，即使你写 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> x =<span style="color: #000000"> p.set_first;
</span><span style="color: #0000ff">var</span> y =<span style="color: #000000"> p.set_second;
x(</span>3<span style="color: #000000">);
y(</span>4);</pre></div>
<p>&nbsp;</p>
<p>代码也会跟我们所期望的一样正常工作了。而且创造出来的r，所有的成员变量都屏蔽掉了，只留下了几个函数给你。与此同时，函数里面访问_this也会得到创建出来的那个interface了。</p>
<p>大家到这里大概已经明白闭包、lambda表达式和interface之间的关系了吧。我看了一下之前写过的六篇文章，加上今天这篇，内容已经覆盖了有：</p>
<ol><li>阅读C语言的复杂的声明语法</li><li>什么是语法噪音</li><li>什么是语法的一致性</li><li>C++的const的意思</li><li>C#的struct和property的问题</li><li>C++的多重继承</li><li>封装到底意味着什么</li><li>为什么exception要比error code写起来干净、容易维护而且不需要太多的沟通</li><li>为什么C#的有些interface应该表达为concept</li><li>模板和模板元编程</li><li>协变和逆变</li><li>type rich programming</li><li>OO的消息发送的含义</li><li>虚函数表是如何实现的</li><li>什么是OO里面的类型扩展开放/封闭与逻辑扩展开放/封闭</li><li>visitor模式如何逆转类型和逻辑的扩展和封闭</li><li>CPS（continuation passing style）变换与异步调用的异常处理的关系</li><li>CPS如何让exception变成error code</li><li>argument passing和symbol resolving</li><li>如何用lambda实现mutable struct和immutable struct</li><li>如何用lambda实现interface</li></ol>
<p>想了想，大概通俗易懂的可以自学成才的那些东西大概都讲完了。当然，系列是不会在这里就结束的，只是后面的东西，大概就需要大家多一点思考了。</p>
<p>写程序讲究行云流水。只有自己勤于思考，勤于做实验，勤于造轮子，才能让编程的学习事半功倍。</p><img src ="http://www.cppblog.com/vczh/aggbug/201541.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-07-05 22:31 <a href="http://www.cppblog.com/vczh/archive/2013/07/05/201541.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（六）&amp;mdash;&amp;mdash;exception和error code</title><link>http://www.cppblog.com/vczh/archive/2013/06/10/200920.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 10 Jun 2013 07:01:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/06/10/200920.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/200920.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/06/10/200920.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/200920.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/200920.html</trackback:ping><description><![CDATA[<p><font size="2" face="微软雅黑">我一直以来对于exception的态度都是很明确的。首先exception是好的，否则就不会有绝大多数的语言都支持他了。其次，error code也没什么问题，只是需要一个前提——你的语言得跟Haskell一样有monad和comonad。你看Haskell就没有exception，大家也写的很开心。为什么呢？因为只要把返回带error code结果的函数给做成一个monad/comonad，那么就可以用CPS变换把它变成exception了。所以说CPS作为跟goto同样基本的控制流语句真是当之无愧呀，只是CPS是type rich的，goto是type poor的。</font></p> <p><font size="2" face="微软雅黑">其实很多人对于exception的恐惧心理在于你不知道一个函数会抛什么exception出来，然后程序一crash你就傻逼了。对于server来讲情况还好，出了问题只要杀掉快速重启就行了，如今没个replication和fault tolerance还有脸说你在写后端（所以不知道那些做web的人究竟在反对什么）？这主要的问题还是在于client。只要client上面的东西还没保存，那你一crash数据就完蛋了是不是——当然这只是你的想象啦，其实根本不是这样子的。</font></p> <p><font size="2" face="微软雅黑">我们的程序抛了一个access violation出来，和抛了其它exception出来，究竟有什么区别呢？access violation是一个很奇妙的东西，一旦抛了出来就告诉你你的程序没救了，继续执行下去说不定还会有破坏作用。特别是对于C/C++/Delphi这类语言来说，你不小心把错误的东西写进了什么乱七八糟的指针里面去，那会儿什么事情都没发生，结果程序跑着跑着就错了。因为你那个算错了得到的野指针，说不定是隔壁的不知道什么object的成员变量，说不定是heap里面的数据结构，或者说别的什么东西，就这么给你写了。如果你写了别的object的成员变量那封装肯定就不管用了，这个类的不变量就给你破坏了。既然你的成员函数都是基于不变量来写的，那这个时候出错时必须的。如果你写到了heap的数据结构那就更加呵呵呵了，说不定下次一new就崩了，而且你还不知道为什么。</font></p> <p><font size="2" face="微软雅黑">出了access violation以外的exception基本是没什么危害的，最严重的大概也就是网线被拔了，另一块不是装OS的硬盘突然坏了什么的这种反正你也没办法但是好歹还可以处理的事情。如果这些exception是你自己抛出来的那就更可靠了——那都是计划内的。只要程序<strong>未来不会进入access violation的状态</strong>，那证明你<strong>现在所能拿到的所有变量</strong>，还有指针指向的memory，基本上都还是靠谱的。出了你救不了的错误，至少你还可以吧数据安全的保存下来，然后让自己重启——就跟word一样。但是你有可能会说，拿出了access violation怎么就不能保存数据了呢？因为这个时候内存都毁了，指不定你保存数据的代码new点东西然后挂了，这基本上是没准的。</font></p> <p><font size="2" face="微软雅黑">所以无论你喜欢exception还是喜欢error code，你所希望达到的效果本质上就是<strong>避免程序未来会进入access violation的状态</strong>。想做到这一点，方法也是很简单粗暴的——只要你在函数里面把运行前该对函数做的检查都查一遍就好了。这个无论你用exception还是用error code，写起来都是一样的。区别在于调用你的函数的那个人会怎么样。那么我来举个例子，譬如说你觉得STL的map实在是太傻比了，于是你自己写了一个，然后有了一个这样子的函数：</font> <div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #008000">//</span><span style="color: #008000"> exception版本</span>
Symbol* SymbolMap::Lookup(<span style="color: #0000ff">const</span> wstring&amp;</font><font size="2"><span style="color: #000000"> name);

</span><span style="color: #008000">//</span><span style="color: #008000"> error code版本</span>
<span style="color: #0000ff">int</span> SymbolMap::Lookup(<span style="color: #0000ff">const</span> wstring&amp; name, Symbol*&amp;</font><font size="2"><span style="color: #000000"> result);

</span><span style="color: #008000">//</span><span style="color: #008000"> 其实COM就是你们最喜欢的error code风格了，写起来应该很开心才对呀，你们的双重标准真严重</span>
HRESULT ISymbolMap::Lookup(BSTR name, ISymbol** result);</font></pre></div></p>
<p><font size="2" face="微软雅黑">于是拿到了Lookup函数之后，我们就要开始来完成一个任务了，譬如说拿两个key得到两个symbol然后组合出一个新的symbol。函数的错误处理逻辑是这样的，如果key失败了，因为业务的原因，我们要告诉函数外面说key不存在的。调用了一个ComposeSymbol的函数丢出什么IndexOutOfRangeException显然是不合理的。但是合并的那一步，因为业务都在同一个领域内，所以suppose里面的异常外面是可以接受的。如果出现了计划外的异常，那我们是处理不了的，只能丢给上面了，外面的代码对于不认识的异常只需要报告任务失败了就可以了。于是我们的函数就会这么写：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2">Symbol* ComposeSymbol(<span style="color: #0000ff">const</span> wstring&amp; a, <span style="color: #0000ff">const</span> wstring&amp; b, SymbolMap*</font><font size="2"><span style="color: #000000"> map)
{
    Symbol</span>* sa=<span style="color: #800080">0</span></font><font size="2"><span style="color: #000000">;
    Symbol</span>* sb=<span style="color: #800080">0</span></font><font size="2"><span style="color: #000000">;
    </span><span style="color: #0000ff">try</span></font><span style="color: #000000">
<font size="2">    {
        sa</font></span><font size="2">=map-&gt;</font><font size="2"><span style="color: #000000">Lookup(a);
        sa</span>=map-&gt;</font><font size="2"><span style="color: #000000">Lookup(b);
    }
    </span><span style="color: #0000ff">catch</span>(<span style="color: #0000ff">const</span> IndexOutOfRangeException&amp;</font><font size="2"><span style="color: #000000"> ex)
    {
        </span><span style="color: #0000ff">throw</span></font><font size="2"><span style="color: #000000"> SymbolKeyException(ex.GetIndex());
    }
    </span><span style="color: #0000ff">return</span></font><span style="color: #000000"><font size="2"> CreatePairSymbol(sa, sb);
}</font></span></pre></div></p>
<p><font size="2" face="微软雅黑">看起来还挺不错。现在我们可以开始考虑error code的版本了。于是我们需要思考几个问题。首先第一个就是Lookup失败的时候要怎么报告？直接报告key的内容是不可能的，因为error code是个int。</font></p>
<p><font size="2" face="微软雅黑">题外话，error code当然可以是别的什么东西，如果需要返回丰富内容的错误的话，那怎样都得是一个指针了，这个时候你们就会面临下面的问题——这已经他妈不满足谁构造谁释放的原则了呀，而且我这个指针究竟直接返回出去外面理不理呢，如果只要有一个环节不理了，那内存岂不是泄露了？如果我要求把错误返回在参数里面的话，我每次调用函数都要创建出那么个结构来保存异常，不仅有if的复杂度，还有创建空间的复杂度，整个代码都变成了屎。所以还是老老实实用int吧……</font></p>
<p><font size="2" face="微软雅黑">那我们要如何把key的信息给编码在一个int里面呢？因为key要么是来自于a，要么是来自于b，所以其实我们就需要两个code了。那Lookup的其他错误怎么办呢？CreatePairSymbol的错误怎么办呢？万一Lookup除了ERROR_KEY_NOT_FOUND以外，或者是CreatePairSymbol的错误刚好跟a或者b的code重合了怎么办？对于这个问题，我只能说：</font></p>
<p><font size="2" face="微软雅黑">要不你们team的人先<strong><font size="5">开会讨论一下</font></strong>最后记录在文档里面备查以免后面的人看了傻眼了……</font></p>
<p><font size="2" face="微软雅黑">好了，现在假设说会议取得了圆满成功，会议双方加深了互相的理解，促进了沟通，最后还写了一个白皮书出来，有效的落实了对a和b的code的指导，于是我们终于可以写出下面的代码了：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">#define</span> SUCCESS 0 <span style="color: #008000">//</span><span style="color: #008000"> global error code for success</span>
<span style="color: #0000ff">#define</span> ERROR_COMPOSE_SYMBOL_WRONG_A 1
<span style="color: #0000ff">#define</span> ERROR_COMPOSE_SYMBOL_WRONG_B 2

<span style="color: #0000ff">int</span> ComposeSymbol(<span style="color: #0000ff">const</span> wstring&amp; a, <span style="color: #0000ff">const</span> wstring&amp; b, SymbolMap* map, Symbol*&amp;</font><font size="2"><span style="color: #000000"> result)
{
    </span><span style="color: #0000ff">int</span> code=</font><font size="2"><span style="color: #000000">SUCCESS;
    Symbol</span>* sa=<span style="color: #800080">0</span></font><font size="2"><span style="color: #000000">;
    Symbol</span>* sb=<span style="color: #800080">0</span></font><font size="2"><span style="color: #000000">;
    </span><span style="color: #0000ff">switch</span>(code=map-&gt;</font><font size="2"><span style="color: #000000">Lookup(a, sa))
    {
    </span><span style="color: #0000ff">case</span></font><font size="2"><span style="color: #000000"> SUCCESS:
        </span><span style="color: #0000ff">break</span></font><font size="2"><span style="color: #000000">;
    </span><span style="color: #0000ff">case</span></font><font size="2"><span style="color: #000000"> ERROR_SYMBOL_MAP_KEY_NOT_FOUND:
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> ERROR_COMPOSE_SYMBOL_WRONG_A;
    </span><span style="color: #0000ff">default</span></font><font size="2"><span style="color: #000000">:
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> code;
    }
    </span><span style="color: #0000ff">switch</span>(code=map-&gt;</font><font size="2"><span style="color: #000000">Lookup(b, sb))
    {
    </span><span style="color: #0000ff">case</span></font><font size="2"><span style="color: #000000"> SUCCESS:
        </span><span style="color: #0000ff">break</span></font><font size="2"><span style="color: #000000">;
    </span><span style="color: #0000ff">case</span></font><font size="2"><span style="color: #000000"> ERROR_SYMBOL_MAP_KEY_NOT_FOUND:
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> ERROR_COMPOSE_SYMBOL_WRONG_B;
    </span><span style="color: #0000ff">default</span></font><font size="2"><span style="color: #000000">:
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> code;
    }
    </span><span style="color: #0000ff">return</span></font><span style="color: #000000"><font size="2"> CreatePairSymbol(sa, sb, result);
}</font></span></pre></div></p>
<p><font size="2" face="微软雅黑">啊，好像太长，干脆我还是不负责任一点吧，反正代码写的好也涨不了工资，干脆不认识的错误都返回ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR好了，于是就可以把代码变成下面这样……都到这份上了不要叫自己程序员了，叫<strong>程序狗</strong>吧……</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">#define</span> SUCCESS 0 <span style="color: #008000">//</span><span style="color: #008000"> global error code for success</span>
<span style="color: #0000ff">#define</span> ERROR_COMPOSE_SYMBOL_WRONG_A 1
<span style="color: #0000ff">#define</span> ERROR_COMPOSE_SYMBOL_WRONG_B 2
<span style="color: #0000ff">#define</span> ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR 3

<span style="color: #0000ff">int</span> ComposeSymbol(<span style="color: #0000ff">const</span> wstring&amp; a, <span style="color: #0000ff">const</span> wstring&amp; b, SymbolMap* map, Symbol*&amp;</font><font size="2"><span style="color: #000000"> result)
{
    Symbol</span>* sa=<span style="color: #800080">0</span></font><font size="2"><span style="color: #000000">;
    Symbol</span>* sb=<span style="color: #800080">0</span></font><font size="2"><span style="color: #000000">;
    </span><span style="color: #0000ff">if</span>(map-&gt;Lookup(a, sa)!=</font><font size="2"><span style="color: #000000">SUCCESS)
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
    </span><span style="color: #0000ff">if</span>(map-&gt;Lookup(b, sb)!=</font><font size="2"><span style="color: #000000">SUCCESS)
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
    </span><span style="color: #0000ff">if</span>(CreatePairSymbol(sa, sb, result)!=</font><font size="2"><span style="color: #000000">SUCCESS)
        </span><span style="color: #0000ff">return</span></font><font size="2"><span style="color: #000000"> ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
    </span><span style="color: #0000ff">return</span></font><span style="color: #000000"><font size="2"> SUCCESS;
}</font></span></pre></div></p>
<p><font face="微软雅黑"><font size="2">当然，如果大家都一样不负责任的话，还是exception完爆error code：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2">Symbol* ComposeSymbol(<span style="color: #0000ff">const</span> wstring&amp; a, <span style="color: #0000ff">const</span> wstring&amp; b, SymbolMap*</font><font size="2"><span style="color: #000000"> map)
{
    </span><span style="color: #0000ff">return</span> CreatePairSymbol(map-&gt;Lookup(a), map-&gt;</font><span style="color: #000000"><font size="2">Lookup(b));
}</font></span></pre></div></font></p>
<p><font size="2" face="微软雅黑">大部分人人只会用在当前条件下最容易写的方法来设计软件，而不是先设计出软件然后再看看怎样写比较容易，这就是为什么我说，只要你一个月给程序员还给不到一狗半，还是老老实实在政策上落实exception吧。至少exception写起来还不会让人那么心烦，可以把程序写得坚固一点。</font></p>
<p><font face="微软雅黑"><font size="2">好了，单线程下面至少你还可以争吵说究竟exception好还是error code好，但是到了异步程序里面就完全不一样了。现在的异步程序都很多，譬如说有良心的手机app啦，譬如说javascript啦，metro程序等等。一个try根本没办法跨线程使用所以一个这样子的函数（下面开始用C#，C++11的future/promise我用的还不熟）：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff"><font size="2">class</font></span><font size="2"><span style="color: #000000"> Normal
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> Do(<span style="color: #0000ff">string</span></font><span style="color: #000000"><font size="2"> args);
}</font></span></pre></div></font></p>
<p><font face="微软雅黑"><font size="2">最后就会变成这样：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff"><font size="2">class</font></span><font size="2"><span style="color: #000000"> Async
{
    </span><span style="color: #008000">//</span><span style="color: #008000"> before .NET 4.0</span>
    IAsyncResult BeginDo(<span style="color: #0000ff">string</span> args, Action&lt;IAsyncResult&gt;</font><font size="2"><span style="color: #000000"> continuation);
    </span><span style="color: #0000ff">string</span></font><font size="2"><span style="color: #000000"> EndDo(IAsyncResult ar);

    </span><span style="color: #008000">//</span><span style="color: #008000"> after .NET 4.0</span>
    Task&lt;<span style="color: #0000ff">string</span>&gt; DoAsync(<span style="color: #0000ff">string</span></font><span style="color: #000000"><font size="2"> args);
}</font></span></pre></div></font></p>
<p><font size="2" face="微软雅黑">当你使用BeginDo的时候，你可以在continuation里面调用EndDo，然后得到一个string，或者得到一个exception。但是因为EndDo的exception不是在BeginDo里面throw出来的，所以无论你EndDo返回string也好，返回Tuple&lt;string, Exception&gt;也好，对于BeginDo和EndDo的实现来说其实都一样，没有上文所说的exception和error code的区别。</font></p>
<p><font face="微软雅黑"><font size="2">不过.NET从BeginDo/EndDo到DoAsync经历了一个巨大的进步。虽然形式上都一样，但是由于C#并不像Haskell那样可以完美的操作函数，C#还是面向对象做得更好，于是如果我们吧Task&lt;T&gt;看成下面的样子，那其实两种写法是没有区别的：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">class</span> Task&lt;T&gt;</font><span style="color: #000000">
<font size="2">{
    </font></span><font size="2"><span style="color: #0000ff">public</span> IAsyncResult BeginRun(Action&lt;IAsyncResult&gt;</font><font size="2"><span style="color: #000000"> continuation);
    </span><span style="color: #0000ff">public</span></font><span style="color: #000000"><font size="2"> T EndRun(IAsyncResult ar);
}</font></span></pre></div></font></p>
<p><font style="" face="微软雅黑"><font style=""><font size="2">不过如果还是用BeginRun/EndRun这种方法来调用的话，使用起来还是很不方便，而且也很难把更多的Task组合在一起。所以最后.NET给出的Task是下面这个样子的（Comonad！）：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">class</span> Task&lt;T&gt;</font><span style="color: #000000">
<font size="2">{
    </font></span><font size="2"><span style="color: #0000ff">public</span> Task&lt;U&gt; ContinueWith&lt;U&gt;(Func&lt;Task&lt;T&gt;, U&gt;</font><span style="color: #000000"><font size="2"> continuation);
}</font></span></pre></div></font></font></p>
<p><font style="" face="微软雅黑"><font style="" size="2">尽管真实的Task&lt;T&gt;要比上面那个复杂得多，但是总的来说其实就是围绕着基本简单的函数建立起来的一大堆helper function。到这里C#终于把CPS变换在异步处理上的应用的这一部分给抽象出来了。在看CPS的效果之前，我们先来看一个同步函数：</font></font><font style="background-color: #f5f5f5" face="微软雅黑"><font style="background-color: #ffffff" size="2"></font></p>
<p style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">void</span> button1_Clicked(<span style="color: #0000ff">object</span></font><font size="2"><span style="color: #000000"> sender, EventArgs e)
{
        </span><span style="color: #008000">//</span><span style="color: #008000"> 假设我们有string Http.Download(string url);</span>
        <span style="color: #0000ff">try</span></font><span style="color: #000000">
<font size="2">        {
                </font></span><font size="2"><span style="color: #0000ff">string</span> a =</font><font size="2"><span style="color: #000000"> Http.Download(url1);
                </span><span style="color: #0000ff">string</span> b =</font><font size="2"><span style="color: #000000"> Http.Download(url2);
                textBox1.Text</span>=a+</font><font size="2"><span style="color: #000000">b;
        }
        </span><span style="color: #0000ff">catch</span></font><font size="2"><span style="color: #000000">(Exception ex)
        {
                textBox1.Text</span>=</font><span style="color: #000000"><font size="2">ex.Message;
        }
}</font></span></pre></p></font>
<p><font style="background-color: #f5f5f5" size="2" face="微软雅黑"></font><font style="" face="微软雅黑"><font size="2">这段代码显然是一个GUI里面的代码。我们如果在一个GUI程序里面这么写，就会把程序写得跟QQ一样卡了。所以实际上这么做是不对的。不过为了表达程序需要做的所有事情，就有了这么一个同步的版本。那么我们尝试吧这个东西修改成异步的把！</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">void</span> button2_Clicked(<span style="color: #0000ff">object</span></font><font size="2"><span style="color: #000000"> sender, EventArgs e)
{
    </span><span style="color: #008000">//</span></font><font size="2"><span style="color: #008000"> 假设我们有Task&lt;string&gt; Http.DownloadAsync(string url);
    </span><span style="color: #008000">//</span><span style="color: #008000"> 需要MethodInvoker是因为，对textBox1.Text的修改只能在GUI线程里面做</span>
    Http.DownloadAsync(url1).ContinueWith(ta=&gt;<span style="color: #0000ff">new</span> MethodInvoker(()=&gt;</font><span style="color: #000000">
<font size="2">    {
        </font></span><span style="color: #0000ff"><font size="2">try</font></span><span style="color: #000000">
<font size="2">        {
            </font></span><span style="color: #008000"><font size="2">//</font></span><font size="2"><span style="color: #008000"> 这个时候ta已经运行完了，所以对ta.Result的取值不会造成GUI线程等待IO。
            </span><span style="color: #008000">//</span><span style="color: #008000"> 而且如果DownloadAsync内部出了错，异常会在这里抛出来。</span>
            <span style="color: #0000ff">string</span> a=</font><font size="2"><span style="color: #000000">ta.Result;
            Http.DownloadAsync(url2).ContinueWith(tb</span>=&gt;<span style="color: #0000ff">new</span> MethodInvoker(()=&gt;</font><span style="color: #000000">
<font size="2">            {
                </font></span><span style="color: #0000ff"><font size="2">try</font></span><span style="color: #000000">
<font size="2">                {
                    </font></span><font size="2"><span style="color: #0000ff">string</span> b=</font><font size="2"><span style="color: #000000">tb.Result;
                    textBox1.Text</span>=a+</font><font size="2"><span style="color: #000000">b;
                }
<strong>                </strong></span><span style="color: #0000ff"><strong><font style="background-color: #ffff00">catch</font></strong></span></font><font size="2"><strong><span style="color: #000000"><font style="background-color: #ffff00">(Exception ex)
</font>                <font style="background-color: #ffff00">{</font>
                <font style="background-color: #ffff00">    textBox1.Text</font></span><font style="background-color: #ffff00">=</font></strong></font><font size="2"><span style="color: #000000"><strong><font style="background-color: #ffff00">ex.Message;
</font>                <font style="background-color: #ffff00">}</font></strong>
            })));
        }
<strong>        </strong></span><span style="color: #0000ff"><strong><font style="background-color: #ffff00">catch</font></strong></span></font><font size="2"><strong><span style="color: #000000"><font style="background-color: #ffff00">(Exception ex)
</font>        <font style="background-color: #ffff00">{</font>
        <font style="background-color: #ffff00">    textBox1.Text</font></span><font style="background-color: #ffff00">=</font></strong></font><span style="color: #000000"><font size="2"><strong><font style="background-color: #ffff00">ex.Message;
</font>        <font style="background-color: #ffff00">}</font></strong>
    })));
}</font></span></pre></div></font></p>
<p><font size="2" face="微软雅黑">我们发现，异步操作发生的异常，把优越的exception拉低到了丑陋的error code的同一个情况上面——我们需要不断地对每一个操作重复同样的错误处理过程！而且在这种地方我们连“不负责任”的选项都没有了，如果你不try-catch（或者不检查error code），那到时候程序就会发生一些莫名其妙的问题，在GUI那一层你什么事情都不知道，整个程序就变成了傻逼。</font></p>
<p><font size="2" face="微软雅黑">现在可以开始解释一下什么是CPS变换了。CPS变换就是把所有g(f(x))都给改写成f(x, r=&gt;g(r))的过程。通俗一点讲，CPS变换就是帮你把那个同步的button1_Click给改写成异步的button2_Click的这个过程。尽管这么说可能不太严谨，因为button1_Click跟button2_Click所做的事情是不一样的，一个会让GUI卡成qq，另一个不会。但是我们讨论CPS变换的时候，我们讨论的是对代码结构的变换，而不是别的什么东西。</font></p>
<p><font size="2" face="微软雅黑">现在就是激动人心的一步了。既然CPS可以把返回值变换成lambda表达式，那反过来我们也可以把所有的<strong>以这种形式存在的</strong>lambda表达式都改写成返回值嘛。现在我们滚回去看一看button2_Click，会发现这个程序其实充满了下面的pattern：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #008000">//</span><span style="color: #008000"> lambda的参数名字故意起了跟前面的变量一样的名字（previousTask）因为其实他们就是同一个东西</span>
previousTask.ContinueWith(previousTask=&gt;<span style="color: #0000ff">new</span> MethodInvoker(()=&gt;</font><span style="color: #000000">
<font size="2">{
    </font></span><span style="color: #0000ff"><font size="2">try</font></span><span style="color: #000000">
<font size="2">    {
        continuation(previousTask.Result);
    }
    </font></span><span style="color: #0000ff"><font size="2">catch</font></span><font size="2"><span style="color: #000000">(Exception ex)
    {
        textBox1.Text</span>=</font><span style="color: #000000"><font size="2">ex.Message;
    }
})));</font></span></pre></div></p>
<p><font face="微软雅黑"><font size="2">我们可以“发明”一个语法来代表这个过程。C#用的是await关键字，那我们也来用await关键字。假设说上面的代码永远等价于下面的这个代码：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff"><font size="2">try</font></span><span style="color: #000000">
<font size="2">{
    </font></span><font size="2"><span style="color: #0000ff">var</span> result=<span style="color: #0000ff">await</span></font><font size="2"><span style="color: #000000"> previousTask;
    continuation(result);
}
</span><span style="color: #0000ff">catch</span></font><font size="2"><span style="color: #000000">(Exception ex)
{
    textBox1.Text</span>=</font><span style="color: #000000"><font size="2">ex.Message;
}</font></span></pre></div></font></p>
<p><font face="微软雅黑"><font size="2">两段代码的关系就跟i++;和i=i+1;一样是可以互相替换的，只是不同的写法而已。那我们就可以用相同的方法来把button2_Click给替换成下面的button3_Click了：</font>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font size="2"><span style="color: #0000ff">void</span> button3_Click(<span style="color: #0000ff">object</span></font><font size="2"><span style="color: #000000"> sender, EventArgs e)
{
    </span><span style="color: #0000ff">try</span></font><span style="color: #000000">
<font size="2">    {
        </font></span><font size="2"><span style="color: #0000ff">var</span> a=<span style="color: #0000ff">await</span></font><font size="2"><span style="color: #000000"> Http.DownloadAsync(url1);
        </span><span style="color: #0000ff">try</span></font><span style="color: #000000">
<font size="2">        {
            </font></span><font size="2"><span style="color: #0000ff">var</span> b=<span style="color: #0000ff">await</span></font><font size="2"><span style="color: #000000"> Http.DownloadAsync(url2);
            textBox1.Text</span>=a+</font><font size="2"><span style="color: #000000">b;
        }
        </span><span style="color: #0000ff">catch</span></font><font size="2"><span style="color: #000000">(Exception ex)
        {
            textBox1.Text</span>=</font><font size="2"><span style="color: #000000">ex.Message;
        }
    }
    </span><span style="color: #0000ff">catch</span></font><font size="2"><span style="color: #000000">(Exception ex)
    {
        textBox1.Text</span>=</font><span style="color: #000000"><font size="2">ex.Message;
    }
}</font></span></pre></div></font></p>
<p><font size="2" face="微软雅黑">聪明的读者立刻就想到了，两个try其实是重复的，那为什么不把他们合并成一个呢！当然我想告诉大家的是，异常是在不同的线程里面抛出来的，只是我们用CPS变换把代码“改写”成这种形式而已。理论上两个try是不能合并的。<font size="5"><strong>但是！</strong></font>我们的C#编译器君是很聪明的。正所谓<strong>语言的抽象高级了一点，那么编译器对你的代码也就理解得更多了一点</strong>。如果编译器发现你在try里面写了两个await，马上就明白了过来他需要帮你复制catch的部分——或者说他可以帮你自动的复制catch的部分，那情况就完全不同了，最后就可以写成：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #008000"><font size="3">//</font></span><font size="3"><span style="color: #008000"> C#要求函数前面要加一个async来允许你在函数内使用await
</span><span style="color: #008000">//</span></font><font size="3"><span style="color: #008000"> 当然同时你的函数也就返回Task而不是void了
</span><span style="color: #008000">//</span></font><font size="3"><span style="color: #008000"> 不过没关系，C#的event也可以接受一个标记了async的函数，尽管返回值不一样
</span><span style="color: #008000">//</span><span style="color: #008000"> 设计语言这种事情就是牵一发而动全身呀，加个await连event都要改</span>
<span style="color: #0000ff">async</span> <span style="color: #0000ff">void</span> button4_Click(<span style="color: #0000ff">object</span></font><font size="3"><span style="color: #000000"> sender, EventArgs e)
{
    </span><span style="color: #0000ff">try</span></font><span style="color: #000000">
<font size="3">    {
        </font></span><font size="3"><span style="color: #0000ff">string</span> a=<span style="color: #0000ff">await</span></font><font size="3"><span style="color: #000000"> Http.DownloadAsync(url1);
        </span><span style="color: #0000ff">string</span> b=<span style="color: #0000ff">await</span></font><font size="3"><span style="color: #000000"> Http.DownloadAsync(url2);
        textBox1.Text</span>=a+</font><font size="3"><span style="color: #000000">b;
    }
    </span><span style="color: #0000ff">catch</span></font><font size="3"><span style="color: #000000">(Exception ex)
    {
        textBox1.Text</span>=</font><span style="color: #000000"><font size="3">ex.Message;
    }
}</font></span></pre></div></font></p>
<p><font size="2" face="微软雅黑">把两个await换成回调已经让我们写的够辛苦了，那么如果我们把await写在了循环里面，事情就不那么简单了。CPS需要把循环翻译成递归，那你就得把lambda表达时拿出来写成一个普通的函数——这样他就可以有名字了——然后才能递归（写出一个用于CPS的Y-combinator是一件很困难的事情，尽管并没有比Y-combinator本身困难多少）。这个例子就复杂到爆炸了，我在这里就不演示了。</font></p>
<p><font size="2" face="微软雅黑">总而言之，C#因为有了CPS变换（await），就可以把button4_Click帮你写成button3_Click然后再帮你写成button2_Click，最后把整个函数变成异步和回调的形式（真正的做法要更聪明一点，大家可以反编译去看）在异步回调的写法里面，exception和error code其实是一样的。但是CPS+exception和CPS+error code就跟单线程下面的exception和error code一样，有着重大的区别。这就是为什么文章一开始会说，我只会在带CPS变换的语言（Haskell/F#/etc）里面使用error code。</font></p>
<p><font size="2" face="微软雅黑">在这类语言里面利用相同的技巧，就可以不是异步的东西也用CPS包装起来，譬如说monadic parser combinator。至于你要选择monad还是comonad，基本上就是取决于你要自动提供错误处理还是要手动提供错误处理。像上面的Task.ContinueWith，是要求你手动提供错误处理的（因为你catch了之后可以干别的事情，Task无法自动替你选择最好的措施），所以他就把Task.ContinueWith写成了comonad的那个样子。</font></p>
<p><font size="2" face="微软雅黑">写到这里，不禁要同情写前端的那帮javascript和自以为可以写后端的node.js爱好者们，你们因为小小的eval的问题，不用老赵的windjs（windjs给javascript加上了await但是它不是一个altjs所以得显式调用eval），是一个多大的损失……</font></p><img src ="http://www.cppblog.com/vczh/aggbug/200920.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-06-10 15:01 <a href="http://www.cppblog.com/vczh/archive/2013/06/10/200920.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（五）&amp;mdash;&amp;mdash;面向对象和消息发送</title><link>http://www.cppblog.com/vczh/archive/2013/05/25/200580.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 25 May 2013 03:08:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/05/25/200580.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/200580.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/05/25/200580.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/200580.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/200580.html</trackback:ping><description><![CDATA[<p>面向对象这个抽象的特例总是有说不完的话题，更糟糕的是很多语言都错误地实现了面向对象——class居然可以当一个变量类型什么的这只是让人们写代码写的更糟糕而已。当然这个话题第三篇文章已经说过了，现在来谈谈人们喜欢拿来装逼的另一个话题——消息发送。</p> <p>按照惯例先来点题外话。说到消息发送，有些人喜欢跳出来说，objective-c的消息做得多优雅啊，代码都可以写成一句话[golang screw:you you:suck]之类的。其实这个还做得不够彻底。在几年前易语言曾经火了一阵，但是为什么大家这么讨厌他呢？其实显然不是因为每个token都是汉字，而是因为他做的一点都不像中文，谁会说话的时候带那么多符号呀。其实objective-c也一样，没人会因为想在一句英语里面用冒号来分割短语的。</p> <p>当我还在读大三的时候，我由于受到了Apple Script（也是苹果做的）的启发，试图发明一门语言，让他可以尽量写起来像自然语言——当然他仍然是严格的编程语言。但是这门语言因为其奇特的语法结构，我只好自己想出了一个两遍parse地方法。第一遍parse出所有函数头，让后用这些函数头临时组成一个parser，用它来parse语句的部分。后面整个也实现出来了，然后我就去做了一下调查，<strong>发现大家不喜欢，原因是要输入的东西太多了</strong>。不过我这里还是贴一下当初是怎么设计的：</p> <div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>phrase <strong><font color="#000000" size="3">print</font></strong>(content) <span style="color: #0000ff">is</span><span style="color: #000000">
    external function </span><span style="color: #800000">"</span><span style="color: #800000">writeln</span><span style="color: #800000">"</span><span style="color: #000000">
end phrase

phrase <strong><font size="3">first</font></strong> (count) <strong><font size="3">items of fibonacci sequence</font></strong> </span><span style="color: #0000ff">is</span>
    <span style="color: #0000ff">if</span> count equals to <span style="color: #800080">1</span><span style="color: #000000"> then
        result </span><span style="color: #0000ff">is</span> [<span style="color: #800080">1</span><span style="color: #000000">]
    </span><span style="color: #0000ff">else</span> <span style="color: #0000ff">if</span> count equals to <span style="color: #800080">2</span><span style="color: #000000"> then
        result </span><span style="color: #0000ff">is</span> [<span style="color: #800080">1</span>,<span style="color: #800080">1</span><span style="color: #000000">]
    </span><span style="color: #0000ff">else</span><span style="color: #000000">
        let list be [</span><span style="color: #800080">1</span>,<span style="color: #800080">1</span><span style="color: #000000">]
        repeat with i </span><span style="color: #0000ff">from</span> <span style="color: #800080">3</span><span style="color: #000000"> to count
            let list be list joins with item length of list </span>- <span style="color: #800080">1</span> of list + item length of list - <span style="color: #800080">2</span><span style="color: #000000"> of list
        end
        result </span><span style="color: #0000ff">is</span><span style="color: #000000"> list
    end
end phrase

phrase (number) </span><strong><font color="#000000"><font size="3"><span style="color: #0000ff"><font color="#000000">is</font></span> odd</font></font></strong> <span style="color: #0000ff">is</span><span style="color: #000000">
    result </span><span style="color: #0000ff">is</span> number mod <span style="color: #800080">2</span> <span style="color: #0000ff">is</span> <span style="color: #800080">0</span><span style="color: #000000">
end phrase alias <strong><font size="3">odd number</font></strong>

phrase <strong><font size="3">append</font></strong> (item) <strong><font size="3">after</font></strong> (list) </span><span style="color: #0000ff">is</span><span style="color: #000000">
    let </span><span style="color: #800080">0</span> element of list <span style="color: #0000ff">from</span><span style="color: #000000"> length of list be [item]
end phrase

phrase ((item) </span><strong><font size="3"><font color="#000000"><span style="color: #0000ff">is</span> validated</font></font></strong>) <span style="color: #0000ff"><strong><font color="#000000" size="3">in</font></strong></span> (list) <span style="color: #0000ff">is</span><span style="color: #000000">
    let filtered list be []
    repeat with item </span><span style="color: #0000ff">in</span><span style="color: #000000"> list
        append item after filtered list </span><span style="color: #0000ff">if</span> item <span style="color: #0000ff">is</span><span style="color: #000000"> validated
    end
    result </span><span style="color: #0000ff">is</span><span style="color: #000000"> filtered list
end phrase

phrase <strong><font size="3">main</font></strong> </span><span style="color: #0000ff">is</span><span style="color: #000000">
    print odd number </span><span style="color: #0000ff">in</span> first <span style="color: #800080">10</span><span style="color: #000000"> items of fibonacci sequence
end phrase</span></pre></div>
<p>倒数第二个函数声明甚至连函数指针的声明也如此的优雅（我自己认为的），整个程序组织起来，我们要输出斐波那契数列里面前10个数字中间的奇数，于是就写成了</p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>print odd number <span style="color: #0000ff">in</span> first <span style="color: #800080">10</span> items of fibonacci sequence</pre></div>
<p>看起来比objective-c要漂亮把。其实如果想把所有的东西换成中文，算法也不需要变化。现在用空格来分割一个一个的词，中文直接用字符就好了，剩下的都一样。要parse这个程序根本没办法用流行的那些方法来parse。当然我知道大家也不会关心这些特别复杂的问题，于是题外话就到这里结束了，这个语言的实现的代码你们大概也永远都不会看到的，啊哈哈哈哈。</p>
<p>为什么要提这件事情呢？我主要是想告诉大家，就算你在用面向对象语言，想在程序里面给一个对象发送一条消息，这个对象并不是非得写在最前面的。为什么呢？有的时候对象不止一个——这个东西叫multiple dispatching，著名的问题就是如何给一堆面向对象的几何体类做他们的求交函数——用面向对象的惯用做法做起来会特别的难受。不过现在我们先来看一下普通的消息发送是什么样子的。</p>
<p>对于一个我们知道他是什么类型的对象来说，发送一个消息就跟直接调用一个函数一样，因为你不需要去resolve一下这个函数到底是谁。譬如说下面的代码： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> Language
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">void</span><span style="color: #000000"> YouSuck(){ ... }
};

Language golang;
golang.YouSuck();</span></pre></div>
<p>最终翻译出来的结果会<strong>近似</strong>于 </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> Language
{
};

</span><span style="color: #0000ff">void</span> Language_YouSuck(Language* <span style="color: #0000ff">const</span> <span style="color: #0000ff">this</span><span style="color: #000000">)
{
    ...
}

Language golang;
Language_YouSuck(</span>&amp;golang);</pre></div>
<p>很多人其实并不能在学习面向对象语言的时候就直接意识到这一点。其实我也是在高中的时候玩delphi突然就在网上看见了这么一篇文章，然后我才明白的。看起来这个过渡并不是特别的自然是不是。</p>
<p>当你要写一个独立的class，不继承自任何东西的时候，这个class的作用只有两个。第一个是封装，这个第三篇文章已经提到过了。第二个作用就是给里面的那些函数做一个匿名的namespace。这是什么意思呢？就像上面的代码一样，你写golang.YouSuck()，编译器会知道golang是一个Language，然后去调用Language::YouSuck()。如果你调用lisp.YouSuck()的时候，说不定lisp是另一个叫做BetterThanGolangLanguage的类型，然后他就去里面找了YouSuck。这里并不会因为两个YouSuck的名字一样，编译器就把它搞混了。这个东西这跟重载也差不多，我就曾经在Microsoft Research里面看见过一个人做了一个语言（主要是用来验证语言本身的正确性的），其中a.b(c, d)是b(a, c, d)的语法糖，这个“.”毫无特别之处。</p>
<p>有一天，情况变了。专门开发蹩脚编译器的AMD公司看见golang很符合他们的口味，于是也写了一个golang的实现。那这个事情应该怎么建模呢？因为golang本身是一套标准，你可也可以称呼他为协议，然后下面有若干个实现。所以Language本身作为一个category也只好跟着golang变成interface了。为了程序简单我们只看其中的一个小片段： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> IGolang
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> YouSuck()=<span style="color: #800080">0</span><span style="color: #000000">;
};

</span><span style="color: #0000ff">class</span><span style="color: #000000"> GoogleGolang : <font color="#0000ff">public</font> IGolang
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">void</span> YouSuck()override{ <span style="color: #008000">/*</span><span style="color: #008000">1</span><span style="color: #008000">*/</span><span style="color: #000000"> }
};

</span><span style="color: #0000ff">class</span><span style="color: #000000"> AmdGolang : <font color="#0000ff">public</font> IGolang
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">void</span> YouSuck()override{ <span style="color: #008000">/*</span><span style="color: #008000">2</span><span style="color: #008000">*/</span><span style="color: #000000"> }
};

IGolang</span>* golang = <span style="color: #0000ff">new</span><span style="color: #000000"> GoogleGolang;
golang</span>-&gt;YouSuck();</pre></div>
<p>我很喜欢VC++的专有关键字override，他可以在我想override但是不小心写错了一点的时候提示我，避免了我大量的错误的发生。当然这个东西别的编译器不支持，所以我在我的代码的靠前的地方写了一个宏，发现不是VC++再编译，我就把override给#define成空的。反正我的程序里面不会用关键字来当变量名的。</p>
<p>看着这个程序，已经不能单纯的用GoogleGolang_YouSuck(golang)来代替这个消息发送了，因为类型是IGolang的话说不定下面是一个AmdGolang。所以在这里我们就要引入虚函数表了。一旦引入了虚函数表，代码就会瞬间变得复杂起来。我见过很多人问，虚函数表那么大，要是每一个类的实例都带一个表的话岂不是很浪费内存？这种人就应该先去看《Inside the C++ Object Model》，然后再反省一下自己的问题有多么的——呃——先看带有虚函数表的程序长什么样子好了： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> vtable_IGolang
{
    </span><span style="color: #0000ff">void</span> (*<span style="color: #000000">YouSuck)(IGolang* <span style="color: #0000ff">const</span> <span style="color: #0000ff">this</span>);
};

</span><span style="color: #0000ff">struct</span><span style="color: #000000"> IGolang
{
    vtable_IGolang</span>*<span style="color: #000000"> vtable;
};

</span><span style="color: #008000">//</span><span style="color: #008000">---------------------------------------------------</span>
<span style="color: #000000">
vtable_IGolang vtable_GoogleGolang;
vtable_GoogleGolang.YouSuck </span>= &amp;<span style="color: #000000">vtable_GoogleGolang_YouSuck;

</span><span style="color: #0000ff">struct</span><span style="color: #000000"> GoogleGolang
{
    IGolang parent;
};

</span><span style="color: #0000ff">void</span> vtable_GoogleGolang_YouSuck(IGolang* <span style="color: #0000ff">const</span> <span style="color: #0000ff">this</span><span style="color: #000000">)
{
    </span><span style="color: #0000ff">int</span> offset=(<span style="color: #0000ff">int</span>)(&amp;((GoogleGolang*)<span style="color: #800080">0</span>)-&gt;<span style="color: #000000">parent);
    GoogleGolang_YouSuck((GoogleGolang</span>*)((<span style="color: #0000ff">char</span>*)<span style="color: #0000ff">this</span>-<span style="color: #000000">offset));
}

</span><span style="color: #0000ff">void</span> GoogleGolang_YouSuck(GoogleGolang* <span style="color: #0000ff">const</span> <span style="color: #0000ff">this</span><span style="color: #000000">)
{
    </span><span style="color: #008000">/*</span><span style="color: #008000">1</span><span style="color: #008000">*/</span><span style="color: #000000">
}

</span><span style="color: #0000ff">void</span> GoogleGolang_ctor(GoogleGolang* <span style="color: #0000ff">const</span> <span style="color: #0000ff">this</span><span style="color: #000000">)
{
    </span><span style="color: #0000ff">this</span>-&gt;parent-&gt;vtable = &amp;<span style="color: #000000">vtable_GoogleGolang;
}

</span><span style="color: #008000">//</span><span style="color: #008000">---------------------------------------------------
</span><span style="color: #008000">//</span><span style="color: #008000"> AmdGolang略，长得都一样
</span><span style="color: #008000">//</span><span style="color: #008000">---------------------------------------------------</span>
<span style="color: #000000">
GoogleGolang</span>* tmp = (GoogleGolang*)malloc(<span style="color: #0000ff">sizeof</span><span style="color: #000000">(GoogleGolang));
GoogleGolang_ctor(tmp);
IGolang</span>* golang = &amp;tmp-&gt;<span style="color: #000000">parent;
golang</span>-&gt;vtable-&gt;YouSuck(golang);</pre></div>
<p>基本上已经面目全非了。当然实际上C++生成的代码比这个要复杂得多。我这里只是不想把那些细节牵引进来，针对我们的那个例子写了个可能的实现。面向对象的语法糖多么的重要啊，尽管你也可以在需要的时候用C语言把这些东西写出来（就跟那个愚蠢的某著名linux GUI框架一样），但是可读性已经完全丧失了吧。明明那么几行就可以表达出来的东西，我们为了达到同样的性能，用C写要把代码写成屎。东西一多，名字用完了，都只好对着代码发呆了，决定把C扔了，完全用C++来写。万一哪天用到了virtual继承——在某些情况下其实是相当好用的，譬如说第三篇文章讲的，在C++里面用interface，而且也很常见——那用C就只能呵呵呵了，写出来的代码再也没法读了，没法再把OOP实践下去了。</p>
<p>好了，消息发送的简单的实现大概也就讲到这里了。只要不是C++，其他语言譬如说只有单根继承的Delphi，实现OOP大概也就是上面这个样子。于是我们围绕着消息发送的语法糖玩了很久，终于遇到了两大终极问题。这两个问题说白了都是开放和封闭的矛盾。我们用基类和一大堆子类的结构来写程序的时候，需要把逻辑都封装在虚函数里面，不然的话你就得cast了，cast是将程序最终导向失控的根源之一。这个时候我们对类型扩展是开放的，而对逻辑扩展是封闭的。这是什么意思呢？让我们来看下面这个例子： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> Shape
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">double</span> GetArea()=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">bool</span> HitTest(Point p)=<span style="color: #800080">0</span><span style="color: #000000">;
};

</span><span style="color: #0000ff">class</span> Circle : <span style="color: #0000ff">public</span><span style="color: #000000"> Shape ...;
</span><span style="color: #0000ff">class</span> Rectangle : <span style="color: #0000ff">public</span> Shape ... ;</pre></div>
<p>我们每当添加一个新形状的时候，只要实现GetArea和HitTest，那么事情就做完了。所以你可以无限的添加新形状——所以类型扩展是开放的。但是你却永远只能做GetArea和HitTest——对逻辑扩展是封闭的。你如果想做除了GetArea和HitTest以外的更多的事情的话，这个时候你就被迫做cast了。那么在类型相对稳定的情况下有没有别的方法呢？设计模式告诉我们，我们可以用Visitor来把情况扭转过来——做成对类型扩展封闭，而对逻辑扩展开放的： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> IShapeVisitor
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Visit(Circle* shape)=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Visit(Rectangle* shape)=<span style="color: #800080">0</span><span style="color: #000000">;
};

</span><span style="color: #0000ff">class</span><span style="color: #000000"> Shape
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Accept(IShapeVisitor* visitor)=<span style="color: #800080">0</span><span style="color: #000000">;
};

</span><span style="color: #0000ff">class</span> Circle : <span style="color: #0000ff">public</span><span style="color: #000000"> Shape
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    ...

    </span><span style="color: #0000ff">void</span> Accept(IShapeVIsitor* visitor)<span style="color: #0000ff">override</span><span style="color: #000000">
    {
        visitor</span>-&gt;Visit(<span style="color: #0000ff">this</span>);  <span style="color: #008000">//</span><span style="color: #008000"> 因为重载的关系，会调用到第一个Visit函数</span>
<span style="color: #000000">    }
};

</span><span style="color: #0000ff">class</span> Rectangle : <span style="color: #0000ff">public</span><span style="color: #000000"> Shape
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    ...

    </span><span style="color: #0000ff">void</span> Accept(IShapeVIsitor* visitor)<span style="color: #0000ff">override</span><span style="color: #000000">
    {
        visitor</span>-&gt;Visit(<span style="color: #0000ff">this</span>);  <span style="color: #008000">//</span><span style="color: #008000"> 因为重载的关系，会调用到第二个Visit函数</span>
<span style="color: #000000">    }
};

</span><span style="color: #008000">//</span><span style="color: #008000">------------------------------------------</span>

<span style="color: #0000ff">class</span> GetAreaVisitor : <span style="color: #0000ff">public</span><span style="color: #000000"> IShapeVisitor
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">double</span><span style="color: #000000"> result;

    </span><span style="color: #0000ff">void</span> Visit(Circle*<span style="color: #000000"> shape)
    {
        result </span>=<span style="color: #000000"> ...;
    }

    </span><span style="color: #0000ff">void</span> Visit(Rectangle*<span style="color: #000000"> shape)
    {
        result </span>=<span style="color: #000000"> ...;
    }
};

</span><span style="color: #0000ff">class</span> HitTestVisitor : <span style="color: #0000ff">public</span> IShapeVisitor ...;</pre></div>
<p>这个时候GetArea可能调用起来就不是那么方便了，不过我们总是可以把它写成一个函数： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">double</span> GetArea(Shape*<span style="color: #000000"> shape)
{
    GetAreaVisitor visitor;
    shape</span>-&gt;Accept(&amp;<span style="color: #000000">visitor);
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> visitor.result;
}</span></pre></div>
<p>这个时候你可以随意的做新的事情了，但是一旦需要添加新类型的时候，你需要改动很多东西，首先是Visitor的接口，其实是让所有的逻辑都支持新类型，这样你就不能仅仅通过添加新代码来扩展新类型了。所以这就是对逻辑扩展开放，而对类型扩展封闭了。</p>
<p>所以第一个问题就是：<strong>能不能做成类型扩展也开放，逻辑扩展也开放呢？</strong>在回答这个问题之前，我们先来看下一个问题。我们要对两个Shape进行求交，看看他们是不是有重叠在一起的部分。但是每一个具体的Shape，譬如Circle啊Rectangle啊，定义都是不一样的，没办法有通用的处理办法，所以我们只能写3个函数了（RR, CC, CR）。如果有3各类型，那么我们就需要6个函数。如果有4个类型，那我们就需要有10个函数——才能处理所有情况。公式倒是可以一下子看出来，函数数量就等于1+2+ … +n，n等于类型的数量。</p>
<p>这看起来好像是一个类型扩展开放的问题是吧，但是实际上他只能用逻辑扩展的方法来做。为什么呢？你看我们的一个visitor其实很像是我们对一个一个的具体类型都试一下看看shape是不是这个类型，从而做出正确的处理。不过这跟我们直接用if地方法相比有两个优点：1、快；2、编译器替你查错有保证。</p>
<p>那实际上应该怎么做呢？想想，我们这里有两次“if type”。第一次针对第一个参数，第二次针对第二个参数。所以我们一共需要n+1=3个visitor。写的方法倒是不复杂，首先我们得准备好RR，CC，CR三个逻辑，然后用visitor去识别类型然后调用它们： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">bool</span> IntersectCC(Circle* s1, Circle*<span style="color: #000000"> s2){ ... }
</span><span style="color: #0000ff">bool</span> IntersectCR(Circle* s1, Rectangle*<span style="color: #000000"> s2){ ... }
</span><span style="color: #0000ff">bool</span> IntersectRR(Rectangle* s1, Rectangle*<span style="color: #000000"> s2){ ... }
</span><span style="color: #008000">//</span><span style="color: #008000"> RC和CR是一样的</span>

<span style="color: #0000ff">class</span> IntersectWithCircleVisitor : <span style="color: #0000ff">public</span><span style="color: #000000"> IShapeVisitor
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    Circle</span>*<span style="color: #000000"> s1;
    </span><span style="color: #0000ff">bool</span><span style="color: #000000"> result;

    </span><span style="color: #0000ff">void</span> Visit(Circle*<span style="color: #000000"> shape)
    {
        result</span>=<span style="color: #000000">IntersectCC(s1, shape);
    }

    </span><span style="color: #0000ff">void</span> Visit(Rectangle*<span style="color: #000000"> shape)
    {
        result</span>=<span style="color: #000000">IntersectCR(s1, shape);
    }
};

</span><span style="color: #0000ff">class</span> IntersectWithRectangleVisitor : <span style="color: #0000ff">public</span><span style="color: #000000"> IShapeVisitor
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    Rectangle</span>*<span style="color: #000000"> s1;
    </span><span style="color: #0000ff">bool</span><span style="color: #000000"> result;

    </span><span style="color: #0000ff">void</span> Visit(Circle*<span style="color: #000000"> shape)
    {
        result</span>=<span style="color: #000000">IntersectCR(shape, s1);
    }

    </span><span style="color: #0000ff">void</span> Visit(Rectangle*<span style="color: #000000"> shape)
    {
        result</span>=<span style="color: #000000">IntersectRR(s1, shape);
    }
};

</span><span style="color: #0000ff">class</span> IntersectVisitor : <span style="color: #0000ff">public</span><span style="color: #000000"> IShapeVisitor
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">bool</span><span style="color: #000000"> result;
    IShape</span>*<span style="color: #000000"> s2;

    </span><span style="color: #0000ff">void</span> Visit(Circle*<span style="color: #000000"> shape)
    {
        IntersectWithCircleVisitor visitor;
        visitor.s1</span>=<span style="color: #000000">shape;
        s2</span>-&gt;Accept(&amp;<span style="color: #000000">visitor);
        result</span>=<span style="color: #000000">visitor.result;
    }

    </span><span style="color: #0000ff">void</span> Visit(Rectangle*<span style="color: #000000"> shape)
    {
        IntersectWithRectangleVisitor visitor;
        visitor.s1</span>=<span style="color: #000000">shape;
        s2</span>-&gt;Accept(&amp;<span style="color: #000000">visitor);
        result</span>=<span style="color: #000000">visitor.result;
    }
};

</span><span style="color: #0000ff">bool</span> Intersect(Shape* s1, Shape*<span style="color: #000000"> s2)
{
    IntersectVisitor visitor;
    visitor.s2</span>=<span style="color: #000000">s2;
    s1</span>-&gt;Accept(&amp;<span style="color: #000000">visitor);
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> visitor.result;
}</span></pre></div>
<p>我觉得你们现在心里的想法肯定是：“我屮艸芔茻。”嗯，这种事情在物理引擎里面是经常要碰到的。然后当你需要添加一个新的形状的时候，呵呵呵呵呵呵呵呵。不过这也是没办法的，谁让现在的要求运行时性能的面向对象语言都这么做呢？</p>
<p>当然，如果在不要求性能的情况下，我们可以用ruby和它的mixin来做。至于说怎么办，其实你们应该发现了，添加一个Visitor和添加一个虚函数的感觉是差不多的。所以只要把Visitor当成虚函数的样子，让Ruby给mixin一堆新的函数进各种类型就好了。不过只有支持运行时mixin的语言才能做到这一点。强类型语言我觉得是别想了。</p>
<p>Mixin地方法倒是很直接，我们只要把每一个Visitor里面的Visit函数都给加进去就好了，大概感觉上就类似于： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> Shape
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #008000">//</span><span style="color: #008000"> Mixin的时候等价于给每一个具体的Shape类都添加下面三个虚函数的重写</span>
<span style="color: #0000ff">    virtual</span> <span style="color: #0000ff">bool</span> Intersect(Shape* s2)=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">bool</span> IntersectWithCircle(Circle* s1)=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">bool</span> IntersectWithRectangle(Rectangle* s1)=<span style="color: #800080">0</span><span style="color: #000000">;
};

</span><span style="color: #008000">//</span><span style="color: #008000">--------------------------------------------</span>

<span style="color: #0000ff">bool</span> Circle::Intersect(Shape*<span style="color: #000000"> s2)
{
    </span><span style="color: #0000ff">return</span> s2-&gt;IntersectWithCircle(<span style="color: #0000ff">this</span><span style="color: #000000">);
}

</span><span style="color: #0000ff">bool</span> Rectangle::Intersect(Shape*<span style="color: #000000"> s2)
{
    </span><span style="color: #0000ff">return</span> s2-&gt;IntersectWithRectangle(<span style="color: #0000ff">this</span><span style="color: #000000">);
}

</span><span style="color: #008000">//</span><span style="color: #008000">--------------------------------------------</span>

<span style="color: #0000ff">bool</span> Circle::IntersectWithCircle(Circle*<span style="color: #000000"> s1)
{
    </span><span style="color: #0000ff">return</span> IntersectCC(s1, <span style="color: #0000ff">this</span><span style="color: #000000">);
}

</span><span style="color: #0000ff">bool</span> Rectangle::IntersectWithCircle(Circle*<span style="color: #000000"> s1)
{
    </span><span style="color: #0000ff">return</span> IntersectCR(s1, <span style="color: #0000ff">this</span><span style="color: #000000">);
}

</span><span style="color: #008000">//</span><span style="color: #008000">--------------------------------------------</span>

<span style="color: #0000ff">bool</span> Circle::IntersectWithRectangle(Rectangle*<span style="color: #000000"> s1)
{
    </span><span style="color: #0000ff">return</span> IntersectCR(<span style="color: #0000ff">this</span><span style="color: #000000">, s1);
}

</span><span style="color: #0000ff">bool</span> Rectangle::IntersectWithRectangle(Rectangle*<span style="color: #000000"> s1)
{
    </span><span style="color: #0000ff">return</span> IntersectRR(s1, <span style="color: #0000ff">this</span><span style="color: #000000">);
}</span></pre></div>
<p>这下子应该看出来为什么我说这种方法只能用Visitor了吧，否则就要把所有类型都写进Shape，就会很奇怪了。如果这样的逻辑一多，类型也有四五个的话，那每加一个逻辑就得添加一批虚函数，Shape类很快就会被玩坏了。而代表逻辑的Visitor是可以放在不同的地方的，互相之间是隔离的，维护起来就会比较容易。</p>
<p>那现在我们就要有第二个问题了：<strong>在拥有两个“this”的情况下，我们要如何做才能把逻辑做成类型扩展也开放，逻辑扩展也开放呢？</strong>然后参考我们的第一个问题：<strong>能不能做成类型扩展也开放，逻辑扩展也开放呢？</strong>你应该心里有数了吧，答案当然是——不能做。</p>
<p>这就是语言的极限了。面向对象才用的single dispatch的方法，能做到的东西是很有限的。情况稍微复杂那么一点点——就像上面对两个形状求交这种正常的问题——写起来都这么难受。</p>
<p>那呼应一下标题，如果我们要设计一门语言，来支持上面这种multiple dispatch，那可以怎么修改语法呢？这里面分为两种，第一种是像C++这样运行时load dll不增加符号的，第二种是像C#这样运行时load dll会增加符号的。对于前一种，其实我们可以简单的修改一下语法： </p>
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">bool</span> Intersect(<span style="color: #0000ff">switch</span> Shape* s1, <span style="color: #0000ff">switch</span> Shape*<span style="color: #000000"> s2);

</span><span style="color: #0000ff">bool</span> Intersect(<span style="color: #0000ff">case</span> Circle* s1, <span style="color: #0000ff">case</span> Circle*<span style="color: #000000"> s2){ ... }
</span><span style="color: #0000ff">bool</span> Intersect(<span style="color: #0000ff">case</span> Circle* s1, <span style="color: #0000ff">case</span> Rectangle*<span style="color: #000000"> s2){ ... }
</span><span style="color: #0000ff">bool</span> Intersect(<span style="color: #0000ff">case</span> Rectangle* s1, <span style="color: #0000ff">case</span> Circle*<span style="color: #000000"> s2){ ... }
</span><span style="color: #0000ff">bool</span> Intersect(<span style="color: #0000ff">case</span> Rectangle* s1, <span style="color: #0000ff">case</span> Rectangle* s2){ ... }</pre></div>
<p>然后修改一下编译器，把这些东西翻译成虚函数塞回原来的Shape类里面就行了。对于第二种嘛，其实就相当于Intersect的根节点、Circle和CC写在dll1，Rectangle和CR、RC、RR写在dll2，然后dll1运行时把dll2给动态地load了进来，再之后调用Intersect的时候就好像“虚函数已经进去了”一样。至于要怎么做，这个大家回去慢慢思考一下吧，啊哈哈哈。</p><img src ="http://www.cppblog.com/vczh/aggbug/200580.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-05-25 11:08 <a href="http://www.cppblog.com/vczh/archive/2013/05/25/200580.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（四）&amp;mdash;&amp;mdash;什么是坑(操作模板)</title><link>http://www.cppblog.com/vczh/archive/2013/05/12/200195.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sun, 12 May 2013 08:31:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/05/12/200195.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/200195.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/05/12/200195.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/200195.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/200195.html</trackback:ping><description><![CDATA[<p>其实我在写这个系列的第三篇文章的时候就已经发现，距离机器越远，也就是抽象越高的概念，坑的数量是越少的。但是这并不是说，距离机器越近的概念就越强大或者说越接近本质。这是广大的程序员对计算理论的一种误解。大多数人理解编程的知识结构的时候，都是用还原论来理解的，这个方法其实并没有错。但问题在于，&#8220;还原&#8221;的方法并不是唯一的。很多人觉得，反正你多高级的语言编译完了无非都是机器码嘛。但是还有另一种解释，你无论多低级的语言编译完了无非也就是带CPS变换（continuation passing style）的&#955;-calculus程序嘛。他们是等价的，不仅能力上也是，&#8220;本质&#8221;上也是。</p> <p>一个用CPS变换完整地处理过的&#955;-calculus程序长的就很像一串指令。而且类似于C++的inline操作，在这里是完全自然、安全、容易做的。那其实为什么我们的机器不发明成这样子呢？显然这完全跟我们想如何写一个程序是没关系的。正是这种冲突让我们有一种&#8220;概念距离机器越远运行速度就越慢&#8221;的错误的直觉。扯远了讲，就算你在用一门函数式语言，譬如说Haskell也好，F#也好，最终在运行的时候，还是在运行彻底编译出来的机器码。这些语言是完全不需要&#8220;模拟器&#8221;的，虽然由于各种历史原因人们首先开发了模拟器。当然一个精心设计过的C程序肯定还是要比haskell快的，但是我觉得能这么干的人不多，而且大多数时候这么干都是在浪费老板的钱而已，因为你们的程序原本就不需要快到那种份上。这种东西就跟那些做互联网对于测试的想法是一样的——有bug？发现了再说，先release抢市场。</p> <p>如果对这方面有了解的话，CPS变换——也就是Lost In Stupid Parentheses-er们最喜欢的call-with-current-continuation，他的另一个名字叫call/cc——是一种跟goto一样强大而且基本的控制流的做法。goto和CPS可以互相转换不说了，所有其它控制流都可以转换成goto和CPS。它们两者在这方面是不相上下的。而且既然一个完全用CPS变换处理过的程序长得就像一串指令，那你说他们的区别是什么呢？区别就是，CPS可以是强类型的，而goto则永远都不可能。</p> <p>作为废话的最后一段，我给个小例子来讲什么叫&#8220;一个用CPS变换完整地处理过的&#955;-calculus程序长的就很像一串指令&#8221;。就让我们用a(b( x ), c( x ))这样的一个表达式来讲：<br>处理前： <div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>a (b x) (c x)</pre></div></p>
<p>处理后：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">b x &#955;a0.
a a0 &#955;a1.
c x &#955;a2.
a1 a2</span></pre></div></p>
<p>用我们熟悉到不能再熟悉的Haskell的Monad的手法来翻译一下其实就是：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>a0 &lt;-<span style="color: #000000"> b(x)
a1 </span>&lt;-<span style="color: #000000"> a(a0)
a2 </span>&lt;-<span style="color: #000000"> c(x)
</span>return (a1(a2))</pre></div></p>
<p>好了，至于上面这种形式（看起来很像SSA）是怎么被做成机器码的，大家自己去看编译原理吧。上面这么多废话就是想表达一个结论：抽象并不意味着负担。当然，至于对程序员的智商上的要求，对某些人也是一种负担，这个我就没办法了，所以就不考虑他了。</p>
<p>===============废话结束================</p>
<p>模板也是这类抽象的一种。为什么我要把标题写成&#8220;坑&#8221;，只是想跟前面统一一下而已，其实到了模板这么高级的抽象的时候，基本上已经没什么坑了。当然C++的做法就另当别论了，而且我想那些坑你们大概一辈子也碰不到的了。那我们先从简单的讲起。</p>
<p>比模板更简单的东西自然就是泛型了。为什么叫他泛型？因为泛型实际上就是一种复制代码的方法，它本身是没有推导能力的，所以肯定谈不上什么模板了。但是在大多数情况下，泛型这么弱的抽象也已经基本够用了。跟泛型相关的手法大约有三个。</p>
<p>第一个就是定义一个<strong>返回一个类的函数</strong>（在这里参数是T）：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span> Array&lt;T&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">public</span> Array(<span style="color: #0000ff">int</span><span style="color: #000000"> count);
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Count{<span style="color: #0000ff">get</span><span style="color: #000000">;}
    </span><span style="color: #0000ff">public</span> T <span style="color: #0000ff">this</span>[<span style="color: #0000ff">int</span> index]{<span style="color: #0000ff">get</span>; <span style="color: #0000ff">set</span><span style="color: #000000">;}
}</span></pre></div></p>
<p>第二个就是，<strong>调用这个函数，参数给他类型</strong>，帮我们new一个类的实例：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> xs = <span style="color: #0000ff">new</span> Array&lt;<span style="color: #0000ff">int</span>&gt;(<span style="color: #800080">10</span>);</pre></div></p>
<p>这其实有两步。第一步是对函数调用Array&lt;int&gt;求值得到一个T，然后对new T(10)进行求值获得一个对象。只是刚好Array&lt;int&gt;的返回值也叫Array&lt;int&gt;，所以比较混淆视听。</p>
<p>事情到这里还没完。上一篇文章中我们讲到，写一个类是要考虑很多contract的问题的。所以Array&lt;T&gt;是个什么东西呢？他至少是一个IEnumerable&lt;T&gt;：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">interface</span> IEnumerable&lt;<span style="color: #0000ff">out</span> T&gt;<span style="color: #000000">
{
    </span><span style="color: #008000">//</span><span style="color: #008000"> ...</span>
<span style="color: #000000">}

</span><span style="color: #0000ff">class</span> Array&lt;T&gt; : IEnumerable&lt;T&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">public</span> Array(<span style="color: #0000ff">int</span><span style="color: #000000"> count);
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Count{<span style="color: #0000ff">get</span><span style="color: #000000">;}
    </span><span style="color: #0000ff">public</span> T <span style="color: #0000ff">this</span>[<span style="color: #0000ff">int</span> index]{<span style="color: #0000ff">get</span>; <span style="color: #0000ff">set</span><span style="color: #000000">;}
}</span></pre></div></p>
<p>于是有一天我们构造了一个Array&lt;Array&lt;string&gt;&gt;的对象，然后要写一个函数来处理他。这个函数做的事情很简单，就是把这个二维的数组给平摊成一个一维的数组，里面所有的数组头尾相接起来。于是根据上一篇文章的内容，我们写一个接受class的函数，也是要想很多contract的问题的（面向对象就是麻烦啊）。这个函数需要的只是遍历的功能，那我们完全没有必要要求他必须是一个Array，于是我们的函数就这么写：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>IEnumerable&lt;T&gt; Flatten&lt;T&gt;(IEnumerable&lt;IEnumerable&lt;T&gt;&gt;<span style="color: #000000"> xss)
{
    </span><span style="color: #0000ff">foreach</span>(<span style="color: #0000ff">var</span> xs <span style="color: #0000ff">in</span><span style="color: #000000"> xss)
        </span><span style="color: #0000ff">foreach</span>(<span style="color: #0000ff">var</span> x <span style="color: #0000ff">in</span><span style="color: #000000"> xs)
            </span><span style="color: #0000ff">yield</span> <span style="color: #0000ff">return</span><span style="color: #000000"> x;
}</span></pre></div></p>
<p>或者你也可以用高级一点的写法，反正是一样的：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>IEnumerable&lt;T&gt; Flatten&lt;T&gt;(IEnumerable&lt;IEnumerable&lt;T&gt;&gt;<span style="color: #000000"> xss)
{
    </span><span style="color: #0000ff">return</span> xss.Aggregate(<span style="color: #0000ff">new</span><span style="color: #000000"> T[], Enumerable.Concat);
}</span></pre></div></p>
<p>有CPS变换就是好呀，没有CPS变换的语言都写不出yield return和async await的。但是你们这些搞前端的人，特别是做nodejs的也是，都是教条主义的，觉得eval是evil，硬是把老赵的windjs（曾用名：jscex）给拒了。亏js还是一个特别适合写callback的语言呢，结果没有$await，你们只能把一个好好的程序写成一支火箭了。</p>
<p>那现在问题来了。当我们想把Array&lt;Array&lt;string&gt;&gt;传给Flatten的时候，我们发现Flatten的参数需要的是IEnumerable&lt;IEnumerable&lt;string&gt;&gt;，究竟二维的数组能不能转成二维的迭代器呢？</p>
<p>C++嘛，因为它没有C#的<strong>协变和逆变</strong>的功能，所以是做不到的了。幸好我们这里用的是C# 4.0。那C#究竟是怎么做的呢？</p>
<p>其实从Array&lt;Array&lt;string&gt;&gt;到IEnumerable&lt;IEnumerable&lt;string&gt;&gt;需要两步。第一步因为Array继承自IEnumerable，所以类型变成了IEnumerable&lt;Array&lt;string&gt;&gt;。第二部就是最重要的步骤了，因为IEnumerable&lt;out T&gt;的T有一个out，这就说明，IEnumerable里面所有的T都是用在函数的返回值上的，他只生产T，不会消耗T。所以一个IEnumerable&lt;子类型&gt;就可以转变成IEnumerable&lt;父类型&gt;，因为子类型总是可以转变成父类型的。因此最后IEnumerable&lt;Array&lt;string&gt;&gt;就变成了IEnumerable&lt;IEnumerable&lt;string&gt;&gt;了。</p>
<p>所以现在我们回过头来看上面提到的泛型的三个手法<br>1、定义一个输入类型输出类型的函数（<strong>class Array&lt;T&gt;</strong>）<br>2、调用这个函数来得到你想要的类型（<strong>new Array&lt;int&gt;()</strong>）<br>3、协变和逆变（<strong>(IEnumerable&lt;IEnumerable&lt;string&gt;&gt;)new Array&lt;Array&lt;string&gt;&gt;()</strong>）</p>
<p>所以说白了泛型就是一个对类型进行操作的东西。当然它的内涵远远没有模板那么丰富，但是既然讨论到了对类型的操作，我觉得我要稍微普及一下一个类型系统的常识——父类型和子类型。</p>
<p>父类型和子类型说的是什么，如果我们有两个类型，一个T，一个U。如果一个U的值，总是可以被看成一个T的值的话，那么我们就说U是T的子类型。我们也可以说，U是T的子集。这里我要说一下为什么正方形是一个长方形但是我们却不能让正方形继承自长方形呢？因为这个&#8220;是一个&#8221;只在只读的时候成立。考虑了写，正方形就不是子类型了。</p>
<p>除了类的继承，协变逆变以外，还有另一种父类型和子类型的关系——那就是模板类型了。举个例子，我有一个函数是这么写的：T Do&lt;T&gt;(T t)。这是一个输入T输出T的模板函数。那我们有一天需要一个输入int输出int的函数怎么办呢？Do&lt;int&gt;就好了。反过来行不行呢？不行。所以delegate T F&lt;T&gt;(T t)就是delegate int F(int t)的子类型。</p>
<p>当然，上面这个例子千万不能和函数的协变逆变混起来了。只有模板才能这么做，delegate string(string)肯定变不了delegate object(object)的。只有delegate string(object)可以通过协变+逆变同时作用变成delegate object(string)。因为object和string是继承的关系，不是模板的关系。</p>
<p>泛型到这里基本上就说完了，轮到模板了。C#的泛型产生的类可以是静态类，其实C++就更可以了，而且C++还有偏特化这种必不可缺的东西。那偏特化有什么用呢？这跟上一篇文章将面向对象的时候一样，其中的一个很大的用处就是拿来做contract。</p>
<p>interface和template（其实是<strong>concept mapping</strong>）拿来做contract的区别，在于你选择一个具有contract的类型的实现的时候，是在运行时做的，还是在编译时做的。其实有很多时候我们并不想，有时候也不能入侵式地给一个类随便添加一个新功能。我们知道，功能就是contract，contract要么是interface，要么是template。interface并不是总是可以加上去的，譬如说对那些不能修改的类，就像string啊int什么的。而且我们也不会拿string和int做多态。这种时候我们就需要concept mapping了，然后靠类型推导去选择正确的实现。在C++里面，就是用template+偏特化来做了。</p>
<p>上面这段话说得好像很抽象（一点都不抽象！），我们还是用一个生动的例子来做吧。譬如说我们要做序列化和反序列化。首先我们可以让若干个类型支持序列化的功能。其次，只要T支持序列化，那么vector&lt;T&gt;啊、list&lt;T&gt;什么的也将自动支持序列化的功能。这是一个递归的描述，所以用template来做刚刚好。但是像vector和list这种不能修改的类，或者int这种原生的类型，我们要怎么支持序列化呢？不能修改类，那只能写在类的外面，变成一个函数了。那对于vector&lt;T&gt;来说，他怎么知道T的序列化函数是什么呢？相信熟悉模板的读者肯定知道正确的答案是什么了。</p>
<p>首先我们要做一个空类叫Serializable&lt;T&gt;，其次不断地偏特化他们，覆盖所有我们需要的类型：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename T&gt;
<span style="color: #0000ff">struct</span><span style="color: #000000"> Serializable
{
};

template</span>&lt;&gt;
<span style="color: #0000ff">struct</span> Serializable&lt;<span style="color: #0000ff">int</span>&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">static</span> <span style="color: #0000ff">int</span> Read(istream&amp;<span style="color: #000000"> i);
    </span><span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Write(ostream&amp; o, <span style="color: #0000ff">int</span><span style="color: #000000"> v);
};

template</span>&lt;&gt;
<span style="color: #0000ff">struct</span> Serializable&lt;wstring&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">static</span> wstring Read(istream&amp;<span style="color: #000000"> i);
    </span><span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Write(ostream&amp; o, <span style="color: #0000ff">const</span> wstring&amp;<span style="color: #000000"> v);
};

template</span>&lt;typename T&gt;
<span style="color: #0000ff">struct</span> Serializable&lt;vector&lt;T&gt;&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">static</span> vector&lt;T&gt; Read(istream&amp; i);  <span style="color: #008000">//</span><span style="color: #008000"> 我们已经有右值引用构造函数了，不怕！</span>
    <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> Write(ostream&amp; o, <span style="color: #0000ff">const</span> vector&lt;T&gt;&amp;<span style="color: #000000"> v);
};</span></pre></div></p>
<p>这里需要提到的一点是，当我们使用Serializable&lt;vector&lt;T&gt;&gt;的时候，我们首先要保证T也是Serializable的。可惜的是C++并不能让我们很好地表达这一点，本来C++0x是有个concept mapping的标准，不过后来被干掉了，我觉得永远都不会有这个东西了。但其实这并不是什么太大的事情，因为只要你写错了，那总是会有编译错误的。</p>
<p>不过go语言在这一点上就不一样了，go没有模板什么像样的代码都写不出就不说了，就算他有模板，然后同时具有Read和Write的类型都实现了go的一个叫做Serializable的interface的话，结果又如何呢？其实这相当于把Serializable&lt;T&gt;::Read(i)和Serializable&lt;T&gt;::Write(o, v)都改成Read(i, &amp;v)和Write(o, v)。这样的问题<a href="http://blog.zhaojie.me/2013/04/why-i-dont-like-go-style-interface-or-structural-typing.html" target="_blank">老赵已经在他的博客</a>讲过了，万一Read和Write不是你写的，功能跟你要的不一样，只是碰巧有了这两个函数怎么办，你还能救吗？你已经不能救了，因为名字都用过了，你想显式地实现一个interface的话go又没这个功能，于是程序就到此傻逼了，Read和Write改名吧，祝你不是项目快写完了才发现这个问题。</p>
<p>关于编译错误，我觉得有一个事情是很值得说的。为什么熟悉Haskell都觉得Haskell的程序只要经过了编译基本上运行就靠谱了？其实并不是Haskell的程序真的免去了调试的这一步，而是因为这门语言经过了精心的设计，把本来在运行时才检查的事情给转到了编译时。当然这有一个不好的地方，就是我们用C语言来写一个程序的时候，虽然因为C语言抽象能力太差被迫写的很糟糕，但是我们总可以运行一点改一点，最终让他可以执行。Haskell就不一样了，只有能编译和不能编译两种状态，你要不断的修改程序，让他可以编译。一旦可以编译，一般就好了。Haskell的这种特性需要淡定的程序员才能使用。</p>
<p>为什么呢，因为Haskell是没有语句的，所以只要你修改了函数让他做了不一样的事情，那么函数的类型就会发生变化。那么所有依赖到这个函数的函数的类型也会发生变化。如果你改错了，那类型检查就会过不去，然后你的程序就不能编译了。<a href="http://channel9.msdn.com/tags/Erik+Meijer/" target="_blank">Erik Meijer</a>菊苣说得好，函数的类型才是表达函数业务逻辑的地方。而之所以要函数体，那是因为编译器不够聪明，得让你告诉他满足这个类型的最简单的解是什么。</p>
<p>所以如果我们在C++也采用这种写法的话——其实也就是把逻辑都交给template+偏特化，或者继承+visitor来做，那么也会有一样的效果，虽然并没有Haskell那么严格。一旦你进行了本质上的逻辑的变动，那你的类型一定会受到影响，那不满足类型要求的地方编译器就会帮你找出来。所以，当你看到一个因为这种情况而产生的编译错误的时候，心理要想：&#8220;<strong>好棒，编译器又给我找出了一个错误，避免我在运行的时候才苦逼的调试它！</strong>&#8221;</p>
<p>当然，模板的这些手法，可以很轻易地用在continuation passing style变换啊、combinator（很像设计模式但其实不是的东西）啊、async啊actor等各种强类型的物体上面，不过这些东西我今天就不打算细说了。当我们在做类似的事情的时候，我们要把类型设计成能表达业务逻辑的那种形式，从而让编译器查更多的东西，把运行时错误尽量化简为编译错误。</p>
<p>当然，C++的template比concept mapping还要更牛逼一个等级的，就是可以做type traits。如果是concept mapping是在对值通过类型进行分类采用不同的计算方法的话，那么type traits就是用来直接对类型进行计算的。那什么是对类型进行计算呢？我来举个例子。</p>
<p>譬如说我们要对一个vector进行排序，但是这个时候我们不像std::sort一样直接给出一个比较函数，而是通过从T拿出一个U当key的方法来做。譬如说对Student类排序，我们可以用他的学号、成绩、姓名等等任何一个属性来排序，这还是一个相当有用的作法。当然，我们总是可以写一个lambda表达式来做出一个用某个属性来比较Student类的函数，然后让std::sort来解决。很可惜的是，现在team的编译器还不够新，不支持C++11，怎么办呢？于是我们决定撸一个自己的sort函数（虽然这种事情是不推荐的，但反正是个例子，就不纠结这个了）：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename T, typename U&gt;
<span style="color: #0000ff">void</span> Sort(vector&lt;T&gt;&amp; ts, U(*<span style="color: #000000">key)(T))
{
    </span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> i=<span style="color: #800080">0</span>;i&lt;ts.size()-<span style="color: #800080">1</span>;i++<span style="color: #000000">)
        </span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> j=ts.size()-<span style="color: #800080">1</span>;j&gt;i;j--<span style="color: #000000">)
            </span><span style="color: #0000ff">if</span>(key(ts[j-<span style="color: #800080">1</span>]) &gt; key(ts[j<span style="color: #000000">]))
                swap(ts[j-<span style="color: #800080">1</span>], ts[j</span><span style="color: #000000">]);
}</span></pre></div></p>
<p>这段冒泡排序看起来没什么问题对吧，无论用什么语言最后写出来都肯定是这个样子的。于是我们写了几个单元测试，譬如说用sin来对double排序啦，求个负数实现逆序啦等等，觉得都没问题。于是开始投入实战了！我们写了下面的代码：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> GetScore(<span style="color: #0000ff">const</span> Student&amp;<span style="color: #000000"> st)
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> st.score;
}

vector</span>&lt;Student&gt;<span style="color: #000000"> students;
....
Sort(students, </span>&amp;GetScore); <span style="color: #008000">//</span><span style="color: #008000"> error</span></pre></div></p>
<p>为什么会错呢？因为GetScore函数接受的不是Student而是const Student&amp;！这下可麻烦了。我们有些函数接受的是T，有些函数接受的是const T&amp;，难道要写两个Sort嘛？当然这种代价肯定是不能接受的。于是我们想，如果可以从U(*key)(T)的T推导出vector里面装的是什么那该多好啊。反正无论函数接受的是string, string&amp; const string, const string&amp;，vector反正只能放string的。</p>
<p>这个时候就要祭出伟大的type traits了。怎么做呢？其实根上面的说法一样，我们把template看成是一个函数，输入是一个类型，输出再也不是值了，还是一个类型就好了：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename T&gt;
<span style="color: #0000ff">struct</span><span style="color: #000000"> RemoveCR
{
    typedef T Result;
};

template</span>&lt;typename T&gt;
<span style="color: #0000ff">struct</span> RemoveCR&lt;T&amp;&gt;<span style="color: #000000">
{
    typedef typename RemoveCR</span>&lt;T&gt;<span style="color: #000000">::Result Result;
};

template</span>&lt;typename T&gt;
<span style="color: #0000ff">struct</span> RemoveCR&lt;<span style="color: #0000ff">const</span> T&gt;<span style="color: #000000">
{
    typedef typename RemoveCR</span>&lt;T&gt;<span style="color: #000000">::Result Result;
};</span></pre></div></p>
<p>我们要怎么看这个过程呢？其实这是个pattern matching的过程，而pattern matching在一定程度上跟if-else其实是差不多的。所以我们看着一类的东西，心里不要总是想着他是个模板，而是要想，RemoveCR是个函数。所以当我们看见第一个RemoveCR的时候，我们心里浮现出来的景象大概是这个样子的：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">Type RemoveCR(Type T)
{
    </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">false</span><span style="color: #000000">);
    ....
<strong><font style="background-color: #ffff00">    </font></strong></span><strong><font style="background-color: #ffff00"><span style="color: #0000ff">else</span>
        <span style="color: #0000ff">return</span></font></strong><span style="color: #000000"><strong><font style="background-color: #ffff00"> T;</font></strong>
}</span></pre></div></p>
<p>好了，这个时候我们看见了第二个RemoveCR，那么这个RemoveCR函数又具体了一点：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">Type RemoveCR(Type T)
{
    </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">false</span><span style="color: #000000">);
<strong><font style="background-color: #ffff00">    </font></strong></span><strong><font style="background-color: #ffff00"><span style="color: #0000ff">else</span> <span style="color: #0000ff">if</span>(T <span style="color: #0000ff">is</span> T0&amp;</font></strong><strong><font style="background-color: #ffff00"><span style="color: #000000">)
        </span><span style="color: #0000ff">return</span></font></strong><span style="color: #000000"><strong><font style="background-color: #ffff00"> RemoveCR(T0);</font></strong>
    ....
    </span><span style="color: #0000ff">else</span>
        <span style="color: #0000ff">return</span><span style="color: #000000"> T;
}</span></pre></div></p>
<p>后来我们看到了第三个RemoveCR，发现定义到此结束了，于是RemoveCR函数的实际的样子就出来了：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #000000">Type RemoveCR(Type T)
{
    </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">false</span><span style="color: #000000">);
    </span><span style="color: #0000ff">else</span> <span style="color: #0000ff">if</span>(T <span style="color: #0000ff">is</span> T0&amp;<span style="color: #000000">)
        </span><span style="color: #0000ff">return</span><span style="color: #000000"> RemoveCR(T0);
<strong><font style="background-color: #ffff00">    </font></strong></span><strong><font style="background-color: #ffff00"><span style="color: #0000ff">else</span> <span style="color: #0000ff">if</span>(T <span style="color: #0000ff">is</span> <span style="color: #0000ff">const</span></font></strong><strong><font style="background-color: #ffff00"><span style="color: #000000"> T0)
        </span><span style="color: #0000ff">return</span></font></strong><span style="color: #000000"><strong><font style="background-color: #ffff00"> RemoveCR(T0);</font></strong>
    </span><span style="color: #0000ff">else</span>
        <span style="color: #0000ff">return</span><span style="color: #000000"> T;
}</span></pre></div></p>
<p>于是我们就可以做很多验证了，譬如说RemoveCR&lt;int&gt;::Result的结果是int，RemoveCR&lt;const int&amp;&gt;::Result的结果还是int。现在好了，我们可以修改我们的Sort函数了：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename T, typename U&gt;
<span style="color: #0000ff">void</span> Sort(vector&lt;typename RemoveCR&lt;T&gt;::Result&gt;&amp; ts, U(*<span style="color: #000000">key)(T))
{
    ....
}</span></pre></div></p>
<p>无论你的排序函数接受的是Student还是const Student&amp;，现在Sort函数都知道，你需要对vector&lt;Student&gt;进行排序。于是任务就完成了！</p>
<p>别的语言里面没有这种问题，是因为只有C++才会把const、volatile和&amp;这样的东西用来修饰一个类型而不是一个变量。这一点我第二篇文章已经讲过了，就不继续啰嗦了。所以说C++的设计虽然考虑得很周到，Bjarne Stroustrup菊苣也说过他不喜欢像别的语言的作者一样把自己的观点强加给用户。从这个出发点上来看，C++这一点相当好。只要你肯学习，又不会太蠢的话，总是可以学会C++的正确使用方法，正常使用C++来写代码的。但是，人真的蠢怎么办呢？Bjarne Stroustrup你这样歧视愚蠢的程序员是不对的，难道蠢就不能做程序员吗，难道学了go陷进去不能自拔的人就再也没有机会学习C++了吗！</p>
<p>关于type traits，haskell的type class（这东西就跟concept mapping一样）其实也有一部分这样的能力，可以帮你从type class的一部分类型参数推导出另一部分的类型参数。譬如说你这么写：
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span> MyClass a b c : a b =&gt;<span style="color: #000000"> c
  </span><span style="color: #0000ff">where</span> ....</pre></div></p>
<p>那么只要你实现了MyClass Int String Char，就不能实现MyClass Int String String了，因为a b =&gt; c这条规则已经限制了，(Int String)只能出现一次，c要完全被a和b决定。所以拥有这个=&gt;的haskell的type class也就可以有一部分type traits的功能了，虽然用法跟C++是截然不同的。</p>
<p>那C++的template除了泛型、concept和type traits之外，还有没有别的功能呢？当然有，我们不仅可以把类型放进模板的参数列表里面去，也可以把一个整数放进去。这个时候我们就可以用Int&lt;100&gt;来表达100，用Pair&lt;Int&lt;100&gt;, Pair&lt;Int&lt;200&gt;, Pair&lt;Int&lt;300&gt;, PairEnd&gt;&gt;&gt;来代表数组[100, 200, 300]，然后各种奇技淫巧就可以用来把模板写成一个不带类型的函数式语言了，在编译期什么东西都可以算。这个事情展开讲就太复杂了，而且也没什么用，你们感兴趣的话去看《C++ Template Metaprogramming》就行了。</p> <img src ="http://www.cppblog.com/vczh/aggbug/200195.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-05-12 16:31 <a href="http://www.cppblog.com/vczh/archive/2013/05/12/200195.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（三）&amp;mdash;&amp;mdash;什么是坑(面向对象和异常处理)</title><link>http://www.cppblog.com/vczh/archive/2013/05/05/199974.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sun, 05 May 2013 03:29:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/05/05/199974.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/199974.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/05/05/199974.html#Feedback</comments><slash:comments>16</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/199974.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/199974.html</trackback:ping><description><![CDATA[<p>在所有的文字之前，我需要强调一下，我本人对structure typing持反对态度，所以就算文中的内容&#8220;看起来很像&#8221;go的interface，读者们也最好不要觉得我是在赞扬go的interface。我比较喜欢的是haskell和rust的那种手法。可惜rust跟go一样恨不得把所有的单词都缩成最短，结果代码写出来连可读性都没有了，单词都变成了符号。如果rust把那乱七八糟的指针设计和go的那种屎缩写一起干掉的话，我一定会很喜欢rust的。同理，COM这个东西设计得真是太他妈正确了，简直就是学习面向对象手法的最佳范例，可惜COM在C++下面操作起来有点傻逼，于是很多人看见这个东西就呵呵呵了。</p> <p>上一篇文章说这次要写类成员函数和lambda的东西，不过我想了想，还是先把OO放前面，这样顺序才对。</p> <p>我记得我在读中学的时候经常听到的宣传，是面向对象的做法非常符合人类的思维习惯，所以人人喜欢，大行其道，有助于写出鲁棒性强的程序。如今已经过了十几年了，我发现网上再也没有这样的言论了，但是也没有跟反C++的浪潮一样拼命说面向对象这里不好那里不好要废除——明显人们还是觉得带面向对象的语言用起来还是比较爽的，不然也就没有那么多人去研究，一个特别合用来写functional programming的语言——javascript——是如何可以&#8220;模拟&#8221;面向对象语言里面的常用操作——new、继承和虚函数覆盖了。</p> <p>所以像面向对象这种定义特别简单的东西，语法上应该做不出什么坑的了。那今天的坑是什么呢？答案：是人。</p> <p>动态类型语言里面的面向对象说实话我也不知道究竟好在哪里，对于这种语言那来讲，只要做好functional programming的那部分，剩下的OO究竟要不要，纯粹是一个语法糖的问题。在动态类型语言里面，一个类和一个lambda expression的差别其实不大。</p> <p>那么静态类型语言里面的面向对象要怎么看待呢？首先我们要想到的一个是，凡是面向对象的语言都支持interface。C++虽然没有直接支持，但是他有多重继承，我们只需要写出一个纯虚类出来，就可以当interface用了。</p> <p>在这里我不得不说一下C++的纯虚类和interface的这个东西。假设一下我们有下面的C#代码： </p> <div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">interface</span><span style="color: #000000"> IButton{}
</span><span style="color: #0000ff">interface</span><span style="color: #000000"> ISelectableButton : IButton{}
</span><span style="color: #0000ff">interface</span><span style="color: #000000"> IDropdownButton : IButton{}
</span><span style="color: #0000ff">class</span><span style="color: #000000"> CheckBox : ISelectableButton{}

</span><span style="color: #0000ff">class</span><span style="color: #000000"> MyPowerfulButton : CheckBox, IDropdownButton
{
    </span><span style="color: #008000">//</span><span style="color: #008000"> 在这里我们只需要实现IDropdownButton里面比IButton多出来的那部分函数就够了。</span>
}</pre></div>
<p>我们先不管GUI是不是真的能这么写，我们就看看这个继承关系就好了。这是一个简单到不能再简单的例子。意思就是我有两种button的接口，我从一个实现里面扩展出一个支持另一种button接口的东西。但是大家都知道，我那个完美的GacUI用的是C++，那么在C++下面会遇到什么问题呢：</p>
<p>#region 抱怨</p>
<p>一般来说在C++里面用纯虚类来代替interface的时候，我们继承一个interface用的都是virtual继承。为什么呢？看上面那个例子，ISelectableButton继承自IButton，IDropdownButton继承自IButton。那么当你写一个MyPowerfulButton的时候，你希望那两个接口里面各自的IButton是不一样的东西吗？这当然不是。那如何让两个接口的IButton指向的是同一个东西呢？当然就是用virtual继承了。</p>
<p>好了，现在我们有CheckBox这个实现了ISelectableButton（带IButton）的类了，然后我们开始写MyPowerfulButton。会发生什么事情呢？</p>
<p>猜错了！答案是，其实我们可以写，但是Visual C++（gcc什么的你们自己玩玩就好了）会给我们一个warning，大意就是你IDropdownButton里面的IButton被CheckBox给覆盖了，再说抽象一点就是一个父类覆盖了另一个父类的虚函数。这跟virtual继承是没关系的，你怎么继承都会出这个问题。</p>
<p>但这其实也怪不了编译器，本来在其他情况下，虚函数这么覆盖自然是不好的，谁让C++没有interface这个概念呢。但是GUI经常会碰到这种东西，所以我只好无可奈何地在这些地方用#pragma来supress掉这个warning，反正我知道我自己在干什么。</p>
<p>C++没有interface的抱怨到这里就完了，但是virtual继承的事情到这里还没完。我再举一个例子： </p>
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> A
{
</span><span style="color: #0000ff">private</span><span style="color: #000000">:
    </span><span style="color: #0000ff">int</span><span style="color: #000000"> i;
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    A(</span><span style="color: #0000ff">int</span><span style="color: #000000"> _i)i:(_i){}
};

</span><span style="color: #0000ff">class</span> B : <span style="color: #0000ff">public</span> <span style="color: #0000ff">virtual</span><span style="color: #000000"> A
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    B(</span><span style="color: #0000ff">int</span><span style="color: #000000"> _i):A(_i){}
};

</span><span style="color: #0000ff">class</span> C : <span style="color: #0000ff">public</span> <span style="color: #0000ff">virtual</span><span style="color: #000000"> A
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    C(</span><span style="color: #0000ff">int</span><span style="color: #000000"> _i):A(_i){}
};

</span><span style="color: #0000ff">class</span> D : <span style="color: #0000ff">public</span> B, <span style="color: #0000ff">public</span><span style="color: #000000"> C
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    D():B(</span><span style="color: #800080">1</span>), C(<span style="color: #800080">2</span><span style="color: #000000">){}
};</span></pre></div>
<p>大家都是知道什么是virtual继承的，就是像上面这个例子，D里面只有一个A对象，B和C在D里面共享了A。那么，我们给B和C用了不同的参数来构造，难道一个A对象可以用不同的参数构造两次吗，还是说编译器帮我们随便挑了一个？</p>
<p>呵呵呵呵呵呵呵呵，我觉得C++的virtual继承就是这里非常反直觉——但是它的解决方法是合理的。反正C++编译器也不知道究竟要让B还是C来初始化A，所以你为了让Visual C++编译通过，你需要做的事情是： </p>
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #000000">D()
    : A(</span><span style="color: #800080">0</span>)  <span style="color: #008000">//</span><span style="color: #008000"> 参数当然是胡扯的，我只是想说，你在D里面需要显式地给A构造函数的参数</span>
    , B(<span style="color: #800080">1</span><span style="color: #000000">)
    , C(</span><span style="color: #800080">2</span><span style="color: #000000">)
{
}</span></pre></div>
<p>#endregion</p>
<p>大家估计就又开始吵了，C++干嘛要支持多重继承和virtual继承这两个傻逼东西呢？我在想，对于一个没有内建interface机制的语言，你要是没有多重继承和virtual继承，那用起来就跟傻逼一样，根本发挥不了静态类型语言的优势——<strong>让interface当contract</strong>。当然，我基本上用多重继承和virtual继承也是用来代替interface的，不会用来做羞耻play的。</p>
<p>当我们在程序里面拿到一个interface也好，拿到一个class也好，究竟这代表了一种什么样的精神呢？interface和class的功能其实是很相似的</p>
<p>interface IA：只要你拿到了一个IA，你就可以对她做很多很多的事情了，当然仅限大括号里面的！<br>class C : IA, IB：只要你拿到了一个C——哦不，你只能拿到interface不能拿到class的——反正意思就是，你可以对她做对IA和IB都可以做的事情了！</p>
<p>所以contract这个概念是很容易理解的，就是只要你跟她达成了contract，你就可以对她做这样那样的事情了。所以当一个函数返回给你一个interface的时候，他告诉你的是，函数运行完了你就可以做这样那样的事情。当一个函数需要一个interface的时候，他告诉你的是，你得想办法让我（函数）干这样那样的事情，我才会干活。</p>
<p>那class呢？class使用来实现interface的，不是给你直接用的。当然这是一个很理想的情况，可惜现在的语言糖不够甜，坚持这么做的话实在是太麻烦了，所以只好把某些class也直接拿来用了，GUI的控件也只好叫Control而不是IControl了。</p>
<p>其实说到底class和interface有什么区别呢？我们知道面向对象的一大特征就是封装，封装的意思就是封装状态。什么是状态呢？反正云风一直在说的&#8220;类里面的数据&#8221;就不是状态。我们先来看什么是数据： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> Point
{
    </span><span style="color: #0000ff">int</span><span style="color: #000000"> x;
    </span><span style="color: #0000ff">int</span><span style="color: #000000"> y;
};</span></pre></div>

<p>这就是典型的数据，你往x和y里面随便写什么东西都是没问题的，反正那只是一个点。那什么是状态呢： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> String
{
    wchar_t</span>*<span style="color: #000000"> buffer;
    </span><span style="color: #0000ff">int</span><span style="color: #000000"> length;
};</span></pre></div>

<p>String和Point有什么不一样呢？区别只有一个：String的成员变量之间是满足一个不变量的：wcslen(buffer) == length;</p>
<p>如果我们真的决定要给String加上这么个不变量的话，那这里面包含了两点：<br>1：buffer永远不是nullptr，所以他总是可以被wcslen(buffer)<br>2：length的值和buffer有直接的关系</p>
<p>如果你要表达一个空字符串，你总是可以写buffer=L&#8221;&#8221;，不过这就要你给String再加上一些数据来指明这个buffer需要如何被释放了，不过这是题外话了。我们可以假设buffer永远是new[]出来的——反正这里不关心它怎么释放。</p>
<p>这个不变量代表什么呢？意思就是说，无论你怎么折腾String，无论你怎么创建释放String，这个等式是一定要满足的。也就是说，作为String外部的&#8220;操作人员&#8221;，你应当没机会&#8220;观测&#8221;到这个String处于一个不满足不变量的状态。</p>
<p>所以这两个成员变量都不应该是public的。因为哪怕你public了他们其中的一个，你也会因为外部可以随意修改它而使他进入一个不满足不变量的状态。</p>
<p>这代表了，为了操作这些成员变量，我们需要public一些函数来给大家用。其实这也是contract，String的成员函数告诉我们，你可以对我（String）做很多很多的事情哦！</p>
<p>这同时也代表了，我们需要一个构造函数。因为如果我们在创建一个String之后，实例没有被正确初始化，那么他就处于了一个不满足不变量的状态，这就不满足上面说的东西了。有些人喜欢带一个Init函数和一个基本不干什么事情的构造函数，我想说的是，反正你构造完了不Init都不能用，你为什么非要我每次创建它的时候都立刻调用Init这么多次一举呢？而且你这样会使得我无法对于一个这样的函数f(shared_ptr&lt;ClassThatNeedsInit&gt; x)直接写f(make_shared(new ClassThatNeedInit))因为你的构造函数是残废的！</p>
<p>有些人会说，init没有返回值，我不知道他犯了错误啊——<strong>你可以用Exception</strong>！</p>
<p>还有些人会说，exception safe的构造函数好难写啊——<strong>学啊我艸</strong>！</p>
<p>但是这样仍然有些人会负隅顽抗，都这么麻烦了反正我可以用对Init和返回值就好了——<strong>你连exception safe的构造函数都不知道怎么写你怎么知道你可以&#8220;用对&#8221;它们</strong>？</p>
<p>#region 题外话展开</p>
<p>但是有些人就喜欢返回error，怎么办呢？其实我们都很讨厌Java那个checked exception的对吧，要抛什么exception都得在函数签名里面写，多麻烦啊。其实这跟error是一样的。一个exception是可以带有很多丰富的信息的——譬如说他的callstack什么的，还可以根据需要有很多其他的信息，总之不是一个int可以表达的。这就是为什么exception【通常】都是一个类。那如果我们不能用exception，但是也要返回一样多的信息怎么办？你只好把函数的返回值写得相当的复杂，譬如说： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> ErrorInfoForThisFunction
{
    xxxxxxxx
};

template</span>&lt;typename R, typename E&gt;
<span style="color: #0000ff">struct</span> ReturnValue <span style="color: #008000">//</span><span style="color: #008000"> C++没有好用的tuple就是卧槽</span>
<span style="color: #000000">{
    </span><span style="color: #0000ff">bool</span><span style="color: #000000"> hasError;
    R returnValue;
    E errorInfo;
};

ReturnValue</span>&lt;ReturnType, ErrorInfoForThisFunction&gt; ThisFunction( ... ); <span style="color: #008000">//</span><span style="color: #008000">我知道因为信息实在太多你们又要纠结返回struct还是它的指针还是ReturnValue里面的东西用指针还是用引用参数等等各种乱七八糟的事情了哈哈哈哈哈哈</span></pre></div>

<p>于是现在出问题了，我有一个ThatFunction调用ThisFunction，当错误是一种原因的时候我可以处理，当错误是另一种原因的时候我无法处理，所以在这种情况下我有两个选择：<br>1：把错误信息原封不断的返回<br>2：把ThisFunction的错误信息包装成ThatFunction的错误信息<br>不过我们知道其实这两种方法都一样，所以我们采用第一种： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> ErrorInfoForThatFunction
{
    yyyyyyyy
};

ReturnValue</span>&lt;ReturnType2, tuple&lt;ErrorInfoForThisFunction, ErrorForThatFunctio, <span style="color: #0000ff">bool</span> <span style="color: #008000">/*</span><span style="color: #008000">用来代表哪个是有效的</span><span style="color: #008000">*/</span>&gt; ThatFunction( ...  ); <span style="color: #008000">//</span><span style="color: #008000">数据越来越多我知道你们会对返回值纠结的越厉害</span></pre></div>

<p>你可能会说，为什么不把tuple包装成另一个struct？其实都一样，懒得写了。</p>
<p>我们知道，通常一个常见的几百人一起写的小软件都会有几百上千万行甚至几十G代码的，函数的调用层次只有几十层都已经很不错了。就算调用链里面只有10%的函数添加了自己的错误信息，那累积到最后肯定会很壮观的。而且只要底层的一个函数修改了错误信息，所有直接间接调用它的函数都会受到影响。</p>
<p>这简直就跟Java的checked exception一样嘛！</p>
<p>有些人会说，我们有error code就够了！<strong>我知道你们根本没有好好想&#8220;怎么做error recovery&#8221;这件事情</strong>。</p>
<p>有些人还会说（就在我微博上看见的），用error code就是代表可以不处理，我干嘛要费那么多心思搞你这些返回值的东西？<strong>我对这种人只能呵呵呵了，转行吧</strong>&#8230;&#8230;</p>
<p>这个时候我就会想，C++多好啊，我只要把ReturnValue&lt;ReturnType, ErrorInfoForThisFunction&gt;给改成ReturnType，然后在函数里面发生了错误还是构造一个ErrorInfoForThisFunction，然后直接给throw出来就好了。throw一个值我们还不用关心怎么释放它，多省事。对于ErrorInfoForThatFunction，我还可以让这两个struct都继承自同一个基struct（就是你们经常在别的语言里面看见的Exception类了），这样我在外面还可以直接catch(const 基struct&amp; ex)。</p>
<p>有些人会说，为什么不强制所有继承都继承自Exception？<strong>我知道你们就只是想catch了之后不理罢了，反正C++也有catch(&#8230;)你们偷着乐就行了</strong>。</p>
<p>用Exception有性能问题？<strong>反正在不发生错误的情况下，写了几句try也就只是操作了写在FS : [ 0 ]里面的一个链表而已，复制几个指针根本就不算什么影响</strong>。</p>
<p>C++的catch不能抓到Access Violation（也就是segmant fault？）？现在连最新的.net你来写catch(Exception ex)也抓不到AccessViolationException了。<strong>都AV了你的内存都搞得一团糟了，如果你这个时候还不备份数据dump自己然后退出重启（如果需要的话），那你接着执行代码，天知道会发生什么事情啊</strong>！连C#都觉得这么做危险了，C++只能更危险——所以用SEH抓下来dump自己然后进程自杀吧。Java还区分Error和Exception，虽然我不知道他具体代表什么，反正一般来说Exception有两种<br>1：可以预见的错误，譬如说Socket断开了所以Write就是败了给个Exception之类的<br>2：必须修改代码的错误，譬如说数组下标越界——<strong>这除了你写错以外根本没有别的原因，就应该挂掉，这时候你debug的时候才能立刻知道，然后改代码</strong>。</p>
<p>所以有三个基类然后最严重的那种不能catch我觉得也是一种好的选择。你可能会问，那C#在AV之后你又抓不到那怎么知道呢？答案：Application类有一个事件就是在发生这类事情的时候被调用的，在里面dump就好了。如果你非要抓AV，那也可以抓得到，就是有点麻烦&#8230;&#8230;</p>
<p>#endregion</p>
<p>说了这么多，无非就是因为一个类的构造函数——其实他真的是一个函数，只是函数名和类名一样了，这种事情在js里面反正经常出现——强制了你只能返回正确的时候的结果，于是有些人没办法加入error code了，又不知道怎么正确使用exception，只好搞出个C语言的封建社会残留思想Init函数来。其实我们知道，一旦有了Exception，函数签名里面的返回值就是他正确执行的时候返回的东西，这根构造函数一样。</p>
<p>C++的exception在构造函数里面不好，其实是因为一旦构造函数发生了异常，那代表这个类没构造完，所以析构函数是不会执行的。这在一定程度上给你写一个正确的构造函数（这也是&#8220;如何写一个正确的类&#8221;的其中一个方面）带来了麻烦，所以很多人到这里就呵呵呵了。</p>
<p>这就跟很多人学习SQL语言结果到group by这里就怎样也跨不过去了一样——人和人之间说没有差距这个不符合客观现实啊&#8230;&#8230;</p>
<p>不过我不否认，想写一个正确的C++程序是一件非常困难的事情，以至于连我在【只有我自己用的那部分library】里面都不是总是遵守各种各样的规则，反正我写的代码，我知道怎么用。不过公司的代码都是一大堆人一起写的，就像sqlserver一个组有一千多人一样（oracle是我们的十几倍，我们还能活下来真是太不容易了）——你能每个人都沟通到吗？撑死了就几十个吧，才不到10%。天知道别人会在代码里面干什么。所以写代码是不能太随便的。<strong>同理，招人也不能太随便，特别是你们这些连code review都不做的公司，你平时都不能阻止他checkin垃圾代码，你还敢招不合格的人吗</strong>？</p>
<p>现在我们回到面向对象的东西。Exception其实也应该算在contract里面，所以其实interface里面的函数会抛什么异常是需要明确的表达出来的。但是checked exception这个东西实在是太蠢了，因为这个规则是不能组合的，会导致上面说的error返回值一样的&#8220;接口信息大爆炸&#8221;。</p>
<p>所有不能组合的东西都是很难用的，譬如checked exception，譬如锁，譬如第一篇文章说的C语言那个碉堡了的函数指针数组作为参数的一个成员函数指针类型的声明什么的。</p>
<p>如果你不直接写出这个函数会抛exception，那要怎么办呢？方法有两个：<br>1：你给我把文档写好了，而且你，还有你，用这个library之前，给我RTFM！<br>2：就跟VisualStudio一样支持xml注释，这样VS就可以在你调用这个函数的时候用tooltip的形式提示你，你需要注意这些那些事情&#8230;&#8230;</p>
<p><strong>什么？你不用IDE？给我RTFM！你连文档都不看？滚！明天不要来上班了！</strong></p>
<p>突然发现本来要写面向对象的，结果Exception也写了相当长的一段。这件故事告诉我们，就算你不知道interface as contract是什么意思，你还能凑合写点能维护的代码。但是你Exception用得不好，程序就写成了渣，这个问题比较严重，所以写的也就比较多了。所以下面我们真正来谈contract的事情。需要注意的是，C++对这种东西是用你们很讨厌的东西来支持的——模板和偏特化。</p>
<p>contract的概念是很广泛的。对于面向对象语言来说，int这种东西其实也可以是一个类。你们不要老是想着编译后生成什么代码的事情，语法这种东西只是障眼法而已，编译出来的东西跟你们看到的可以是完全不同的。一个典型的例子就是尾递归优化了。还有C#的int虽然继承自object但是你直接用他的话生成出来的代码跟C++是没什么区别的——因为编译器什么后门都可以开！</p>
<p>那我们就拿int来说吧。int有一个很重要的特征，就是可以用来比较。C++怎么表达这个事情的呢？ 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">struct</span> <span style="color: #0000ff">int</span><span style="color: #000000">
{
    ......
    </span><span style="color: #0000ff">bool</span> <span style="color: #0000ff">operator</span>&lt;(<span style="color: #0000ff">int</span><span style="color: #000000"> i);
    ......
};</span></pre></div>

<p>如果你想写一个排序函数，内部想用&lt;来排序的话，你不需要在接口上写任何东西，你只需要假设那个typename T的T可以被&lt;就好了。所有带有&lt;的类型都可以被这个函数使用。这特别的structure typing，而且C++没有concept mapping，导致了你无法在接口上表达&#8220;这个类必须带&lt;&#8221;的这件事情，所以一旦你用错了，这错误信息只能跟烟雾一般缭绕了&#8230;&#8230;</p>
<p>concept mapping其实也是一个面向对象的特别好用特征不过这太高级了估计很多人都没用过——你们又不喜欢haskell和rust——那对于我们熟悉的面向对象的特性来讲，这样的事情要怎么表达呢？</p>
<p>于是我们伟大的先知Anders Hejlsberg菊苣就做了这么个决定（不是他干的也是他手下干的！） 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">interface</span> IComparable <span style="color: #008000">//</span><span style="color: #008000"> .net 1.0没有模板，只好搞了这么个傻逼东西出来</span>
<span style="color: #000000">{
    </span><span style="color: #0000ff">int</span> CompareTo(<span style="color: #0000ff">object</span> o); <span style="color: #008000">//</span><span style="color: #008000">具体的忘了，这是我根据记忆随便写的</span>
<span style="color: #000000">}

</span><span style="color: #0000ff">interface</span> IComparable&lt;T&gt;<span style="color: #000000">
{
    </span><span style="color: #0000ff">int</span><span style="color: #000000"> CompareTo(T o);
}

</span><span style="color: #0000ff">struct</span> Int32 : IComparable, IComarable&lt;T&gt;<span style="color: #000000"> ...
{
    ......
}</span></pre></div>

<p>所以你的排序函数只需要写成： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">void</span><span style="color: #000000"> Sort&lt;T&gt;(T[] data)
    </span><span style="color: #0000ff">where</span> T : IComparable&lt;T&gt;<span style="color: #000000">
{ ...... }</span></pre></div>

<p>看看IComparable&lt;int&gt;这个傻逼。我为什么要创建一个对象（IComparable&lt;int&gt;），他的职责就是跟另一个int作比较？这实在是太蠢了，无论怎么想都不能想出这种对象到底有什么存在的意义。不过因为C#没有concept mapping，于是看在interface as contract的份上，让interface来干这种事情其实也是很合理的。</p>
<p>所以contract这个东西又赋予了一个更加清晰的意义了。我们其实想要表达的事情是&#8220;我们可以对这个参数做什么事情&#8221;，而不是&#8220;这个参数是什么类型&#8221;。所以在这个Sort函数里面，这个T其实不代表任何事情，真正起到声明的作用的是where T : IComparable&lt;T&gt;这一句，指明了data数组里面的所有东西都是可以被排序的。那你可能会问，为什么不把IComparable&lt;T&gt;改成IComparable然后干脆把参数改成IComparable[] data呢？虽然说这样做的确更加&#8220;面向对象&#8221;，但是实在是太不现实了&#8230;&#8230;</p>
<p>本来面向对象这种概念就不是特别的可以表达客观现实，所以出现一些这种状况，也是正常的。</p>
<p>#region 题外话</p>
<p>看看这两个函数： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre><span style="color: #0000ff">void</span> Sort&lt;T&gt;(T[] data) <span style="color: #0000ff">where</span> T:IComparable&lt;T&gt;<span style="color: #000000">;
</span><span style="color: #0000ff">void</span> Sort(IComparable[] data);</pre></div>

<p>他们互相之间存在了一个特别优美的（数学意义上的）变换，发现没有，发现没有！所以对于动态类型语言（interface可以从代码里面得到）做一些&#8220;静态化&#8221;的优化的时候，就可以利用类似的技巧——咳咳，说太远了，赶紧打住。谁说懂点代数对编程没用的？哼！</p>
<p>#endregion</p>
<p>在这里我们终于看到了contract在带泛型的纯洁的面向对象语言里面的两种表达方法。你可能会想，<strong>我想在java里面干这种事情怎么办？还是换C#吧</strong>。那我们拿到一个class的时候，这代表什么呢？其实我们应该看成，其实我们拿到的是一个interface，只是他恰好只有一个实现。所以在这种时候，你最好不要依赖于&#8220;这个interface恰好只有一种实现而且我知道他是什么&#8221;的这个事情，否则程序写大了，你会发现你越来越不满足&#8220;面向interface编程&#8221;的这个原则，代码越来越难处理了。</p>
<p>我们可能会想到另一件事情，先知们跟我们说，当你设计函数参数的类型的时候，这个类型越基，哦不，越是在继承链里面距离object靠得越近越好，这是为什么呢？这其实也是一个interface as contract的问题。举个例子，我们需要对一个数组求和： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre>T Sum&lt;T&gt;(T[] data, Func&lt;T, T, T&gt; 加法函数); <span style="color: #008000">//</span><span style="color: #008000"> C#真的可以用中文变量的哦！</span></pre></div>

<p>费尽心思终于写好了，然后我们第二天发现，我们对List&lt;T&gt;也要做一样的事情。那怎么办呢？</p>
<p>在这里，Sum究竟对data有什么需求呢？其实研究一下就会发现，我们需要的只是想遍历data里面的所有内容而已。那data是不是一个数组，还是列表，还是一颗二叉树，还是你垃圾ipad里面的一些莫名其妙的数据也好，其实都一样。那C#里面什么interface代表&#8220;遍历&#8221;这个contract呢？当然是IEnumerable&lt;T&gt;了： 
<div class="cnblogs_code" style="border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; padding-bottom: 5px; padding-top: 5px; padding-left: 5px; border-left: #cccccc 1px solid; padding-right: 5px; background-color: #f5f5f5"><pre>T Sum&lt;T&gt;(IEnumerable&lt;T&gt; data, Func&lt;T, T, T&gt; 加法函数); <span style="color: #008000">//</span><span style="color: #008000"> C#真的可以用中文变量的哦！</span></pre></div>

<p>这样你的容器只要能够被foreach，也就是继承自IEnumearble&lt;T&gt;，就可以用这个函数了。这也是为什么&#8221;linq to 容器&#8221;基本上都是在IEnumerable上做的，因为他们只需要遍历。</p>
<p>哦，我们还说到了foreach。foreach是一个语句，用来遍历一个容器。那你如何表达一个容器可以被foreach拿来遍历的这个contract呢？还是IEnumerable&lt;T&gt;。interface拿来做contract的确是最佳匹配呀。</p>
<p>其实说了这么多，我只想表达一个东西。不要太在意&#8220;这个变量的类型是什么&#8221;，你要关心的是&#8220;我可以对这个变量做这样那样的事情&#8221;。这就是为什么我们会推荐&#8220;面向接口编程&#8221;，因为人总是懒散的，需要一些约束。interface不能用来new，不包含成员变量，刚好是contract的一种很好地表达方法。C++表达contract其实还可以用模板，不过这个就下次再说了。如果你们非得现在就知道到底怎么用的话，我就告诉你们，只要把C#的(i as IComparable&lt;int&gt;).CompareTo(j)换成Comparable&lt;int&gt;::Compare(i, j)就好了。</p>
<p>所以在可能的情况下，我们设计函数的参数和返回值的类型，也尽量用更基的那些类型。因为如果你跟上面的Sum一样，只关心遍历，那么你根本不应该要求你的参数可以被随机访问（数组就是这个意思）。</p>
<p>希望大家看完这篇文章之后可以明白很多我们在面向对象编程的时候，先知们建议的那些条款。当然这里还不涉及设计模式的东西。其实设计模式说白了是语法的补丁。设计模式这种东西用的最多，C#略少，你们会发现像listener模式这种东西C#就不常用，因为它有更好的东西——event。</p> <img src ="http://www.cppblog.com/vczh/aggbug/199974.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-05-05 11:29 <a href="http://www.cppblog.com/vczh/archive/2013/05/05/199974.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（二）&amp;mdash;&amp;mdash;什么是坑(b)</title><link>http://www.cppblog.com/vczh/archive/2013/04/28/199805.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sun, 28 Apr 2013 10:26:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/04/28/199805.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/199805.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/04/28/199805.html#Feedback</comments><slash:comments>17</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/199805.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/199805.html</trackback:ping><description><![CDATA[<p>我从来没有在别的语言的粉里面看见过这么容易展示人性丑陋一面的粉，就算是从十几年前开始的C++和C对喷，GC和非GC对喷，静态类型动态类型对喷的时候，甚至是云风出来喷C++黑得那么惊天动地的时候，都没有发生过这么脑残的事情。这种事情只发生在go语言的脑残粉的身上，这究竟代表什么呢？想学go语言的人最好小心一点了，学怎么用go没关系，go学成了因为受不了跳到别的语言去也没关系，就算是抖M很喜欢被折腾所以坚持用go也没关系，但是把自己学成了脑残粉，自己的心智发生不可逆转的变换，那就不好了。</p>
<p>当然，上一篇文章最后那个例子应该是我还没说清楚，所以有些人有这种&#8220;加上一个虚析构函数就可以了&#8221;的错觉也是情有可原的。Base* base = new Derived;之后你去delete没问题，是因为析构函数你还可以声明成虚的。但是Base* base = new Derived[10];之后你去delete[]发生了问题，是因为Derived和Base的长度不一样，所以当你开始试图计算&amp;base[1]的时候，你实际上是拿到了第一个Derived对象的中间的一个位置，根本不是第二个Derived。这个时候你在上面做各种操作（譬如调用析构函数），你连正确的this指针都拿不到，你再怎么虚也是没用的。不过VC++单纯做delete[]的话，在这种情况下是不会有问题的，我猜它内部不仅记录了数组的长度，还记录了每一个元素的尺寸。当然，你直接用bases[1]-&gt;DoSomething()的时候，出事是必须的。</p>
<p>所以今天粉丝群在讨论昨天的这个例子的时候，我们的其中一位菊苣就说了一句话：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>当你使用C++的时候C的一部分子集最好少碰</pre></div>
<p>我也很赞同。反正C++已经有各种内置类型了，譬如typeid出来的按个东西（我给忘了）啊，initialization_list啊，range什么的。为什么就不给new T[x]创建一个类型呢？不过反正都已经成为现实了，没事就多用用vector和shared_ptr吧，不要想着什么自己new自己delete了。</p>
<p>今天我们来讲一个稍微&#8220;高级&#8221;一点点的坑。这是我在工作之后遇到的一个现实的例子。当然，语言的坑都摆在那里，人往坑里面跳都肯定是因为自己知道的东西还不够多造成的。但是坑有三种，第一种是很明显的，只要遵守一些看起来很愚蠢但是却很有效的原则（譬如说if(1 == a)&#8230;）就可以去除的。第二种坑是因为你不知道一些高级的知识（譬如说lambda和变量揉在一起的生命周期的事情）从而跳坑的。第三种纯粹就是由于远见不够了&#8212;&#8212;譬如说下面的例子。</p>
<p>在春光明媚的一个早上，我接到了一个新任务，要跟另一个不是我们组的人一起写一个图像处理的pipeline的东西。这种pipeline的节点无非就是什么直方图啊，卷积啊，灰度还有取边缘什么的。于是第一天开会的时候，我拿到了一份spec，上面写好了他们设计好但是还没开始写的C++的interface（没错，就是那种就算只有一个实现也要用interface的那种流派），让我回去看一看，过几天跟他们一起把这个东西实现出来。当然，这些interface里面肯定会有矩阵：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename T&gt;
<span style="color: #0000ff">class</span><span style="color: #000000"> IMatrix
{
</span><span style="color: #0000ff">public</span><span style="color: #000000">:
    </span><span style="color: #0000ff">virtual</span> ~<span style="color: #000000">IMatrix(){}

    </span><span style="color: #0000ff">virtual</span> T* GetData()=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">int</span> GetRows()=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">int</span> GetColumns()=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">int</span> GetStride()=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> T Get(<span style="color: #0000ff">int</span> r, <span style="color: #0000ff">int</span> c)=<span style="color: #800080">0</span><span style="color: #000000">;
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Set(<span style="color: #0000ff">int</span> r, <span style="color: #0000ff">int</span> c, T t)=<span style="color: #800080">0</span><span style="color: #000000">;
};</span></pre></div>
<p>其实说实话，IMatrix这么写的确没什么大问题。于是我们就很愉快的工作了几天，然后把这些纯粹跟数学有关的算法都完成了，然后就开始做卷积的事情了。卷积所需要的那一堆数字其实说白了他不是矩阵，但因为为这种东西专门做一个类也没意义，所以我们就用行列一样多的矩阵来当filter。一开始的接口定义成这个样子，因为IBitmap可能有不同的储存方法，所以如何做卷积其实只有IBitmap的实现自己才知道：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename TChannel&gt;
<span style="color: #0000ff">class</span><span style="color: #000000"> IBitmap
{
......
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Apply(IMatrix&lt;<span style="color: #0000ff">float</span>&gt;&amp; filter)=<span style="color: #800080">0</span><span style="color: #000000">;
......
};</span></pre></div>
<p>于是我们又愉快的度过了几天，直到有一天有个人跳出来说：&#8220;Apply里面又不能修改filter，为什么不给他做成const的？&#8221;于是他给我们展示了他修改后的接口：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename TChannel&gt;
<span style="color: #0000ff">class</span><span style="color: #000000"> IBitmap
{
......
    </span><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Apply(IMatrix&lt;<span style="color: #0000ff">const</span> <span style="color: #0000ff">float</span>&gt;&amp; filter)=<span style="color: #800080">0</span><span style="color: #000000">;
......
};</span></pre></div>
<p>我依稀还记得我当时的表情就是这样子的&#8594;囧。</p>
<p>语言的类型系统是一件特别复杂的事情，特别是像C++这种，const T&lt;a, b, c&gt;和T&lt;const a, const b, cont c&gt;是两个不一样的类型的。一们语言，凡是跟优美的理论每一个不一致的地方都是一个坑，区别只是有些坑严重有些坑不严重。当然上面这个不是什么大问题，因为真的按照这个接口写下去，最后会因为发现创建不了IMatrix&lt;const float&gt;的实现而作罢。</p>
<p>而原因很简单，因为一般来说IMatrix&lt;T&gt;的实现内部都有一个T*代表的数组。这个时候给你换成了const float，你会发现，你的Set函数在也没办法把const float写进const float*了，然后就挂了。所以正确的方法当然是：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Apply(<span style="color: #0000ff">const</span> IMatrix&lt;<span style="color: #0000ff">float</span>&gt;&amp; filter)=<span style="color: #800080">0</span>;</pre></div>
<p>不过在展开这个问题之前，我们先来看一个更加浅显易懂的&#8220;坑&#8221;，是关于C#的值类型的。譬如说我们有一天需要做一个超高性能的包含四大力学的粒子运动模拟程序&#8212;&#8212;咳咳&#8212;&#8212;总之从一个Point类型开始。一开始是这么写的（C# 5.0）：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">struct</span><span style="color: #000000"> Point
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span><span style="color: #000000"> x;
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span><span style="color: #000000"> y;
}

</span><span style="color: #0000ff">var</span> ps = <span style="color: #0000ff">new</span> Point[] { <span style="color: #0000ff">new</span> Point { x = <span style="color: #800080">1</span>, y = <span style="color: #800080">2</span><span style="color: #000000"> } };
ps[</span><span style="color: #800080">0</span>].x = <span style="color: #800080">3</span>;</pre></div>
<p>&nbsp;</p>
<p>已开始运作的很好，什么事情都没有发生，ps[0]里面的Point也被很好的更改了。但是有一天，情况变了，粒子之间会开始产生和消灭新的粒子了，于是我把数组改成了List：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">var</span> ps = <span style="color: #0000ff">new</span> List&lt;Point&gt; { <span style="color: #0000ff">new</span> Point { x = <span style="color: #800080">1</span>, y = <span style="color: #800080">2</span><span style="color: #000000"> } };
ps[</span><span style="color: #800080">0</span>].x = <span style="color: #800080">3</span>;</pre></div>
<p>&nbsp;</p>
<p>结果编译器告诉我最后一行出了一个错误：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>Cannot modify the <span style="color: #0000ff">return</span> value of <span style="color: #800000">'</span><span style="color: #800000">System.Collections.Generic.List&lt;ArrayTest2.Program.Point&gt;.this[int]</span><span style="color: #800000">'</span> because it <span style="color: #0000ff">is</span> not a variable</pre></div>
<p>&nbsp;</p>
<p>C#这语言就是牛逼啊，我用了这么久，就只找出这个&#8220;不起眼的问题&#8221;的同时，还是一个编译错误，所以用C#的时候根本没有办法用错啊。不过想想，VB以前这么多人用，除了on error resume next以外也没用出什么坑，可见Microsoft设计语言的功力比某狗公司那是要强多了。</p>
<p>于是我当时就觉得很困惑，随手写了另一个类来验证这个问题：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> PointBox
{
    </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> Number { <span style="color: #0000ff">get</span>; <span style="color: #0000ff">set</span><span style="color: #000000">; }
    </span><span style="color: #0000ff">public</span> Point Point { <span style="color: #0000ff">get</span>; <span style="color: #0000ff">set</span><span style="color: #000000">; }
}

</span><span style="color: #0000ff">var</span> box = <span style="color: #0000ff">new</span> PointBox() { Number = <span style="color: #800080">1</span>, Point = <span style="color: #0000ff">new</span> Point { x = <span style="color: #800080">1</span>, y = <span style="color: #800080">2</span><span style="color: #000000"> } };
box.Number </span>+= <span style="color: #800080">3</span><span style="color: #000000">;
box.Point.x </span>= <span style="color: #800080">5</span>;</pre></div>
<p>&nbsp;</p>
<p>结果倒数第二行过了，倒数第一行还是编译错误了。为什么同样是属性，int就可以+=3，Point就不能改一个field非得创建一个新的然后再复制进去呢？后来只能得到一个结论，数组可以List不可以，属性可以+=不能改field（你给Point定义一个operator+，那你对box.Point做+=也是可以的），只能认为是语言故意这么设计的了。</p>
<p>写到这里，我想起以前在MSDN上看过的一句话，说一个结构，如果超过了16个字节，就建议最好不要做成struct。而且以前老赵写了一个小sample也证明大部分情况下用struct其实还不如用class快。当然至于是为什么我这里就不详细展开了，我们来讲语法上的问题。</p>
<p>在C#里面，struct和class的区别，就是值和引用的区别。C#专门做了值类型和引用类型，值类型不能转成引用（除非box成object或nullable或lazy等），引用类型不能转值类型。值不可以继承，引用可以继承。我们都知道，你一个类继承自另一个类，目的说到底都是为了覆盖几个虚函数。如果你不是为了覆盖虚函数然后你还要继承，八成是你的想法有问题。如果继承了，你就可以从子类的引用隐式转换成父类的引用，然后满足里氏代换原则。</p>
<p>但是C#的struct是值类型，也就是说他不是个引用（指针），所以根本不存在什么拿到父类引用的这个事情。既然你每一次见到的类型都是他真正的类型（而不像class，你拿到IEnumerable&lt;T&gt;，他可能是个List&lt;T&gt;），那也没有什么必要有虚函数了。如果你在struct里面不能写虚函数，那还要继承干什么呢？所以struct就不能继承。</p>
<p>然后我们来看一看C#的属性。其实C#的operator[]不是一个操作符，跟C++不一样，他是当成属性来看待的。属性其实是一个语法糖，其中的getter和setter是两个函数。所以如果一个属性的类型是struct，那么getter的返回值也是struct。一个函数返回struct是什么意思呢？当然是把结果【复制】一遍然后返回出去了。所以当我们写box.Point.x=5的时候，其实等价于box.get_Point().x=5。你拿到的Point是复制过的，你对一个复制过的struct来修改里面的x，自然不能影响box里面存放着的那个Point。所以这是一个无效语句，C#干脆就给你定了个编译错误了。不过你可能会问，List和Array大家都是operator[]也是一个属性，那为什么Array就可以呢？答案很简单，Array是有特殊照顾的&#8230;&#8230;</p>
<p>不过话说回来，为什么很少人遇到这个问题？想必是能写成struct的这些东西，作为整体来讲本身是一个状态。譬如说上面的Point，x和y虽然是分离的，但是他们并不独立代表状态，代表状态的是Point这个整体。Tuple（这是个class，不过其实很像struct）也一样，还有很多其他的.net framework里面定义的struct也一样。因此就算我们经常构造List&lt;Point&gt;这种东西，我们也很少要去单独修改其中一个element的一部分。</p>
<p>那为什么struct不干脆把每一个field都做成不可修改的呢？原因是这样做完全没有带来什么好处，反正你误操作了，总是会有编译错误的。还有些人可能会问，为什么在struct里面的方法里，对this的操作就会产生影响呢？这个问题问得太好了，因为this是一个本质上是&#8220;指针&#8221;的东西。</p>
<p>这就跟上一篇文章所讲的东西不一样了。这篇文章的两个&#8220;坑&#8221;其实不能算坑，因为他们最终都会引发编译错误来迫使你必须修改代码。所以说，如果C++的new T[x]返回的东西是一个货真价实的数组，那该多好啊。数组质检科从来没有什么转换的。就像Delphi的array of T也好，C#的T[]也好，C++的array&lt;T&gt;或者vector&lt;T&gt;也好，你从来都不能把一个T的数组转成U的数组，所以也就没有这个问题了。所以在<strong>用C++的时候，STL有的东西，你就不要自己撸了</strong>，只伤身体没好处的&#8230;&#8230;</p>
<p>那么回到一开始说的const的问题。我们在C++里面用const，一般都是有两个目的。第一个是用const引用来组织C++复制太多东西，第二个是用const指针来代表某些值是不打算让你碰的。但是一个类里面的函数会做什么我们并不知道，所以C++给函数也加上了const。这样对于一个const T的类型，你只能调用T里面所有标记了const的函数了。而且对于标记了const的成员函数，他的this指针也是const T* const类型的，而不是以前的T* const类型。</p>
<p>那类似的问题在C#里面是怎么解决的呢？首先第一个问题是不存在的，因为C#复制东西都是按bit复制的，你的struct无论怎么写都一样。其次，C#没有const类型，所以如果你想表达一个类不想让别人修改，那你就得把那些&#8220;const&#8221;的部分抽出来放在父类或父接口里面了。所以现在C#里面除了IList&lt;T&gt;类型以外，还有IReadOnlyList&lt;T&gt;。其实我个人觉得IReadOnlyList这个名字不好，因为这个对象说不定底下是个List，你用着用着，因为别人改了这个List导致你IReadOnlyList读出来的东西变了，迷惑性就产生了。所以在这种情况下，我宁可叫他IReadableList。他是Readable的，只是把write的接口藏起来的你碰不到而已。</p>
<p>所以，const究竟是在修饰什么的呢？如果是修饰类型的话，跟下面一样让函数的参数的类型都变成const，似乎完全是没有意义的：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> Add(<span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> a, <span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> b);</pre></div>
<p>&nbsp;</p>
<p>或者更甚，把返回值也改成const：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> Add(<span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> a, <span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> b);</pre></div>
<p>&nbsp;</p>
<p>那他跟<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> Add(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span> b);</pre></div>
<p>&nbsp;</p>
<p>究竟有什么区别呢？或许在函数内部你不能把参数a和b当变量用了。但是在函数的外部，其实这三个函数调用起来都没有任何区别。而且根据我们的使用习惯来讲，const修饰的应该不是一个类型，而是一个变量才对。我们不希望IBitmap::Apply函数里面会修改filter，所以函数签名就改成了：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> Apply(<span style="color: #0000ff">const</span> IMatrix&lt;<span style="color: #0000ff">float</span>&gt;&amp; filter)=<span style="color: #800080">0</span>;</pre></div>
<p>&nbsp;</p>
<p>我们不希望用宏来定义常数，所以我们会在头文件里面这么写：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> ADD = <span style="color: #800080">1</span><span style="color: #000000">;
</span><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> SUB = <span style="color: #800080">2</span><span style="color: #000000">;
</span><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> MUL = <span style="color: #800080">3</span><span style="color: #000000">;
</span><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> DIV = <span style="color: #800080">4</span><span style="color: #000000">;
</span><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> PUSH = <span style="color: #800080">5</span><span style="color: #000000">;
</span><span style="color: #0000ff">const</span> <span style="color: #0000ff">int</span> POP = <span style="color: #800080">6</span>;</pre></div>
<p>&nbsp;</p>
<p>或者干脆用enum：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">enum</span> <span style="color: #0000ff">class</span><span style="color: #000000"> Instructions
{
    ADD </span>= <span style="color: #800080">1</span><span style="color: #000000">,
    SUB,
    MUL,
    DIV,
    PUSH,
    POP
};</span></pre></div>
<p>&nbsp;</p>
<p>对于C++来讲，const还会对链接造成影响。整数数值类型的static const成员变量也好，const全局变量也好，都可以只写在头文件给一个符号，而不需要在cpp里面定义它的实体。但是对于非static const的成员变量来说，他又占用了class的一些位置（C#的const成员变量跟static是不相容的，它只是一个符号，跟C++完全不是一回事）。</p>
<p>而且根据大部分人对const的认识，我们用const&amp;也好，const*也好，都是为了修饰一个变量或者参数。譬如说一个临时的字符串：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">const</span> wchar_t* name = L<span style="color: #800000">"@</span><span style="color: #800000">GeniusVczh</span><span style="color: #800000">"</span>;</pre></div>
<p>&nbsp;</p>
<p>或者一个用来计算16进制编码的数组：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">const</span> wchar_t code[] = L<span style="color: #800000">"</span><span style="color: #800000">0123456789ABCDEF</span><span style="color: #800000">"</span>;</pre></div>
<p>&nbsp;</p>
<p>其实说到底，我们心目中的const都是为了修饰变量或者参数而产生的，说白了就是为了控制一个内存中的值是否可以被更改（这一点跟volatile一样，而C#的volatile还带fence语义，这一点做得比C++那个只用来控制是否可以被cache进寄存器的要强多了）。所以C++用const来修饰类型又是一个违反直觉的设计了。当然，如果去看《C++设计与演化》的话，的确可以从中找到一些讲为什么const会用来描述类型的原因。不过从我的使用经验上来看，const至少给我们带来了一些不方便的地方。</p>
<p>第一个就是让我们写一个正确的C++ class变得更难。就像C#里面说的，一个只读的列表，其实跟一个可读写的列表的概念是不一样的。在C++里面，一个只读的列表，是一个可以让你看见写函数却不让你用的一个进入了特殊状态的可读写的列表。一般来说，一个软件都要几千个人一起做。我今天写了一个类，你明天写了一个带const T&amp;参数的模板函数，后天他发现这两个东西凑在一起刚好能用，但是一编译发现那个类的所有成员函数都不带const结果没办法搞了。怎么办？重写吗，那我们得自己维护多出来的一份代码，还可能跟原类的作者犯下一样的错误。修改它的代码吗，鬼知道给一个函数加上const会不会给这个超大的软件的其他部分带来问题，说不定就像字符串类一样，有一些语义上是const的函数实际上需要修改一些成员变量结果你又不得不给那些东西加上mutable关键字了。你修改了之后，代码谁来维护，又成为一个跟技术无关的政治问题了。而且就算你弄明白了什么函数要加const，结果你声明一个const变量的时候const放错了位置，也会有一些莫名其妙的问题出现了。</p>
<p>如果从一开始就用C#的做法，把它分离成两个接口，这样做又跟C++有点格格不入，为什么呢？为什么STL那么喜欢泛型+值类型而不是泛型+引用类型？为什么C#就喜欢泛型+引用类型而不是泛型+值类型？其实这两种设计并没有谁好谁不好的地方，至于C++和C#有不同的偏爱，我想原因应该是出在GC上。语言有GC，你new的时候就不需要担心什么时候去delete，反正内存可以循环回收总是用不完的。C++却不行，内存一旦leak就永远的leak了，这么下去迟早都会挂掉的。所以当我们在C++和C#里面输入new这个关键字的时候，心情其实是差别相当大的。所以大家在C++里面就不喜欢用指针，而在C#里面就new的很开心。既然C++不喜欢指针，类似IReadOnlyList&lt;T&gt;的东西不拿指针直接拿来做值类型的话又是没有什么意义的，所以干脆就加上了const来&#8220;禁止你访问类里面的一部分东西&#8221;。于是每当你写一个类的时候，你就需要思考上一段所描述的那些问题。但是并不是所有C++的程序员都知道所有的这些细节的，所以后面加起来，总会有傻逼的时候&#8212;&#8212;<strong>当然这并不怪C++，怪的是你面试提出的太容易，让一些不合格的程序员溜进来了。C++不是谁都可以用的。</strong></p>
<p>第二个问题就是，虽然我们喜欢在参数上用const T&amp;来避免无谓的复制，但是到底在函数的返回值上这么做对不对呢？const在返回值的这个问题上这是一把双刃剑。我自己写过一个linq for C++，山寨了一把IEnumerable和IEnumerator类，在Current函数里面我返回的就是一个const T&amp;。本来容器自己的IEnumerator写的挺好，因为本来返回的东西就在容器里面，是有地址的。但是开始写Select和Where的时候就傻逼了。我为了正确返回一个const T&amp;，我就得返回一个带内存地址的东西，当然最终我选择了在MoveNext的时候把结果cache在了这个SelectEnumerator的成员变量里面。<strong>当然这样做是有好处的，因为他强迫我把所有计算都放在MoveNext里面，而不会偷懒写在Current里。</strong>但是总的来说，要不是我写代码的时候蛋定，说不定什么时候就掉坑里了。</p>
<p>总的来说，引入const让我们写出一个正确的C++程序的难度变大了。const并不是一无是处，如果你是在想不明白什么时候要const什么时候不要，那你大不了不要在自己的程序里面用const就好了。当然我在这里并不是说C语言什么都没有就比C++好。<strong>一个语言是不可能通过删掉什么来让他变得更好的。</strong>C语言的抽象能力实在是太低了，以至于让我根本没办法安心做好逻辑部分的工作，而总要关心这些概念究竟要用什么样的扭曲的方法才能在C语言里面比较顺眼的表达出来（我知道你们最后都选择了宏！是吧！是吧！），从而让我变&#8220;烦&#8221;，bug就变多，程序到最后也懒得写好了，最后变成了一坨屎。</p>
<p>嘛，当然如果你们说我没有linus牛逼，那我自然也没办法说什么。但是C语言大概就是那种只有linus才能用的顺手的语言了。C++至少如果你心态好的话，没事多用STL，掉坑的概率就要比直接上C语言小多了。</p>
<p>语言的坑这种事情实在是罄竹难书啊，本来以为两篇文章就可以写完的，结果发现远远不够。看在文章长度的份上，今天就到此为止了，下一篇文章还有大家喜闻乐见的函数指针和lambda的大坑等着你们&#8230;&#8230;</p>
<p><font size="5"><strong>待续</strong></font></p> <img src ="http://www.cppblog.com/vczh/aggbug/199805.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-04-28 18:26 <a href="http://www.cppblog.com/vczh/archive/2013/04/28/199805.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何设计一门语言（一）&amp;mdash;&amp;mdash;什么是坑(a)</title><link>http://www.cppblog.com/vczh/archive/2013/04/27/199765.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 27 Apr 2013 09:24:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/04/27/199765.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/199765.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/04/27/199765.html#Feedback</comments><slash:comments>37</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/199765.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/199765.html</trackback:ping><description><![CDATA[<p>这个系列的起因是这样的，王垠写了一篇喷go的博客<a href="http://www.yinwang.org/blog-cn/2013/04/24/go-language/" target="_blank">http://www.yinwang.org/blog-cn/2013/04/24/go-language/</a>，里面说go已经烂到无可救药了，已经懒得说了，所以让大家去看<a href="http://www.mindomo.com/view.htm?m=8cc4f95228f942f8886106d876d1b041" target="_blank">http://www.mindomo.com/view.htm?m=8cc4f95228f942f8886106d876d1b041</a>，里面有详细的解释。然后这篇东西被发上了微博，很多博友立刻展示了人性丑陋的一面：<br />1、那些go的拥护者们，因为go被喷了，就觉得自己的人格受到了侮辱一样，根本来不及看到最后一段的链接，就开始张牙舞爪。<br />2、王垠这个人的确是跟人合不来，所以很多人就这样断定他的东西&#8220;毫无参考价值&#8221;。 
<p>不过说实话，文章里面是喷得有点不礼貌，这也在一定程度上阻止了那些不学无术的人们继续阅读后面的精华部分。如果所有的文章都这样那该多好啊，那么烂人永远都是烂人，不纠正自己的心态永远获得不了任何有用的知识，永远过那种月入一蛆的日子，用垃圾的语言痛苦的写一辈子没价值的程序。 
<p>废话就说到这里了，下面我来说说我自己对于语言的观点。为什么要设计一门新语言？原因无非就两个，要么旧的语言实在是让人受不了，要么是针对领域设计的专用语言。后一种我就不讲了，因为如果没有具体的领域知识的话，这种东西永远都做不好（譬如SQL永远不可能出自一个数据库很烂的人手里），基本上这不是什么语言设计的问题。所以这个系列只会针对前一种情况&#8212;&#8212;也就是设计一门通用的语言。通用的语言其实也有自己的&#8220;领域&#8221;，只是太多了，所以被淡化了。纵观历史，你让一个只做过少量的领域的人去设计一门语言，如果他没有受过程序设计语言理论的系统教育，那只能做出屎。譬如说go就是其中一个&#8212;&#8212;虽然他爹很牛逼，但反正不包含&#8220;设计语言&#8221;这个事情。 
<p>因此，在21世纪你还要做一门语言，无非就是对所有的通用语言都不满意，所以你想自己做一个。不满意体现在什么方面？譬如说C#的原因可能就是他爹不够帅啦，譬如说C++的原因可能就是自己智商太低hold不住啦，譬如说Haskell的原因可能就是用的人太少招不到人啦，譬如说C的原因可能就是实在是无法完成人和抽象所以没有linus的水平的人都会把C语言写成屎但是你又招不到linus啦，总之有各种各样的原因。不过排除使用者的智商因素来讲，其实有几个语言我还是很欣赏的&#8212;&#8212;C++、C#、Haskell、Rust和Ruby。如果要我给全世界的语言排名，前五名反正是这五个，虽然他们之间可能很难决出胜负。不过就算如此，其实这些语言也有一些让我不爽的地方，让我一直很想做一个新的语言（来给自己用（？）），证据就是&#8212;&#8212;&#8220;看我的博客&#8221;。 
<p>那么。一个好的语言的好，体现在什么方面呢？一直以来，人们都觉得，只有库好用，语言才会好用。其实这完全是颠倒了因果关系，如果没有好用的语法，怎么能写出好用的库呢？要找例子也很简单，只要比较一下Java和C#就够了。C#的库之所以好用，跟他语言的表达能力强是分不开的，譬如说linq（，to xml，to sql，to parser，etc），譬如说WCF（仅考虑易用性部分），譬如说WPF。Java能写得出来这些库吗？硬要写还是可以写的，但是你会发现你无论如何都没办法把他们做到用起来很顺手的样子，其实这都是因为Java的语法垃圾造成的。这个时候可以抬头看一看我上面列出来的五种语言，他们的特点都是&#8212;&#8212;因为语法的原因，库用起来特别爽。 
<p>当然，这并不要求所有的人都应该把语言学习到可以去写库。程序员的分布也是跟金字塔的结构一样的，库让少数人去写就好了，大多数人尽管用，也不用学那么多，除非你们想成为写库的那些。不过最近有一个很不好的风气，就是有些人觉得一个语言难到自己无法【轻松】成为写库的人，就开始说他这里不好那里不好了，具体都是谁我就不点名了，大家都知道，呵呵呵。 
<p>好的语言，除了库写起来又容易又好用以外，还有两个重要的特点：容易学，容易分析。关于容易学这一点，其实不是说，你随便看一看就能学会，而是说，只要你掌握了门道，很多未知的特性你都可以猜中。这就有一个语法的一致性问题在里面了。语法的一致性问题，是一个很容易让人忽略的问题，因为所有因为语法的一致性不好而引发的错误，原因都特别的隐晦，很难一眼看出来。这里我为了让大家可以建立起这个概念，我来举几个例子。 
<p>第一个例子是我们喜闻乐见的C语言的指针变量定义啦： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> a, *b, **c;</pre></div>
<p>相信很多人都被这种东西坑过，所以很多教科书都告诉我们，当定义一个变量的时候，类型最后的那些星号都要写在变量前面，避免让人误解。所以很多人都会想，为什么要设计成这样呢，这明显就是挖个坑让人往下跳嘛。但是在实际上，这是一个语法的一致性好的例子，至于为什么他是个坑，问题在别的地方。</p>
<p>我们都知道，当一个变量b是一个指向int的指针的时候，*b的结果就是一个int。定义一个变量int a;也等于在说&#8220;定义a是一个int&#8221;。那我们来看上面那个变量声明：int *b;。这究竟是在说什么呢？其实真正的意思是&#8220;定义*b是一个int&#8221;。这种&#8220;定义和使用相一致&#8221;的方法其实正是我们要推崇的。C语言的函数定义参数用逗号分隔，调用的时候也用逗号分隔，这是好的。Pascal语言的函数定义参数用分号分隔，调用的时候用逗号分隔，这个一致性就少了一点。</p>
<p>看到这里你可能会说，你怎么知道C语言他爹就是这么想的呢？我自己觉得如果他不是这么想的估计也不会差到哪里去，因为还有下面一个例子：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span> F(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span><span style="color: #000000"> b);
</span><span style="color: #0000ff">int</span> (*f)(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span><span style="color: #000000"> b</span>);</pre></div>
<p>这也是一个&#8220;定义和使用相一致&#8221;的例子。就第一行代码来说，我们要如何看待&#8220;int F(int a, int b);&#8221;这个写法呢？其实跟上面一样，他说的是&#8220;定义F(a, b)的结果为int&#8221;。至于a和b是什么，他也告诉你：定义a为int，b也为int。所以等价的，下面这一行也是&#8220;定义(*f)(a, b)的结果为int&#8221;。函数类型其实也是可以不写参数名的，不过我们还是鼓励把参数名写进去，这样Visual Studio的intellisense会让你在敲&#8220;(&#8221;的时候把参数名给你列出来，你看到了提示，有时候就不需要回去翻源代码了。</p>
<p>关于C语言的&#8220;定义和使用相一致&#8221;还有最后一个例子，这个例子也是很美妙的：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">int</span><span style="color: #000000"> a;
typedef </span><span style="color: #0000ff">int</span><span style="color: #000000"> a;

</span><span style="color: #0000ff">int</span> (*f)(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span><span style="color: #000000"> b);
typedef </span><span style="color: #0000ff">int</span> (*f)(<span style="color: #0000ff">int</span> a, <span style="color: #0000ff">int</span> b);</pre></div>
<p>typedef是这样的一个关键字：他把一个符号从变量给修改成了类型。所以每当你需要给一个类型名一个名字的时候，就先想一想，怎么定义一个这个类型的变量，写出来之后往前面加个typedef，事情就完成了。</p>
<p>不过说实话，就一致性来讲，C语言也就到此为止了。至于说为什么，因为上面这几条看起来很美好的&#8220;定义和使用相一致&#8221;的规则是不能组合的，譬如说看下面这一行代码：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><font color="#4b4b4b">typedef</font> <span style="color: #0000ff">int</span>(__stdcall*f[<span style="color: #800080">10</span>])(<span style="color: #0000ff">int</span>(*a)(<span style="color: #0000ff">int</span>, <span style="color: #0000ff">int</span>));</pre></div>这究竟是个什么东西呢，谁看得清楚呀！而且这也没办法用上面的方法来解释了。究其原因，就是C语言采用的这种&#8220;定义和使用相一致&#8221;的手法刚好是一种解方程的手法。譬如说int *b;定义了&#8220;*b是int&#8221;，那b是什么呢，我们看到了之后，都得想一想。人类的直觉是有话直说开门见山，所以如果我们知道int*是int的指针，那么int* b也就很清楚了&#8212;&#8212;&#8220;b是int的指针&#8221;。&nbsp;<br /><br />因为C语言的这种做法违反了人类的直觉，所以这条本来很好的原则，采用了错误的方法来实现，结果就导致了&#8220;坑&#8221;的出现。因为大家都习惯&#8220;int* a;&#8221;，然后C语言告诉大家其实正确的做法是&#8220;int *a;&#8221;，那么当你接连的出现两三个变量的时候，问题就来了，你就掉坑里去了。 
<p>这个时候我们再回头看一看上面那一段长长的函数指针数组变量的声明，会发现其实在这种时候，C语言还是希望你把它看成&#8220;int* b;&#8221;的这种形式的：f是一个数组，数组返回了一个函数指针，函数返回int，函数的参数是int(*a)(int, int)所以他还是一个函数指针。</p>
<p>我们为什么会觉得C语言在这一个知识点上特别的难学，就是因为他同时混用了两种原则来设计语法。那你说好的设计是什么呢？让我们来看看一些其它的语言的作法：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>C++<span style="color: #000000">:
function</span>&lt;<span style="color: #0000ff">int</span> __stdcall(function&lt;<span style="color: #0000ff">int</span>(<span style="color: #0000ff">int</span>, <span style="color: #0000ff">int</span>)&gt;)&gt; f[<span style="color: #800080">10</span><span style="color: #000000">];

C#:
Func</span>&lt;Func&lt;<span style="color: #0000ff">int</span>, <span style="color: #0000ff">int</span>, <span style="color: #0000ff">int</span>&gt;, <span style="color: #0000ff">int</span>&gt;<span style="color: #000000">[] f;

Haskell:
f :: [(</span><span style="color: #0000ff">int</span>-&gt;<span style="color: #0000ff">int</span>-&gt;<span style="color: #0000ff">int</span>)-&gt;<span style="color: #0000ff">int</span><span style="color: #000000">]

Pascal:
</span><span style="color: #0000ff">var</span> f : array[<span style="color: #800080">0</span>..<span style="color: #800080">9</span>] of function(a : function(x : integer; y : integer):integer):integer;</pre></div>
<p>&nbsp;</p>
<p>这些语言的做法，虽然并没有遵守&#8220;定义和使用相一致&#8221;的原则，但是他们比C语言好的地方在于，他们只采用一种原则&#8212;&#8212;这就比好的和坏的混在一起要强多了（这一点go也是，做得比C语言更糟糕）。</p>
<p>当然，上面这个说法对Haskell来说其实并不公平。Haskell是一种带有完全类型推导的语言，他不认为类型声明是声明的一部分，他把类型声明当成是&#8220;提示&#8221;的一部分。所以实际上当你真的需要一个这种复杂结构的函数的时候，实际上你并不会真的去把它的类型写出来，而是通过写一个正确的函数体，然后让Haskell编译器帮你推导出正确的类型。我来举个例子：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>superApply fs x = (foldr id (.) fs) x</pre></div>
<p>&nbsp;</p>
<p>关于foldr有一个很好的理解方法，譬如说foldr 0 (+) [1,2,3,4]说的就是1 + (2 + (3 + (4 + 0)))。而(.)其实是一个把两个函数合并成一个的函数：f (.) g = \x-&gt;f(g( x ))。所以上述代码的意思就是，如果我有下面的三个函数： 
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>add1 x = x + <span style="color: #800080">1</span><span style="color: #000000">
mul2 x </span>= x * <span style="color: #800080">2</span><span style="color: #000000">
sqr x </span>= x * x</pre></div>
<p>&nbsp;</p>
<p>那么当我写下下面的代码的时候：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>superApply [sqr, mul2, add1] <span style="color: #800080">1</span></pre></div>的时候，他做的其实是sqr(mul2(add1(1)) = ((1+1)*2) * ((1+1)*2) = 16。当然，Haskell还可以写得更直白：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>superApply [(\x-&gt;x*x), (*<span style="color: #800080">2</span>), (+<span style="color: #800080">1</span>)] <span style="color: #800080">1</span></pre></div>
<p>&nbsp;</p>
<p>Haskell代码的简洁程度真是丧心病狂啊，因为如果我们要用C++来写出对应的东西的话（C语言的参数无法是一个带长度的数组类型所以其实是写不出等价的东西的），会变成下面这个样子：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>template&lt;typename T&gt;<span style="color: #000000">
T SuperApply(</span><span style="color: #0000ff">const</span> vector&lt;function&lt;T(T)&gt;&gt;&amp; fs, <span style="color: #0000ff">const</span> T&amp;<span style="color: #000000"> x)
{
    T result </span>=<span style="color: #000000"> x;
    </span><span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> i=fs.size()-<span style="color: #800080">1</span>; i&gt;=<span style="color: #800080">0</span>; i--<span style="color: #000000">)
    {
        result </span>=<span style="color: #000000"> fs[i](result);
    }
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> result;
}</span></pre></div>
<p>&nbsp;</p>
<p>C++不仅要把每一个步骤写得很清楚，而且还要把类型描述出来，整个代码就变得特别的混乱。除此之外，C++还没办法跟Haskell一样吧三个函数直接搞成一个vector然后送进这个SuperApply里面直接调用。当然有人会说，这还不是因为Haskell里面有foldr嘛。那让我们来看看同样有foldr（reverse + aggregate = foldr）的C#会怎么写：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>T SuperApply&lt;T&gt;(Func&lt;T, T&gt;<span style="color: #000000">[] fs, T x)
{
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> (fs
        .Reverse()
        .Aggregate(x</span>=&gt;x, (a, b)=&gt;y=&gt;<span style="color: #000000">b(a(y)))
        )(x);
}</span></pre></div>
<p>&nbsp;</p>
<p>C#基本上已经达到跟Haskell一样的描述过程了，而且也可以写出下面的代码了，就是无论声明和使用的语法的噪音稍微有点大&#8230;&#8230;<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>SuperApply(<span style="color: #0000ff">new</span> Func&lt;T, T&gt;<span style="color: #000000">[]{
    x</span>=&gt;x*<span style="color: #000000">x,
    x</span>=&gt;x*<span style="color: #800080">2</span><span style="color: #000000">,
    x</span>=&gt;x+<span style="color: #800080">1</span><span style="color: #000000">
    }, </span><span style="color: #800080">1</span>);</pre></div>
<p>&nbsp;</p>
<p>为什么要在讨论语法的一致性的时候说这些问题呢，在这里我想向大家展示Haskell的另一种&#8220;定义和使用相一致&#8221;的做法。Haskell整个语言都要用pattern matching去理解，所以上面的这段代码<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>superApply fs x = (foldr id (.) fs) x</pre></div>说的是，凡是你出现类似superApply a b的这种&#8220;pattern&#8221;，你都可以把它当成(foldr id (.) a) b来看。譬如说<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>superApply [(\x-&gt;x*x), (*<span style="color: #800080">2</span>), (+<span style="color: #800080">1</span>)] <span style="color: #800080">1</span></pre></div>其实就是<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>(foldr id (.) [(\x-&gt;x*x), (*<span style="color: #800080">2</span>), (+<span style="color: #800080">1</span>)]) <span style="color: #800080">1</span></pre></div>只要superApply指的是这个函数，那无论在什么上下文里面，<strong>你都可以放心的做这种替换而程序的意思绝对不会有变化</strong>&#8212;&#8212;这就是haskell的带有一致性的原则。那让我们来看看Haskell是如何执行他这个一致性的。在这里我们需要知道一个东西，就是如果我们有一个操作符+，那我们要把+当成函数来看，我们就要写(+)。如果我们有一个函数f，如果我们要把它当成操作符来看，那就要写成`f`（这是按键！左边的那个符号）。因此Haskell其实允许我们做下面的声明：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>(Point x y) + (Point z w) = Point (x+z) (y+<span style="color: #000000">w)
(</span>+) (Point x y) (Point z w) = Point (x+z) (y+<span style="color: #000000">w)

(Point x y) `Add` (Point z w) </span>= Point (x+z) (y+<span style="color: #000000">w)
Add (Point x y) (Point z w) </span>= Point (x+z) (y+w)</pre></div>
<p>&nbsp;</p>
<p>斐波那契数列的简单形式甚至还可以这么写：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>f <span style="color: #800080">1</span> = <span style="color: #800080">1</span><span style="color: #000000">
f </span><span style="color: #800080">2</span> = <span style="color: #800080">1</span><span style="color: #000000">
f (n</span>+<span style="color: #800080">2</span>) = f(n+<span style="color: #800080">1</span>) + f(n)</pre></div>
<p>&nbsp;</p>
<p>甚至连递归都可以写成：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>GetListLength [] = <span style="color: #800080">0</span><span style="color: #000000">
GetListLength (x:xs) </span>= <span style="color: #800080">1</span> + GetListLength xs</pre></div>
<p>&nbsp;</p>
<p>Haskell到处都贯彻了&#8220;函数和操作符的替换关系&#8221;和&#8220;pattern matching&#8221;两个原则来做&#8220;定义和实现相一致&#8221;的基础，从而实现了一个比C语言那个做了一半的混乱的原则要好得多的原则。</p>
<p>有些人可能会说，Haskell写递归这么容易，那会不会因为鼓励人们写递归，而整个程序充满了递归，很容易stack overflow或者降低运行效率呢？在这里你可以往上翻，在这篇文章的前面有一句话&#8220;好的语言，除了库写起来又容易又好用以外，还有两个重要的特点：容易学，容易分析。&#8221;，这在Haskell里面体现得淋漓尽致。</p>
<p>我们知道循环就是尾递归，所以如果我们把代码写成尾递归，那Haskell的编译器就会识别出来，从而在生成x86代码的时候把它处理成循环。一个尾递归递归函数的退出点，要么是一个不包含自身函数调用的表达式，要么就是用自身函数来和其它参数来调用。听起来比较拗口，不过说白了其实就是：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre>GetListLength_ [] c =<span style="color: #000000"> c
GetListLength_ (x:xs) c </span>= GetListLength_ xs (c+<span style="color: #800080">1</span><span style="color: #000000">)
GetListLength xs </span>= GetListLength_ xs <span style="color: #800080">0</span></pre></div>
<p>&nbsp;</p>
<p>当你写出这样的代码的时候，Haskell把你的代码编译了之后，就会真的输出一个循环，从而上面的担心都一扫而空。</p>
<p>实际上，有很多性能测试都表明，在大多数平台上，Haskell的速度也不会被C/C++慢超过一倍的同时，要远比go的性能高出许多。在Windows上，函数式语言最快的是F#。Linux上则是Scala。Haskell一直都是第二名，但是只比第一名慢一点点。</p>
<p>为了不让文章太长，好分成若干次发布，每次间隔都较短，所以今天的坑我只想多讲一个&#8212;&#8212;C++的指针的坑。剩下的坑留到下一篇文章里面。下面要讲的这个坑，如果不是在粉丝群里面被问了，我还不知道有人会这么做：<br />
<div style="border-bottom: #cccccc 1px solid; border-left: #cccccc 1px solid; padding-bottom: 5px; background-color: #f5f5f5; padding-left: 5px; padding-right: 5px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; padding-top: 5px" class="cnblogs_code"><pre><span style="color: #0000ff">class</span><span style="color: #000000"> Base
{
  ...
};

</span><span style="color: #0000ff">class</span> Derived : <span style="color: #0000ff">public</span><span style="color: #000000"> Base
{
  ...
};

Base</span>* bs = <span style="color: #0000ff">new</span> Derived[<span style="color: #800080">10</span><span style="color: #000000">];
delete[] bs;</span></pre></div>
<p>&nbsp;</p>
<p>我想说，这完全是C++兼容C语言，然后让C语言给坑了。其实这个问题在C语言里面是不会出现的，因为C语言的指针其实说白了只有一种：char*。很多C语言的函数都接受char*，void*还是后来才有的。C语言操作指针用的malloc和free，其实也是把他当char*在看。所以当你malloc了一个东西，然后cast成你需要的类型，最后free掉，这一步cast存在不存在对于free能否正确执行来说是没有区别的。</p>
<p>但是事情到了C++就不一样了。C++有继承，有了继承就有指针的隐式类型转换。于是看上面的代码，我们new[]了一个指针是Derived*类型的，然后隐式转换到了Base*。最后我们拿他delete[]，因为delete[]需要调用析构函数，但是Base*类型的指针式不能正确计算出Derived数组的10个析构函数需要的this指针的位置的，所以在这个时候，代码就完蛋了（如果没完蛋，那只是巧合）。</p>
<p>为了兼容C语言，&#8220;new[]的指针需要delete[]&#8221;和&#8220;子类指针可以转父类指针&#8221;的两条规则成功的冲突到了一起。实际上，如果需要解决这种问题，那类型应该怎么改呢？其实我们可以跟C#一样引入Derived[]的这种指针类型。这还是new[]出来的东西，C++里面也可以要求delete[]，但是区别是他再也不能转成Base[]了。只可惜，T[]这种类型被C语言占用了，在函数参数类型里面当T*用。C语言浪费语法罪该万死呀&#8230;&#8230;</p>
<p><strong><font size="5">待续</font></strong></p><img src ="http://www.cppblog.com/vczh/aggbug/199765.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-04-27 17:24 <a href="http://www.cppblog.com/vczh/archive/2013/04/27/199765.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>伴随我成长的编程书</title><link>http://www.cppblog.com/vczh/archive/2013/03/24/198769.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sun, 24 Mar 2013 06:35:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/03/24/198769.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/198769.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/03/24/198769.html#Feedback</comments><slash:comments>28</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/198769.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/198769.html</trackback:ping><description><![CDATA[<h3>一、</h3> <p>这篇文章是应之前在微博上爆过的下个周末某出版社的线下活动而写的。回顾我和C++在这个世纪的第二个春天开始发生过的种种事情，我发现我并不是用一个正常的方法来学会如何正常使用C++的。我的C++学习伴随着很多其他流行或者不流行的语言。现在手中掌握的很多淫荡的技巧正是因为学习了很多编程语言的缘故，不过这并不妨碍我正常地使用C++来在合理的时间内完成我的目标。 <p>学习C++是一个艰难的过程。如果从我第一次看C++的书算起，现在已经过了11年了。一开始的动机也是很不靠谱的。刚开始我很喜欢用VB6来开发游戏，但是我能找到的资料都是用C++来做例子的，文字部分又不丰富，于是我遇到了很多困难。因此我去三联书店买了本C++的书，想着我如果学会了C++，就可以把这些例子翻译成VB6的代码，然后继续用VB6来写游戏。阴差阳错，我买到的是一本语法手册。不过那个时候我还小，不知道什么是MSDN，也不知道MSDN是可以打印出来卖的：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_2.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb.png" width="204" height="244"></a><br>不过因为C++在当时并不是我学习的重点，于是我就没事的时候翻一翻。我们都知道语言参考手册（MSDN里面叫Language Reference）的顺序都是按照类别而不是教学顺序来排列的。于是当我花了很长时间看完了第一遍的时候，就觉得这本书写的云里雾里。刚开始讲什么是表达式的时候，例子就出现了大量的函数和类这种更加复杂的东西。于是我选择重新看一遍，基本的概念就都知道了。当然这个时候完全不能算&#8220;学会C++&#8221;，编程这种事情就跟下象棋一样，规则都很容易，但是你想要下得好，一定要通过长期的练习才能做到。 <p>当然，在这段时间里面，我依然是一边看C++一边用VB6来学习编程。初二的时候学校发了QBasic的课本，当时看了一个星期就完全学会了，我觉得写代码很好玩，于是从此就养成了我没事逛书店的习惯（就连长大了之后泡MM也有时候会去书店，哈哈哈哈哈）。值得一提的是，我第二次去书店的时候，遇到了下面的这本书《Visual Basic高级图形程序设计教程》：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_4.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_1.png" width="172" height="244"></a><br>在这之前我买到的两本VB6的书都是在教你怎么用简单的语法，拖拖界面。然后就做出一个程序来。那个时候我心目中编程的概念就是写写记事本啊、写字板啊、计算器等等这些东西，直到我发现了这本书。我还记得当时的心情。我在书架上随手翻了翻，发现VB竟然也可以写出那么漂亮的图形程序。 <p>这本书包含的知识非常丰富，从如何调用VB内置的绘图命令、如何调用Windows API函数来快速访问图片，讲到了如何做各种图像的特效滤镜、如何做几何图形的变换，一直到如何对各种3D物体做真实感渲染，甚至是操作4维图形，都讲得清清楚楚。这本书比其他大多数编程读物好的地方在于，读者可以仅靠里面的文字，基本不用看他的代码，就可以学会作者想让你学会的所有东西。因此当我发现我怎么着也找不到这本书的光盘（事实上书店就没有给我）的时候，我并没有感到我失去了什么。这本书的文字部分不仅写得很详细，而且作者还很负责任。作者知道像图形这种对数学基础有一定要求的东西，程序员不一定懂——尤其是我那个时候才上初中，就更不可能懂了——所以在书里面看到一些复杂的数学公式的时候，作者都会很耐心的告诉你这些公式的来源，它们的&#8220;物理意义&#8221;，有些时候甚至还会推导给你看。因此可以想象，这本书包含的内容也特别的丰富。这导致我在读的时候不断地找资料补充自己的数学知识，从而可以亲自把那些程序写（而不是抄）出来。这个过程一直持续到了我终于不用VB转Delphi，到最后上大学改用C++的那个时候，我终于理解了整本书里面讲的所有内容，给我后面的很多事情打下了坚实的基础。 <p>因为数学知识缺乏的关系，学习这些基础知识又不可能那么快，所以我把一部分时间投入在了游戏开发里面，尝试自己弄点什么出来。毕竟当时对编程有兴趣，就是因为&#8220;说不定游戏也可以用代码写出来&#8221;的想法，于是我得到了下面的这本书：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_6.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_2.png" width="177" height="244"></a><br>这本书是我觉得21天惊天阴谋系列里面唯一一本良心的书。它并没有只是简单的罗列知识，而是教你利用VB6内置的功能搭建从简单到复杂的游戏程序。我第一次看到关于链表的知识就是在这里。可惜在我还没学会如何使用VB6的类模块功能之前，我就已经投向了Delphi，因此并没有机会实践这个知识。不过在此之后，我用VB6写的小游戏，已经尝试把游戏本身的模块（这是VB6的一个功能，就跟namespace差不多）分离，积累一些基础代码。 <p>在这段时间里面，我学习语法都学得很慢。循环甚至是在我用人肉展开循环的方法一行一行复制黏贴出了一个井字棋的AI之后才学会的。后来很晚才学会了写函数，全局变量则更晚了。于是在那个时候我写了很多看起来很愚蠢的代码。曾经我以为一个函数的全局变量在退出函数之后是会保留的，然后对着自己写出来的不能运行的代码感到十分的莫名其妙。还有一次做一个记事本，因为不知道&#8220;当前文件路径&#8221;要存在什么地方，于是在界面上放了一个Label来放文件名。后来有了雄心壮志，想用VB搞定一个长得像Basic的超简陋的脚本。这当然最后是失败了，但是我依稀记得，我当时取得的成就就是把脚本语言的字符串分割成了一个一个的token之后，保存在了一个表格控件里面，以便之后（后来这个&#8220;之后&#8221;没写出来）读的时候方便一点。之后还尝试写一个读四则运算字符串计算结果的程序，都是先找最里层的括号，把那条不带括号的简单式子计算完之后，把结果也处理成字符串replace回去。直到整个字符串收敛成一个值为止。一直等到我后来买到了一本系统介绍VB6语法和用法的书之后，我的代码才稍微变得不像猴子打出来的。 <p>在刚开始学编程的时候，基本上都没有什么固定的方向，都是在书店里面碰到什么酒写什么。于是有一次我在书店里看到了《Visual Basic 网络高级编程》<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_8.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_3.png" width="158" height="218"></a><br>这本书是我在学习VB的过程中最后一本我觉得不错的书了。虽然VB本身也提供了很多访问网络资源的控件，但是这本书并没有让你仅仅会用被人的轮子来写代码，而是一步一步的告诉你这些网络协议的内容，然后让你用Socket来跟这些服务器直接交互。我记得我最后成功的做出了一个邮件收发程序，跟联想1+1系列自带程序的功能已经可以媲美了。</p> <h3>二、</h3> <p>当我发现C++实在是太难，根本没办法真的把网上那些C++的程序改成VB之后，我上了高一，接触了NOI。NOI让我得到的一个收获就是，让我在上了大学之后很坚定的不把时间浪费在ACM上，从而有了很多时间可以搞图形、编译器和女同学。参加高中的NOI培训让我知道了什么是数据结构，还有什么是指针。老师在讲Pascal的时候说，要灵活使用指针才可以写出高性能的程序。这让我大开眼界，不仅因为VB没有指针，而且当时用VB写图形的程序感觉怎么样也快不上去（当然这有大半原因是因为我代码写得烂，不能全怪VB）的同时，还让我认识了Delphi。Delphi跟VB一样可以拖控件，而且控件长得还很像。于是我就抱着试一试的心理，开始学习如何用Delphi来写代码。 <p>因为有《Visual Basic 高级图形程序设计教程》的知识作为背景，我很快就掌握了如何用Delphi来开发跟图形相关的程序。那个时候我觉得该做的准备已经准备好了，于是用Delphi写了一遍我在VB的时候总是写不快的一个RPG游戏。这个游戏虽然不大，但是结构很完整。在开发这个游戏的过程中，我第一次体验到了模块化开发的好处，以及积累基础代码对开发的便利性。同时也让我尝到了一个难以维护的程序时多么的可怕。这个游戏前后开发了八个月，有一半的事件都是在写代码。对于当时的我来说，程序的结构已经过于复杂，代码也多到差不多失控的地步了。后来我统计了一下，一共有一万两千行代码。由于那个时候我的调试能力有限，而且也不知道如何把程序写成易于调试的形式。结果我等到了我的核心部分都写完了之后，才能按下F9做第一次的运行（！！！）。当然运行结果是一塌糊涂。我花了很大的努力才把搞到能跑。 <p>由于程序本身过长，我在开发的过程中觉得已经很难控制了。再加上我发现我的同一个模块里的函数基本上都是下面的形式：<br>PrefixFunction(var data:DataStructure, other parameters ...)<br>总觉得跟调用Delphi的类库的时候很像。所以我就想，既然代码都变成了这样，那是不是学习面向对象开发会好一点？在这个过程中我有幸遇到了这本《Delphi6 彻底研究》：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_10.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_4.png" width="170" height="244"></a><br>虽然说这本书并没有包含那些深刻的面向对象的知识，但是他详细的介绍了Delphi的语法、基础的类库的用法还有Delphi那套强大的控件库和数据开发的能力。这本书第一次让我知道，Delphi是可以内嵌汇编代码的。这给我对计算机的深入理解打开了一扇门。 <p>学习汇编是一个漫长的过程。这倒不是因为汇编的概念很复杂，而是因为里面的细节实在是太多了。这些知识靠网络上零星的文章实在是无法掌握，于是在常年逛书店的习惯之下，我又遇到了《Windows 汇编语言程序设计教程》。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_12.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_5.png" width="175" height="244"></a><br>这本书内容其实并不是很多，但是他给了我一个很好的入门的方法，也讲了一些简单的汇编的技巧，譬如说怎么写循环啊，怎么用REPZ这样的前缀等等，让我可以用汇编写出有意义的程序。汇编和Delphi的结合也促使我开始去思考他们之间的关系，譬如说一段Delphi的代码就经是如何映射到汇编上面的。下面发生的一个小故事让我印象深刻。 <p>那还是一个，我还很喜欢各种不知所谓的奇技淫巧的日子。有一天我在论坛里看到有人说，交换两个integer变量可以用一种奇葩的写法：<br>a:=a xor b;<br>b:=b xor a;<br>a:=a xor b;<br>于是我就理所当然得想，如果我把它改成汇编，那是不是可以更快，并且超过那种需要中间变量的写法？后来我试了一次，发现慢了许多。这个事件打破了我对会变的迷信，当然什么C语言是最快的语言之类的，我从此也就以辩证的眼光去看带了。在接下来的高中生涯里，我只用了汇编一次，那还是在一个对图像做alpha blending的程序里面。我要同时计算RGB，但是寄存器每一个都那么大，我觉得很浪费，于是尝试用R&lt;&lt;16+G放到一个寄存器里面，跟另一个R&lt;&lt;16+G相加。中间隔了一个字节用来做进位的缓冲，从而达到了同时计算两个byte加法的效果。后来测试了一下，的确比直接用Delphi的代码来写要快一些。 <p>纯粹的教程类书籍看多了之后，除了类库用得熟、代码写得多以外，好处并不大。所以当我有一天在书店里发现《凌波微步》的时候，刚翻开好几页，我就被它的内容吸引住了，断然入手。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_14.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_6.png" width="197" height="244"></a><br>这本书让我第一次觉得，一个程序写得好和写得烂竟然有如此之大的差别。作者下笔幽默，行文诙谐，把十几个例子用故事一般的形式讲出来。这本书不告诉你什么是好的，而告诉你什么是不好的。每一个案例的开头都给出了写得不好的代码的例子，然后会跟你解释的很清楚，说这么做有什么不好，改要怎么改的同时，为什么好的方法是长那个样子的。这本书也开始让我相信方法论的意义。在这个时候之前，我在编程这个东西上的理论基础基本上就只有链表和排序的知识，其它的东西基本都不懂，但是想做出自己想要做的事情却又不觉得有什么太大的麻烦。甚至我到高三的时候写了一个带指令集和虚拟机的Pascal脚本语言（不含指针）的时候，我连《编译原理》这本书都没有听过。因此以前觉得，反正要写程序，只要往死里写，总是可以写出来的。但是实际上，有理论基础和没有理论基础的程序员之间的区别，不在于一个程序能不能写出来，而在于写出来之后性能是不是好，代码是不是容易看懂的同时还很好改，而且还容易测试。这本书对于我的意义就是给我带来了这么一个观点，从而让我开始想去涉猎类似的内容。 <p>当然，那段时间只是这么想，但是却不知道要看什么。所以在一次偶然之下，我发现了《OpenGL 超级宝典》。当然第一次看的时候还是第二版，后来我又买了第三版。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_16.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_7.png" width="175" height="244"></a><br>鉴于以前因为《Visual Basic 高级图形程序设计教程》的缘故，我在看这本书之前已经用Delphi写过一个简单的支持简单光照和贴图的软件渲染程序，于是看起来特别的快。其实OpenGL相比起DirectX，入门级的那部分API（指glBegin(GL_TRIANGLE_STRIP)这些）是做得比DirectX漂亮的，可惜性能太低，没人会真的在大型游戏里使用。剩下的那部分比DirectX就要烂多了。所以当我开始接触高级的API的时候，OpenGL的低速部分让我恋恋不舍。OpenGL的程序我一路写到了差不多要高考的时候。在那之前学习了一些简单的技巧。上了大学之后，学习了一些骨骼动画啊、LOD模型啊、场景管理这些在OpenGL和DirectX上都通用的知识，但是却并没有在最后把一个游戏给做出来。 <p>我最后一次用OpenGL，是为了做一个自绘的C++GUI库。这个库的结构比起现在的GacUI当然是没法。当时用OpenGL来做GUI的时候，让我感觉到要操作和渲染字符串在OpenGL上是困难重重，已经难到了几乎没办法处理一些高级文字效果（譬如RichText的渲染）的地步了。最后只能每次都用GDI画完之后把图片作为一个贴图保存起来。OpenGL贴图数量有限，为了做这个事情还得搞一个贴图管理器，把不同的文字都贴到同一张图上。做得筋疲力尽之余，效果还不好。当我后来开发GacUI的时候，我用GDI和DirectX作为两个渲染器后端，都成功的把RichText渲染实现出来了，我就觉得我以后应该再也不会使用OpenGL了。GDI和DirectX才是那种完整的绘图API，OpenGL只能用来画图，写不了字。 <p>有些人可能会觉得，为什么我会一直在同时做图形图像、编译器和GUI的事情。大家还记得上文我曾经说过我曾经用了好久做了一个伊苏那种模式的RPG出来。其实我一直都很想走游戏开发的路线，可惜由于各种现实原因，最后我没有把这件事情当成工作。做出那个RPG的时候我也很开心，丝毫不亚于我毕业后用C#写出了一个带智能提示的代码编辑器的那一次。当然在上大学之后我已经觉得没有一个美工是做不出什么好游戏的，但是想花时间跟你一起干的美工同学又很难找，因此干脆就来研究游戏里面的各种技术，于是就变成了今天这个样子。当然，现在开发游戏的心思还在，我想等过些时日能够空闲了下来，我就来忽悠个美工妹纸慢慢搞这个事情。 <p>虽然说《Visual Basic高级图形程序设计教程》是一本好书，但这只是一本好的入门书，想要深入了解这方面的内容还是免不了花时间看其他材料的。后来我跟何咏一起做图形的时候，知识大部分来源于论文。不过图像方面，还是下面这本冈萨雷斯写的《数字图像处理》给了我相当多的知识。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_18.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_8.png" width="171" height="244"></a><br>这本书的特点是，里面没有代码，我很喜欢，不会觉得浪费钱。不过可惜的是在看完这本书之后，我已经没有真的去写什么图像处理的东西了。后面做软件渲染的时候，我也没有把它当成我的主业来做，权当是消磨时间。每当我找不到程序可以写觉得很伤心的时候，就来看看论文，改改我那个软件渲染器，增加点功能之后，我就会发现一个新的课题，然后把时间都花在那上面。</p> <h3>三、</h3> <p>整个高三的成绩都不错，所以把时间花在编程上的时候没人理我，直到我二模一落千丈，因此在高考前一个月只好&#8220;封笔&#8221;，好好学习。最后因为失误看错了题目，在高考的时候丢了十几分的原始分，估计换算成标准分应该有几十分之多吧，于是去了华南理工大学。所幸这本来就是我的第一志愿，所以当时我也不觉得有什么不开心的。去了华南理工大学之后，一个令我感到十分振奋的事情就是，学校里面有图书馆，图书馆的书还都不错。虽然大部分都很烂，但是因为基数大，所以总能够很轻松的找到一些值得看的东西。 <p>我还记得我们那一年比较特殊，一进去就要军训。军训的时候电脑还没来得及带去学校，学校也不给开网络，所以那一个月的晚上都很无聊，跟同学也还不熟悉，不知道要干什么。所以那段时间每到军训吃晚饭，我就会跑到学校的图书馆里面泡到闭馆为止。于是有一天让我发现了李维写的这本《Inside VCL》。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_20.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_9.png" width="194" height="244"></a><br>虽然到了这个时候我用Delphi已经用得很熟悉了，同时也能写一些比较复杂的程序了，但是对于Delphi本身的运作过程我是一点都不知道。所以当我发现这本书的时候，如鱼得水。这本书不仅内容深刻，更重要的是写的一点都不晦涩难懂，所以我看的速度非常快。基本上每个晚上都可以看100页，连续七八天下来这本书就被我翻完了。这带来了一个副作用就是，图书馆的姐姐也认识我了——当然这并没有什么用。 <p>过后我又在书店得到了一本《Delphi 源代码分析》。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_22.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_10.png" width="182" height="244"></a><br>这本书跟《Inside VCL》的区别是，《Inside VCL》讲的是VCL的设计是如何精妙，《Delphi 源代码分析》讲的则是Delphi本身的基础设施的内部实现的细节。以前我从来不了解也没主动想过，Delphi的AnsiString和UnicodeString是指向一个带长度记录的字符串指针，学习了指针我也没把这两者联系起来（当然这跟我当时还没开始试图写C++程序有关）。于是看了这本书，我就有一种醍醐灌顶的感觉。虽然这一切看起来都是那么的自然，让我觉得&#8220;就是应该这么实现的才对&#8221;，但是在接触之前，就是没有去想过这个事情。 <p>令人遗憾的是，在我得到这本书的同时，Borland也把Delphi独立出来做了一个叫做Codegear的公司，后来转手卖掉了。我在用Delphi的时候还想着，以后干脆去Borland算了，东西做得那么好，在那里工作肯定很开心。我在高中的时候还曾经把Borland那个漂亮的总部的图片给我妈看过，不过她一直以为是微软的。于是我在伤心了两个晚上之后，看了一眼为了做参考我带到学校来的《Visual C++ 5.0语言参考手册》，找了一个盗版的Visual C++ 2005，开始决定把时间投入在C++上面了。于是Delphi之旅到此结束，从此之后，就是C++的时光了。</p> <h3>四、</h3> <p>学习图形学的内容让我学会了如何写一个高性能的计算密集型程序，也让我不会跟很多程序员一样排斥数学的内容。学习Delphi让我开阔了眼界的同时，还有机会让我了解Delphi内部工作原理和细节。这一切都为我之后做那些靠谱的编译器打下了基础。 <p>因为在高三的时候我在不懂得《编译原理》和大部分数据结构的知识的情况下，用Delphi写出了一个Pascal脚本引擎，所以当我听说我大学的班主任是教编译原理的时候，我就很开心，去跟她交流这方面的内容，把我当时的设想也拿给她看。当然我的设想，没有理论基础的知识，都是很糟糕的，于是班主任就给了我一本《编译原理》。当然，这并不是《龙书》，而是一本质量普通的书。不过当我了解了这方面的内容之后，《龙书》的大名也就进入我的耳朵里了：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_24.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_11.png" width="174" height="244"></a><br>由于之前用很愚蠢的方法写了个Pascal脚本的缘故，看《龙书》之后很容易就理解了里面各种精妙的算法在工程上的好处。我之前的作法是先用扫描的方法切下一个一个的token，然后做一个递归来递归去复杂到自己都没法看的一遍扫描生成简单指令的方法来做。程序写出来之后我当场就已经看不懂了。自从看了《龙书》之后，我才知道这些过程可以用token和语法树来对算法之间进行解耦。不过《龙书》的性质也是跟《Visual Basic 高级图形程序设计教程》一样，是入门类的书籍。用来理解一下编译器的运作过程是没问题的，但是一旦需要用到高级的知识。 <p>这个时候我已经初步理解了编译器前端的一些知识，但是后端——譬如代码生成和垃圾收集——却还是一知半解。不过这并不妨碍我用好的前端知识和烂的后端知识来做出一个东西来。当时我简单看了一下Java语言的语法，把我不喜欢的那些东西砍掉，然后给他加上了泛型。Java那个时候的泛型实现好像也是刚刚出现的，但是我不知道，我也从来没想过泛型要怎么实现。所以当时我想来想去做了一个决定，泛型只让编译器去检查就好了，编译的时候那些T都当成object来处理，然后就把东西做出来了。我本来以为我这种偷工减料拆东墙补西墙忽悠傻逼用户的方法是业界所不容的，不过后来发现Java竟然也是那么做的，让我觉得我一定要黑他一辈子。后来我用我做的这个破语言写了一个俄罗斯方块的游戏，拿给了我的班主任看，向她证明她拿给我的书我没有白看。 <p>不过由于受到了Delphi的影响，我并没有在我的C++代码里面使用泛型。当时由于不了解STL，也懒得去看，于是自己就尝试折腾这么几个容器类自己用。现在代码还留着，可以给大家贴一段：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_26.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_12.png" width="562" height="763"></a><br>这段代码已经可以作为反面教材使用了。除了基类有一个virtual的析构函数和代码对齐的比较漂亮以外，基本所有的地方都是设计错误的典型表现。为了这段代码的贴图我特地在硬盘里面翻出来了我那个山寨Java脚本的代码，一打开就有一股傻逼的气息扑面而来，截图放进word之后，屏幕犹如溢屎，内容不堪入目。 <p>之所以把代码写成这样，跟Delphi的class不是值类型的这个功能是分不开的。写了几年的Delphi之后，再加上第一次开始写有点小规模的C++程序，我从来没考虑过一个用来new的class是可以创建成值类型的。所以那个时候我一直处于用C++的语法来写Delphi的状态上。当然这样是不对的，但是因为那一段时间运气比较背，好的C++书都没给我碰上，直到我看到了《C++语言的设计和演化》<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_28.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_13.png" width="169" height="244"></a><br>C++他爹写的这本《C++语言的设计和演化》是一本好书，我认为每一个学习C++的人都应该看。本来《C++Primer》也是一本不错的书，不过因为我阴差阳错用了《Visual C++ 5.0 语言参考手册》入门，所以这本书就被我跳过了。一开始C++用得很烂，觉得浑身不舒服，但是有知道为什么。看了这本书之后很多疑问就解决了。 <p>《C++语言的设计和演化》讲的是当年C++他爹发明C++的时候如何对语言的各种功能做取舍的故事。在这个长篇小说里面，C++他爹不厌其烦地说，虽然C++看起来很鸟，但是如果不这样做，那就会更鸟。看完了这本书之后，基本上就剩下不会模板元编程了，剩下的语言的功能都知道在什么时候应该用，什么时候不该用。C++他爹还描述了一些重要的类——譬如说智能指针和STL的迭代器——在语义上的意思。其实这就跟我们在看待C++11的shared_ptr、unique_ptr和weak_ptr的时候，不要去想这是一个delete对象的策略，而是要想这是一个描述对象所有权关系的这么个&#8220;关键字&#8221;一样。有些时候细节看得太明白，而忽略了更高层次上的抽象，此乃见树木不见森林。 <p>C++知道每一个特性如何正常使用还不够，如果不知道他们是如何实现的，那有可能在非常极端的情况下，写出来的程序会发挥的不好。正如同如果你知道C++编译器、操作系统和CPU内部是如何处理这些东西的细节，如果你顺着他们去写你的程序的话，那性能的提高会特别明显。譬如说在做渲染器的时候，为什么光线追踪要按照希尔伯特顺序来发射光线，为什么KD树可以把每一个节点压缩成8个字节的同时还会建议你按层来排列他们，都是因为这些背后的细节所致。这些细节做得好，渲染器的效率提高一倍是完全没问题的。这些知识固然很多，但是C++的那部分，却包含在了一本《深度探索C++对象模型》里面：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_30.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_14.png" width="198" height="244"></a><br>读《深度探索C++对象模型》，不仅仅是为了知道C++在涉及虚拟多重继承基类的成员函数指针结构是怎样的，而且你还可以从中学到很多技巧——当然是指数据结构的技巧。这本书的内容大概分为两个部分。第一个部分就跟需求一样，会跟你介绍C++的对象模型的语义，主要就是告诉你，如果你这样写，那你就可以获得XXX，失去YYY。第二部分就跟实现一样。按照需求来得到一个好的实现总是一个程序员想做的事情，那么这就是个很好的例子。正常使用C++需要的无限智慧，大部分就包含在上面这两本书里面。一旦把这两本书的内容都理解好，以后写起C++的代码都会得心应手，不会被各种坑所困扰，正确审视自己的代码。 <p>文章之前的部分有提到过，让我正视理论和方法论的意义的是《凌波微步》，所以当工具都掌握的差不多的时候，总需要花时间补一补这方面的内容。首当其冲当然就是大家喜闻乐见的《算法导论》了。我记得当时是唐良同学推荐给我的这本书，还重点强调了一定要看原文，因为中文的翻译不行。所以我就在一个春光明媚的早上，来到了广州天河书城，把这本书搞到手。<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_32.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_15.png" width="193" height="244"></a><br>这本书的封面颜色暗示着你，想读这本书， 应该去一个山清水秀绿荫环绕的地方。事实证明这是对的。在差不多考英语四级的前后，我有一段时间每天都去华南理工大学那个著名的分手亭看这本书。亭子后面是一个湖，前面有很多树和杂草，旁边还有一个艺术学院，充满了人文的气息。在这种地方看《算法导论》，不仅吸收得快，而且过了一年，我真的分手了。 <p>说实话这本书我没有看完，而且那些证明的部分我都跳过了，实在是对这些东西没有兴趣。不过关于数据结构和大部分算法我看得很仔细。于是我在这方面的能力就大幅度提高——当然跟那些搞ACM的人相比反应还是不够快，不过我的志向并不在这里。除此之外，我通过《算法导论》也学到了如何准确的计算一个函数的时间复杂度和空间复杂度。事实证明这个技能十分重要，不仅可以用来找bug，还可以用来面试。</p> <h3>五、</h3> <p>对于一个读计算机的大学生来说，算法懂了，工具会了，接下来就是开眼界了。不过这些东西我觉得是没法强求的，就像下面这本《程序设计语言——实践之路》一样，都是靠运气才到手的——这是一个小师妹送我的生日礼物：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_34.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_16.png" width="200" height="244"></a><br>原本学习的汇编也好，VB、Delphi和C++也好，都是同一类的编程语言。这导致我在相当长的时间里面都无疑为编程就差不多是这个样子。直到我看到了《程序设计语言——实践之路》。这本书告诉我，这个世界上除了命令是语言，还有各种不同的编程的范式和方法。于是借着这本书的机会，我了解到世界上还有Prolog、Erlang和Haskell这么美妙的语言。 <p>这对我的触动很大。一直以来我都是用一种编程方法来解决所有我遇到的问题的。然后突然有一天，我发现有很多问题用别的方法来解决更好，于是我就开始去研究这方面的内容。一开始我的认识还是比较浅，应用这些方法的时候还处于只能了解表面的状态，譬如说曾经流行过几天的Fluent Interface，还有声明式编程啊，AOP等等。直到我遇到了这本全面改变我对C++模板看法的书——《Real World Haskell》：<br><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_36.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_17.png" width="188" height="244"></a><br>是的，你没看错，是《Real World Haskell》！Haskell颠覆了我的世界观，让我第一次知道，原来代码也是可以推导的。说实话我用Haskell用的并不熟，而且我也没写过多少个Haskell的大程序，但是Haskell的很多方面我都去花了很长时间去了解，譬如那个著名的Monad。多亏了当时搞明白了Monad，我借助这方面的知识，理解了《Monadic Parser Combinator》这篇论文，还看懂ajoo那篇著名的<a href="http://www.blogjava.net/ajoo/category/6968.html">面向组合子编程系列</a>。 <p>当我终于明白了Haskell的类型推导之后，我终于体会到了Haskell和C++之间的巨大差异——Haskell的程序的逻辑，都是完全表达在函数签名上的类型里面，而不是代码里的。当你写一个Haskell函数的时候，你首先要知道你的函数是什么类型的，接下来你就把代码当成是方程的解一样，找到一个满足类型要求的实现。Haskell的表达式一环扣一环，几乎每两个部分的类型都互相制约，要求特别严格。导致Haskell的程序只要编译通过，基本上不用运行都有95%的概率是靠谱的，这一点其他语言远远达不到。而且Haskell的类库（Hackage）之多覆盖GUI、GPU程序、分布式、并发支持、图像处理，甚至是网页（Haskell Server Page）都有，用来写实用的程序完全没问题。之所以Haskell不流行，我觉得仅有的原因就是对初学者来说太难了，但是人们一旦熟悉了C的那一套，看Haskell的难度就更大了，比什么都不会的时候更大。 <p>于是回过头来，模板元编程也就变成一个很自然的东西了。你把模板元编程看成是一门语言，把&#8220;类型&#8221;本身看成是一个巨大的带参数enum的一部分（scala叫case type），于是类型的名字就变成了值，那么模板元编程的技巧，其实就是对类型进行变换、操作和计算的过程。接下来只要会用模板的形式来表达if、while、函数调用和类型匹配，那掌握模板元编程是顺利成章的事情。撇去type traits这些只是模板元编程的具体应用不说，只要熟悉了Haskell，熟悉C++的模板语法，学会模板元编程，只需要一个下午——就是学会用蹩脚的方法来写那些你早就熟悉了的控制流语句罢了。 <p>当模板元编程变成了跟写i++一样自然的东西之后，我看语言的感觉也变了。现在看到一个程序语言，再也不是学习与发这么简单了，而是可以看到作者设计这门语言的时候想灌输给你的价值观。譬如说，为什么C语言的typedef长那个样子的？因为他想告诉你，你int a;定义的是一个变量，那么typedef int a;就把这个变量的名字改成了类型的名字。为什么C语言定义函数的时候，参数是用逗号隔开？因为你调用函数的时候，也是用逗号来隔开参数的。这就是语法里面的一致性问题。一个一致性好的语言，一个有编程经验初学者只要学习到了其中的一部分，就可以推测他所想要的未知特性究竟是如何用于发表达出来的。一个一致性差的语言，你每一次学到一个新的功能或者语法，都是一个全新的形式，到处杂乱无章，让人无可适从（所以我很讨厌go，还不把go的library移植成C++直接用C++写算了）。 <p>从此之后，我就从一个解决问题的程序员，变成一个研究编程本身的程序员了。当然我并不去搞什么学术研究，我也不打算走在理论的前沿——这并不适合我，我还是觉得做一个程序员是更快乐一点的。这些知识在我后续学习开发编译器和设计语言的时候，起了决定性的作用。而且当你知道如何设计一个优美的语法，那么你用现有的语法来设计一个优美的library，也就不会那么难了。当然，设计优美的library是需要深入的了解正在使用的语言本身的，这样的话有可能维护这个library的门槛就会提高。不过这没有关系，这个世界上本来就有很多东西是2000块钱的程序员所无法完成的，譬如维护STL，维护linux内核，甚至是维护Microsoft Office。</p> <h3>六、</h3> <p>上面所列出来的书，每一本都是对我有深刻的影响的。当然光有深刻的影响是不够的，具体的领域的知识，还是需要更多的资料来深入研究，譬如说下面的一个单子，就是我在学习开发编译器和虚拟机的时候所看过的。内容都很深刻，很适合消磨时间。在这里我要感谢g9yuayon同学，他在我需要开阔眼界的时候，给我提供了大量的资料，让我得以快速成长，功不可没。 <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_38.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_18.png" width="165" height="210"></a><br>虚拟机——系统与进程的通用平台 <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_40.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_19.png" width="168" height="210"></a><br>Garbage Collection——Algorithms for Automatic Dynamic Memory Management <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_42.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_20.png" width="150" height="210"></a><br>高级编译器设计与实现（鲸书） <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_44.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_21.png" width="148" height="210"></a><br>程序设计语言理论基础 <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_46.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_22.png" width="115" height="158"></a><br>类型与程序设计语言 <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_48.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_23.png" width="166" height="244"></a><br>Parsing Techniques——A Practical Guide <p><a href="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_50.png"><img style="background-image: none; border-bottom: 0px; border-left: 0px; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top: 0px; border-right: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://www.cppblog.com/images/cppblog_com/vczh/Windows-Live-Writer/fc6c1b2c2401_CB25/image_thumb_24.png" width="153" height="244"></a><br>The Implementation of Functional Programming Languages                          <img src ="http://www.cppblog.com/vczh/aggbug/198769.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-03-24 14:35 <a href="http://www.cppblog.com/vczh/archive/2013/03/24/198769.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2012龙年之旅</title><link>http://www.cppblog.com/vczh/archive/2013/01/25/197566.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 25 Jan 2013 14:29:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2013/01/25/197566.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/197566.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2013/01/25/197566.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/197566.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/197566.html</trackback:ping><description><![CDATA[<p><span>本来每年都要写一篇年经帖来提高一下知名度的，但是最近因为做GacUI太兴奋，竟然把这件事情给忘了，实在是罪过。<br /></span><br />如果要说我2012年做过的什么事情最重要，那当然要属开发了GacUI（<a href="http://www.gaclib.net/">Home Page</a>, <a href="http://gac.codeplex.com/">Codeplex</a>, <a href="https://github.com/vczh/gac">Github</a>）和创建了<a href="http://www.cppblog.com/vczh/archive/2012/11/29/195805.html">粉丝群</a>（啊哈哈）了吧。博客到现在还有三个坑没填完，分别是那个已经坑了好久、大家都要看、但是我却不知道要写什么的《C++使用技巧》，还有两个大家不怎么想看的《可配置语法分析器开发纪事》和《GacUI与设计模式》。 <br /><strong><br />关于</strong><strong>GacUI</strong>，我已经在微博上做了许多广告，也有一些人开始尝试使用它了。目前GacUI还处于一个凑合着能用的beta状态，我在接下来的很长一段时间内应该会继续update它。我的本意是要把WPF那么精妙的设计给山寨到C++上面来，从而结束非得用MFC才能正常开发GUI的日子。而且因为之前我用C#的WinForm开发IDE太蛋疼了，parser需要写两遍（编译器一遍，IDE一遍，语言还不一样），所以我在设计GacUI的时候，质量要求就是朝着Visual Studio看齐的。所以大家会看到我在做GacUI的时候，文本框就内置了高速的着色系统，还做了一个新的parser来产生严格的parser或者松散的parser，分别给编译器和IDE使用。然后我用这个parser写了一个xml和json的库，最后在昨天还update了一下Linq to C++，把我看得不顺眼的东西都干掉，于是我也拥有了一个Linq to Xml for C++的库了。 </p>
<p>但是GacUI还是有很多东西要做。我脑子里一直有一个清晰的路线图，而且这个路线图是直接朝着目标前进的：做一个C++的GUI库，顺便给一个类似Expression Blend那样子的东西，然后那个框架还可以为我以后开发语言，或者给现有的语言做IDE。所以为了达到这个目标，我至少要给GacUI的控件和对象模型做反射。为了让大家可以使用，我还得准备一个看起来跟MSDN很像的文档。因此路线图就是（粗体的部分已经完成了） 
<p>1. <strong>开发控件库</strong> <br />2. <strong>拥有一套生成Release</strong><strong>的工具链，包括parser</strong><strong>生成器、文档生成器、各种代码生成器等</strong> <br />3. <strong>有一个小巧玲珑简单好用的XML</strong><strong>库</strong> <br />4. <strong>可以读PDB</strong><strong>把GacUI</strong><strong>的对象声明都拿到手</strong> <br />5. <strong>利用PDB</strong><strong>和GacUI</strong><strong>的源代码里面的XML</strong><strong>注释生成文档</strong> <br />6. 用一个类似C#那样子的语法来给GacUI&#8220;声明&#8221;一个对象模型，让他可以被反射，也可以用来生成各种语言用的接口，特别是动态语言例如javascript和python的 <br />7. 把PDB的内容和对象模型结合起来，生成C++用的反射代码 <br />8. 利用反射代码，设计一个GUI的XML（或者别的什么东西）表示，从而实现动态加载窗口 <br />9. 制作一个长得和操作模式都跟Visual Studio差不多的多文档编辑框架 <br />10. 用上面的框架开发一个GUI编辑器，用来拖控件生成xml+资源，就可以嵌入C++的exe，或者提供给脚本语言使用了 <br />11. 提供一个脚本语言，作为可选的插件，来简化复杂GUI的开发 <br />12. 给这个语言提供一个IDE <br />
<p>大家可以看到，这就是为什么我最近要花时间做着色、parser生成器、用parser生成器来生成xml和json的库的parsing部分、做一个linq to C++并且让xml库直接支持就像C#的linq to xml一样。虽然看起来这些东西跟GacUI本身毫无关系，但是实际上为了实现那个复杂又得自动生成不然写到孩子出来还人肉不完的反射代码生成，一定要有配套的基础设施才行。<br /><strong><br />关于粉丝群</strong>，因为我加入的大部分编程区最后都瘪了，所以本来我并没有创建一个群用来交流技术的想法。不过因为某群友说找不到人研究我以前的代码的一篇回复，我还是创建了这个群。本来群只有100人的，但是有两个人赞助了一下，瞬间变成了500人群。所以以后不断的有人进来的时候我就再也不需要踢掉不说话的人了。很快群里就开始热烈的讨论起问题，经常讨论的那么十几二十个人也这么固定下来了。这个群和别的群不一样的地方在于，所有问傻逼问题和求大作业的全部被我和鹳狸猿们一个不留的干掉了，啊哈哈哈哈。<br /><br />由于我在cppblog广告的关系，加入这个群的人大部分还是做C++的，和S1那群做web的平时跟技术有关的话题完全不同，对待某些人生底线问题（譬如说大括号要不要换行等）的态度也是完全不同。当然偶尔有人经不住每天几千个消息的冲击退群了，但是群的热烈程度还是一点也没有消减。 <br /><strong><br />关于</strong><strong>C++</strong><strong>实用技巧</strong>，由于我自诩是一个做C++基础类库的人，对待C++各种奇技淫巧的态度自然也是不一样的。尽管大家都说C++学起来很难，坑很多，模板根本看不懂，析构函数没写程序函数经常要烂掉之类的，不过我的观点还是很明确的&#8212;&#8212;其实C++有很多难以理解的功能，都是给写基础类库准备的。只要程序员们不要本着&#8220;我一定要看懂类库怎么写才用&#8221;的这种无聊观点的话，其实压力并不会那么大。大多数人觉得C++难，但其实难的部分他做项目大概也是用不上的，本质原因还是不够淡定导致。 <br /><br />说到这里我就想起了以前跟人家讨论的，为什么C#用起来就那么舒服呢？很重要的一点其实是，因为选择少，所以连烦恼都没有了。反正事情都能完成，但是方法只有一种的话，你永远都不需要去比较或者纠结说，究竟要用什么样的方法来实现。而且一个自带垃圾收集器+泛型+函数式编程+continuation的语言，语法懂得少也可以用，语法懂得多用起来还特别省事，这一点的确比C++要好得多。回想起2002在CSDN那个著名的对垃圾收集器的大讨论，ajoo有一点说得很好，有没有GC，设计出来的架构都大不一样。想想原因其实也很简单，语言一旦带有GC的话，通常都会对内存做出严格的控制，因此你想干掉一个对象就只有一种方法&#8212;&#8212;等他去死了（C#的IDisposable跟这个其实没什么关系）。因此那些C++里面很执着的谁创建谁删除啊，COM的什么引用计数啊，这些乱七八糟的东西统统就没有了。你可以不顾一起的创建各种细粒度对象，不断地创建各种接口，而根本不用担心这些对象在死的时候你要干些什么，不仅做出来的设计干净，用起来也省心。 <br /><strong><br />关于可配置语法分析器开发纪事</strong>，按照计划还剩下两篇，不过因为这两篇的内容已经不怎么重要，所以最近的时间都用在开发GacUI上面了。等杂事搞完了之后我就补上这部分内容。 <br /><strong><br />关于</strong><strong>GacUI</strong><strong>与设计模式</strong>，这个系列自从写了两篇文章之后，尽管GacUI都是我一手写出来的，但是我发现要整理出那个架构清楚的表达出来，需要花很多的时间。为了保证文章的质量，我干脆就暂时停下来了，一边推进GacUI的开发进度，一边 重新整理。虽然我从来都只用VC++来编译我的代码，不过GacUI从一开始设计架构上就有考虑跨平台的问题，而且我也把对Windows.h的依赖也局限在少数的几个cpp文件里，头文件则完全是没有污染的。尽管代码里面肯定有VC++对标准作出的一点点人性化修改而垃圾GCC故意不支持从而造成代码不能再GCC上面编译，不过在计划上我大概会在今年的下半年开始把代码修改成让垃圾GCC也可以编译GacUI了。 <br /><strong><br />关于</strong><strong>2013</strong><strong>年</strong>，出去开发GacUI和心目中的那个脚本引擎，我在2013年最想点的技能树就是编译器的后端知识了。尽管我在09年的时候做过一个傻逼C语言编译器，尽管也是编译成机器码，但是都是用最简单粗暴的方法来做的。为了以后的脚本引擎，把这件事情做好，掌握编译器的后端也就变成必要的事情了。不过我在这里还是想说，编译器的前端知识也是很重要的。经过设计语言的语法的训练，和对设计类型系统的训练，不仅可以提高数学知识、提高智商，还可以让你学习新的语言和类库变得更快。编程都是举一反三的，并不是直接的针对他学习才是长远看来最好的方法。</p><img src ="http://www.cppblog.com/vczh/aggbug/197566.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2013-01-25 22:29 <a href="http://www.cppblog.com/vczh/archive/2013/01/25/197566.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于编程的胡扯</title><link>http://www.cppblog.com/vczh/archive/2012/06/22/179673.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Thu, 21 Jun 2012 17:59:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2012/06/22/179673.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/179673.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2012/06/22/179673.html#Feedback</comments><slash:comments>17</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/179673.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/179673.html</trackback:ping><description><![CDATA[<div>&nbsp;&nbsp;&nbsp; 博客光写GacUI（<a href="http://www.gaclib.net" target="_blank">www.gaclib.net</a>）的Demo更新也好无聊啊。所以今天先换换口味，胡扯点别的。<br /><br />&nbsp;&nbsp;&nbsp; 一年一度的高中毕业生填简历的日子即将就要到了，又有很多人问计算机专业的事情。其实我从心底里觉得，高考后才来问这个，已经完了一大半了。当然另一小半十分有前途的人可以在大学四年赶上来，不过估计他们还是要读个研究生，才能把自己训练成能用的码农。<br /><br />&nbsp;&nbsp;&nbsp; 编程是一件很难的事情。当然我的意思跟那篇著名的《编程是一件很难的事情》不一样。想把代码写好，本来就是一件非常困难的事情。我大三的时候训练一个大一的老乡，就光是C++，长达四年后她还搞不清楚模板元编程究竟是什么。而且还有C语言学会了转C++会把坏习惯带进来啦，C++的人转做C#之后发现很多C++的好技巧到了C#都只会让程序变得更慢啦，很多写动态语言的人不理解类型的好处还在那里胡扯啦，还有C#和javascript明明放着大好的函数式风格不用，非要把代码写的超长（本来光是这样没什么问题的，只是有某些人不肯学习新知识）。可见，就算把自己训练了好多年，最终进入了工作岗位，想把代码写好，也是一件非常困难的事情。<br /><br />&nbsp;&nbsp;&nbsp; 当然有些人说，如今只有产品做得好才能赚钱，代码写的好有个屁用。这只能是人各有志，有些人就不喜欢钻研代码，这本来也没什么。但是这些人老是跳出来忽悠别人，也只会让编程变得更难。只是幸好，我的单位并不会跟某些单位一样说一些&#8220;把代码写得那么好有什么用，搞到我们还非得学东西才能看你的代码，赶紧做点新feature啦&#8221;的这种话，我已经觉得很好了。<br /><br />&nbsp;&nbsp;&nbsp; 写得好这个东西还是比较抽象。我认为其中一条就是代码要好维护。我一直以为，只有代码写得好维护，好改，清晰易懂，这样加新的功能才会容易，不出事情，顺利发布软件。后来我发现我错了，腾讯不也是QQ一版一版的发吗，原来加班也是一种方法，啊哈哈哈。如果在一个单位里面，不加班别人就会找你麻烦的话，我相信你也不会花心思把代码写好的，反正都要加班。<br /><br />&nbsp;&nbsp;&nbsp; 不过对于志向就是写代码的那一些人，最好还是不要受到这些外来信息的干扰。最近跟我们组里的一个test manager聊天，他是一个菲律宾人，说是从纸带时代开始就写代码了（不过看起来好年轻&#8230;&#8230;），工作的时候还觉得C语言是一个崭新的语言。后来他跟我说，如果一个人有志向与，代码一条路走到黑，最好就去学习一下怎么当architect。他说道，Architect的知识架构是由各种pattern组成的，然后就说了自己年轻的时候的很多故事来作证这个道理。然后还讲了微软的其中一个创始人到现在还坚持一线写代码的事情，不过没告诉我是谁。<br /><br />&nbsp;&nbsp;&nbsp; 在这之前，刚好MSR的Daan Leijen因为来北京参加programming language相关的conference，就来我们这里参观了一下。后来我看他做过GUI，做过parser combinator，发明实现过语言，就前去搭讪，结果发现他读书的时候的导师竟然是Erik Meijer。按照他的话说，&#8220;then we are connected&#8221;，如果说成中文，就是有缘分吧。接着就跟他讨论了一些parser combinator和类型系统之类的东西。我说我之前也搞过这些东西，最后还贡献了一部分给公司，换了个组之后还开了讲座什么的。他讲到他读书的时候，也是学校没教自己自学的这些东西，后来周围也没什么人做，但是并没有让他丧失动力。然后就说了一句话让我印象很深刻：&#8220;原来你也做这些东西啊，我应该可以看到为什么你要从产品组跳到MSRA来了。&#8221;他直到今天，头发都基本上掉光了，还在那里继续研究programming language的东西，还给了我几篇论文。我觉得很好，人就该像他那样。<br /><br />&nbsp;&nbsp;&nbsp; 有些时候，人就得有那个信念，才能把可行但是难度大的东西，也最终搞出来。我自己写了11年的程序，其实并没有接触过十分广泛的东西，因为很多时间都花在重写我的一些idea上面了。譬如说编译器就写了五六个，GUI库就写了八遍，还有些杂七杂八的。不过从这个过程之中，可以明显感觉到自己什么时候比以前更进一步。这种signal有很多，譬如说当你决定要添加一个比较复杂的功能，也可以迅速知道怎么做而不用动到架构啦；譬如说你觉得你的代码越来越顺眼啦；譬如说你因为架构不行决定重写的时候，你发现前一个版本的代码可以捡起来继续用的部分越来越多啦。<br /><br />&nbsp;&nbsp;&nbsp; 写到这里，我想起很多人都问过我，程序要怎么写才能写得好，或者说设计模式要怎么写，之类的问题。如果把学习编程花费的精神代价做标准的话，捷径是没有的。但是如果仅仅把时间作为标准的话，捷径显然是有的。怎样才能加速你学习的过程呢？答案就是，先写再看书。对于像编译原理这种略微高深的知识，总要自己写过几遍，吃了一些苦头，才能知道为什么书里非要把算法那么设计结构那么安排。对于像设计模式这种需要大量经验才可以领悟到的知识，如果你从来没独立写过一个上万行的程序，你觉得你能理解设计模式在讲什么吗？我觉得这种时候能做的也就是背下来，理解什么的都是扯淡。诸如此类，学习程序，如果要加速那个过程，肯定要花大量的时间写代码。当你把项目做得越大、越复杂、算法越扭曲、界面越华丽、尺寸已经大到你觉得不学习新的方法论就肯定会让代码失控的时候，这个时候你来看设计模式的书，保证是每看到一个模式都觉得人家说到你心坎里去了。那你不仅可以迅速理解，而且以后还可以不由自主的想起来使用它。<br /><br />&nbsp;&nbsp;&nbsp; 当然，如果你不是一个喜欢写代码的人，那这个方法肯定没有用，因为中途放弃什么的太多了。这种时候，只能怪你没缘分，设计模式不渡你了。如果你最后撑下来了，虽然你自己觉得你也花费了相当的努力，但是别人反正是看不到你的努力的，就会开始觉得你有捷径了。为什么呢？因为效率高啊，时间花得短啊。<br /><br />&nbsp;&nbsp;&nbsp; 光写代码也是没用的。同人于野一篇讲成年人还能不能进步的博客说得很好，知识分为舒适区，学习区和恐慌区。舒适区的意思就是，你很容易就可以做完。学习区的意思就是，你需要花费大量的智力才可以做完。恐慌区的意思就是，你根本不知道如何下手。当你在为了练习编写大量的代码的时候，你要尽量把题目都安排在学习区这里，这样才能让你进步快的同时，还不会被问题打倒，可以继续积累成就感了。<br /><br />&nbsp;&nbsp;&nbsp; 学生做这个最方便了，工作之后，如果刚好遇上个黑心公司要你天天加班，你反而没时间做学习区的内容了，公司给你的肯定是舒适区的苦力活。<br /><br />&nbsp;&nbsp;&nbsp; 说到这里，如果你还有时间练习的话，千万不要去想：&#8220;我每一个程序都要跨平台&#8221;，&#8220;我只做这个语言&#8221;等等。反正将来，语言你都要会，平台的差异你都要知道，为什么要断送自己了解这些东西的机会呢？你真的以为不知道垃圾收集的原理，和一些底层的可以通过C++的练习而得到的的操作，你真的可以在某些关键时刻操纵好C#吗？当然有些人会觉得，我估计一辈子不会遇到这些问题的，所以我还是不管他了。人各有志嘛，C#不渡你，也是你自己的事情。如果你真的可以一辈子都在一个平台上用一种语言做同一种程序做到退休，那真是幸福的生活啊。<br /><br />&nbsp;&nbsp;&nbsp; 胡扯到这里也差不多了，这就是月经贴，时不时，总是要发一下的。</div><img src ="http://www.cppblog.com/vczh/aggbug/179673.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2012-06-22 01:59 <a href="http://www.cppblog.com/vczh/archive/2012/06/22/179673.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>进入2012 -- 回顾我走过的编程之路</title><link>http://www.cppblog.com/vczh/archive/2011/12/16/162252.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 16 Dec 2011 15:34:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2011/12/16/162252.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/162252.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2011/12/16/162252.html#Feedback</comments><slash:comments>27</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/162252.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/162252.html</trackback:ping><description><![CDATA[<p style="margin: 0cm 0cm 0pt" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'; color: red"><strong>（很荣幸被华南理工大学软件学院邀请撰写此文，关于毕业那会儿找工作的一些事情）<br /><br /></strong></span><span style="font-family: '微软雅黑','sans-serif'"><span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">前些日子被华南理工大学软件学院邀请回去参加一些活动，其中包括跟一些师弟师妹们进行座谈。期间就有一个人问，要怎么样才可以去微软。其实我从来没有想过这个问题，所以那个时候的答案自然就是微软的广告（编程好，数学好，态度好）了。<span lang="EN-US">09</span>年大四那会儿，刚好碰上了美帝的次贷危机，令我们这些想去美帝的公司被剥削的这帮人倍感艰辛。所幸后来还是过五关斩六将，最后在实习结束之后成功留了下来。这其中的因果，显然不是面试的那几天所能够决定的，因此还得从<span lang="EN-US">hello world</span>讲起。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">我有幸从初二开始就学习编程。那个时候世界已经处于一个现代化的程度了，操作系统都有虚拟内存，有图形界面，有因特网，开发软件还有集成开发环境可用，跟一些老前辈所描述的编译一个程序还要换几次磁盘的日子已经完全不一样了。那个时候正值购买电脑半年，处于看见什么东西都感到十分好奇的时候，再加上父亲那个时候不太同意我玩游戏，所以我就在想什么时候也自己做几个游戏，就可以光明正大的玩了。所以在听到汕头华侨中学开<span lang="EN-US">Visual Basic 5.0</span>的课的时候，感到比较兴奋。但是其兴奋程度比起初一为了上第一节电脑课兴奋过度，骑自行车超速以至于留了一大堆血没了几颗牙的那一天，已经可以忽略了。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">那个时候还是<span lang="EN-US">21</span>世纪的第一年，正处于上网费用巨贵无比、<span lang="EN-US">Google</span>还刚起来没多久基本没人知道的时候，学习编程要比现在困难很多。当时想寻找什么知识，因特网基本上是没什么指望的，所以我就有了一个没事去书店的爱好。没过多久我就找到了一本《<span lang="EN-US">Visual Studio</span>高级图形程序设计教程》。这本书我很喜欢，插图十分漂亮，而且还是使用<span lang="EN-US">Visual Basic</span>编程绘制的，更是爱不释手。可惜内容过于高深，所以后来就有了初三的时候自学学会初步的立体解析几何，以及高三上课不听讲仅凭自己看数学分析后来还被我看明白了的故事。中间因为试图使用编程绘制很多复杂的图形和对图像进行各种复杂的变换，于是每当写程序之前都要在纸上推导长长的公式。如果程序的运行结果不对了，根本无从调试，只好重新推导，借以希望可以发现公式的几个<span lang="EN-US">bug</span>以解释为什么会出现错误。从此以后我对符号运算就十分拿手。而且做数学物理作业也好，为了编程推导公式也好，需要计算的东西太多懒得到处寻找废纸，从而便获得了心算复杂过程的本领（可惜现在已经丧失了）。这顺带还给我带来了一个好处，就是高考数学选择题在发卷后不许动笔的<span lang="EN-US">10</span>分钟内就被我全部心算出答案，而且全对了。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">图形编程做久了，就想起了当初的理想，于是就搞游戏去了。那会儿看到了成都金点工作组开发的《圣剑英雄传》，点燃了我开发<span lang="EN-US">RPG</span>的热情。在经历了几次失败之后，我终于在高二的正月初一那一天完成了《天地传》的所有编码工作，没过多久就上传到了<span lang="EN-US">GameRes</span>的网站上。这是我第一个行数过万的程序。为了顺利完成它，我悟到了很多道理，包括为什么要面向对象，为什么要划分模块减少互相依赖。这也成为我后来开发自绘图形界面和脚本引擎的契机。后来我试图用<span lang="EN-US">OpenGL</span>做<span lang="EN-US">3D</span>游戏，但是由于很难找到有共同爱好的美工跟我一起做，便作罢了。但是这却让我获得了很多时间，可以投入到图形界面和脚本引擎之中去。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">后来我就萌发了解释高级语言的想法。这是我整个编程历史上的第一个转折点。那个时候我数据结构只会用链表，而且编译原理也好，设计模式也好，都还没听过。那个时候去解释高级语言自然是比较困难的。因此我经过很多天的苦思冥想自己想出了一个如今称之为一遍编译（也就是很烂）的方法来把一个简单的高级语言重新处理成一个简单的指令集语言，就跟汇编长得差不多。那个时候已经高三了，所以其实也没多少时间可以投入在编程上面，因此做出来的第一个原型是一个简化后的<span lang="EN-US">Pascal</span>的解释器，用<span lang="EN-US">Delphi</span>开发的。现在想起来，里面肯定有巨多内存泄露和性能问题，不过当时根本不知道这些东西是什么。在高中毕业之后的三个月无所事事的日子里，我就重新把这个东西设计了一遍，得到了一个几十页的计划。由于后来没来得及做完，就打印出来带去了华南理工大学。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">刚进了大学没几天，就听一个大四的师姐说我们的班主任陈健老师是教编译原理的，于是我就把这一叠纸拿给了她看。她什么也没说（现在回想起来，只能是那一份设计实在是不堪入目&#8230;&#8230;），就给了我一本编译原理的课本。我很快就看完了，然后用了里面的知识做了第一个真正意义上的脚本引擎，语法山寨了<span lang="EN-US">Java</span>语言的一些简单的部分，还添加了一个编译的时候自动把模板参数都改成<span lang="EN-US">Object</span>类型的语法，起了个名字叫<span lang="EN-US">JoveScript</span>。后来上了<span lang="EN-US">Java</span>的课，发现<span lang="EN-US">Java</span>竟然真的这么干了，让我觉得好生奇怪。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">后来我陆陆续续写了很多脚本引擎。大一的时候做的<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">JoveScript</span></strong>是第一个我觉得还能见人的脚本引擎。第二个就是大二失败了一整年吸取了很多教训之后，于大三开发出的动态语言，名字叫<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">FreeScript</span></strong>（可以在我的博客<span lang="EN-US">http://www.cppblog.com/vczh</span>上找到）。最近正在打算将其更新到<span lang="EN-US">3.0</span>来配合一个正在开发中的显卡加速的<span lang="EN-US">GUI</span>类库<span lang="EN-US">GacUI</span>。接下来就是在去微软上海的<span lang="EN-US">WCF Tools</span>组实习的那一段时间里面，利用每天晚上的时间完成的一门纯函数式语言叫<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">KernelFP</span></strong>，这后来成为了我的毕业设计。提交了毕业设计之后，我又在毕业前的几个月时间里面完成了<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">CMinus</span></strong>。这不是编译原理课程设计上的那个简单到没法再简单的<span lang="EN-US">CMinus</span>，而是一个完整的<span lang="EN-US">C</span>语言编译器（其中函数指针的语法被我改掉了，但是仍然支持）。其编译结果是保存到内存中的一段<span lang="EN-US">X86</span>二进制代码，可以将函数的起始地址强制转换成函数指针直接在<span lang="EN-US">C++</span>程序中使用，这是因为我在生成指令的时候遵守了<span lang="EN-US">Visual C++</span>中的一些在<span lang="EN-US">MSDN</span>里描述得很清楚的约定。毕业后我又雄心勃勃地做了<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">NativeX</span></strong>，是一个带泛型以及<span lang="EN-US">concept mapping</span>的<span lang="EN-US">C</span>语言。前几个月我又试图山寨<span lang="EN-US">C#</span>，但是无奈<span lang="EN-US">C#</span>实在是太复杂，所以转而去做<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">GacUI</span></strong>。图形界面（<span lang="EN-US">GUI</span>）类库我也写了不少。继高中的时候为<span lang="EN-US">RPG</span>而开发的两个控件类库之后，在上大学的过程中使用<span lang="EN-US">OpenGL</span>开发的两次<span lang="EN-US">GUI</span>类库均告失败。后来还封装了一次<span lang="EN-US">Windows</span>的<span lang="EN-US">API</span>（<strong style="mso-bidi-font-weight: normal"><span lang="EN-US">Vczh GUI</span></strong>），试图让其易用性接近<span lang="EN-US">VCL</span>或<span lang="EN-US">WinForm</span>。毕业后我又尝试发了若干次基于渲染的<span lang="EN-US">GUI</span>，换了几次架构，一直到现在正在开发的<span lang="EN-US">GacUI</span>才感觉走上了正轨。我在这个过程中得到的一个结论就是：<span lang="EN-US">Windows Presentation Foundation</span>的设计实在是太完美了&#8230;&#8230;在做这些东西之余，我还开发了三次三维物体的软件渲染程序，前两个是在毕业前做的，最后一个是一年前因为一下子不知道要如何利用业余时间来充实生活而开发出来的，目的是用于打发时间。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">在这里我想可以回答一个月前不能很好地回答师弟师妹们的一个问题了。如何能够在微软找到工作？因为我把我上面做的这些东西都写进了简历。同时如果你们到了大四才来问这个，就已经太迟了&#8230;&#8230;<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">值得一提的是，我从大三开始指导一名基础几乎是零的、比我低两个年级的软件学院的一位学生学习编程。为了让对方在接受我为期<span lang="EN-US">3</span>年的训练之后有扎实的<span lang="EN-US">C++</span>基础、熟练的单元测试编写水平以及能够靠直觉给出一些不算太差的设计，我回顾了许多关于<span lang="EN-US">C++</span>的内容，特别是给指针的几节课备课了好几天，并且每一天都要出一个作业。在这个过程中我深刻的感觉到，如果要快速提高自己的编程水平的话，你必须总是去做一些你做得出来，但是难度大到只要再难一点点你就做不出来的事情。再这么坚持好些年之后，肯定会进入高手的行列。因此我在安排作业的过程中，有意推迟了关于指针的内容。首先让对方接受变量和分支循环，然后要养成一个好的风格（譬如说不能老是用一个字母给变量命名之类），然后学会操作数组，接下来才是关于没有强制类型转换的指针的一些操作，并且在一个月之内做出一个带单元测试的字符串类。指针的重点是要对方深刻的理解，&#8220;指针本身就是一个指向位置的数字&#8221;这么一个概念。为此我特别设计（但没有实现）了一门只带有一个全局无限长数组的汇编语言来讲述指针背后一些复杂的概念。之后就是一些关于面向对象的知识、设计模式的知识、还有跟脚本引擎有关的一些东西。该学生的毕业设计是一个简单的动态语言的脚本引擎，并且该脚本引擎的实现正确地运行了我在上面模仿<span lang="EN-US">Linq</span>的一个列表处理函数库。这个实现闭包一层套一层，到处都在给一个物体添加删除函数，创建各种延迟执行的迭代器，很是能够考验一个脚本引擎的实现。对方毕业后被网易招去了，并且在待遇上给予了一些人文关怀。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">自己的编程历程不仅包括自己在业余时间内做的这些程序，而且也包括在微软实习和工作的过程。高中的时候就听说了华南理工大学有微软俱乐部的事情，再加上自己对微软也持有一定的向往，因此在入学之后，除了学院的学生会以外，我就一直在密切关注着微软俱乐部的招新，并且忽略其它所有社团。不过说实话在学生会和微软俱乐部的工作也纯属打酱油，没干过什么正事儿。大二的时候微软搜索技术中心（<span lang="EN-US">STC</span>）来微软俱乐部收简历的时候，我在路上碰到了陈健老师，也就是之前提到的班主任，就跟她说了这个事情。后来由于对方说我年龄太小而作罢，因为其它人全部都是研究生。到了大三的时候，陈健老师就跟我提到她可以找老同学帮我投微软的实习简历，因此我于<span lang="EN-US">2008</span>年<span lang="EN-US">3</span>月份接到了微软上海的电话面试。电话面试有两次，第一次对方是一位<span lang="EN-US">HR</span>，第二次则是一位软件工程师。在第二次电话面试的过程中，我们聊了上面提到的<span lang="EN-US">FreeScript</span>，还针对一些数据结构和框架设计的问题进行了热情洋溢的讨论。没过几天，我就收到了面试通知，前往上海闵行区的紫竹数码信息港面试。那是我人生中的第一次面试。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">微软的面试安排精确到秒，这跟某些公司比起来要人性化许多，不会动辄浪费别人数个小时的时间。实习的面试一共有三轮，对话全部使用英语，尽管里面只有一个是外国人。我还依稀记得被那个年轻的老外面试的时候由于过于紧张，而导致一道简单的问题没有给出最优解的事情。不过他们最终还是让我进入微软位于上海的一个<span lang="EN-US">WCF Tools</span>小组实习。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">这个小组有一位让我十分尊敬的软件开发主管。主管先生是一位热爱敏捷并且经常投身于实践中的人。他在我长达<span lang="EN-US">4.5</span>个月的实习过程中，教给了我很多软件工程上的东西，而其中最重要的、让我受益匪浅的则是关于单元测试的内容。除此之外，我也体验了快速迭代、<span lang="EN-US">Scrum</span>会议、结对编程以及基于源代码版本管理系统（我们使用的是<span lang="EN-US">TFS</span>）进行多人协作开发的流程。在经历了为<span lang="EN-US">TechEd</span>大会修改<span lang="EN-US">PetShop</span>制作<span lang="EN-US">WCF</span>的<span lang="EN-US">Demo</span>、为<span lang="EN-US">Visual Studio 2010</span>的<span lang="EN-US">WCF</span>开发工具修<span lang="EN-US">bug</span>和开发一个具有高度可扩展性的配置文件编辑器之后，我于<span lang="EN-US">2008</span>年<span lang="EN-US">12</span>月份结束了在微软的实习。经过了这次实习，我对源代码的掌控能力也得到了提高，并且直接体现在我利用业余时间开发的项目的代码质量上。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">在实习结束之前，我获得了一次面试全职员工（<span lang="EN-US">FTE</span>）的机会。当时形势十分严峻。<span lang="EN-US">2008</span>年美国的次贷危机于<span lang="EN-US">10</span>月份正式影响微软上海，公司在那一段时间决定减少全职员工的招聘数量。而我是<span lang="EN-US">11</span>月份进行转正的面试，结果这件事情令我十分紧张。后来主管先生表示他的个人建议是希望我毕业后留下来继续工作，让我吃了一颗定心丸。实习生转全职员工的面试一共有五轮。其中令我印象非常深刻的是有一轮的面试官问了我很多非常复杂的问题，最后还考了我一道关于线索二叉树在线更新的问题，不过我已经记不清楚具体是什么内容了。我只记得我花了很长时间终于想到了一个正确的算法之后，时间就结束了，根本来不及在白板上写代码。后来我终于通过了面试，少数的几个名额里面终于被我拿走了一个。不过听说几个月后限制开始放宽，没有我面试的时候那么困难了。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">在实习和面试的过程中，我觉得华南理工大学软件学院开设的很多课程其实都是十分有用的，特别是关于数据结构、设计模式和软件测试的内容。这些都是在工作中十分有用的知识，并且也需要在今后的工作中继续积累这些东西的经验。只不过因为学院学生人数众多，而一个新的学院总是免不了缺乏一些师资力量，所以我有很多同学都表示很难体会到课本中所提到内容的作用。想必如今应该比我们那几年要改善许多了。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">面试结束到获得<span lang="EN-US">offer</span>中间隔了几十天，最后<span lang="EN-US">HR</span>的通知在除夕的那一天终于到来了。之后的半年时间我就在学校里面继续做自己的事情，偶尔参加几个活动介绍经验等等，还有就是跟一些人出去游玩。毕业后动身前往上海微软。中间发生了一些事情，因为名额变动的问题，我虽然拿的是<span lang="EN-US">WCF Tools</span>的<span lang="EN-US">offer</span>，但是最后却被安排到<span lang="EN-US">SQL Server</span>组，在此之前我并没有收到通知。由于我比较不喜欢数据库，对<span lang="EN-US">SQL Server</span>了解很浅，所以我做了一年半的<span lang="EN-US">SQL Server Management Studio</span>（也就是传说中的&#8220;界面&#8221;）的开发。在这期间我跟同事们传播了一些关于单元测试、界面开发、设计模式、<span lang="EN-US">Linq</span>和语法分析器的知识。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">这一年半的经历让我成长了许多，主要是比起实习，正式工作的时候总是免不了经常要跟别的团队、公司、民族、国家和物种进行热情洋溢的广泛交流，而且还占用了不少的时间。有些时候还要坐飞机前往美帝，感受一下社会主义的优越性。正式软件的界面部分十分复杂，不仅要在操作系统的<span lang="EN-US">DPI</span>变动以及本地化（大部分内容是把界面上的文字翻译成别的语言）的过程中界面的布局需要自动调整，以便不让一些文字或者按钮只显示一半，还要照顾各式各样的残疾人（特别是失去视力的人群），并且对于某些自绘的复杂内容还要提供一些运行时的接口，使得自动测试团队可以完成他们的工作。这个经历让我感受到了开发一个严谨的界面是多么地不容易。另一个感受是关于需求变更的。设计模式的存在就是为了抵御需求变更，这个真理我直到工作之后才能明白。你必须把一个软件的架构设计得如此之好，才能在需求大规模变更之后，还能在整体上让你的代码是漂亮的、易于修改的、高性能的、并且是安全的。每一次改动都不能是打补丁，你总是需要重构来使得你的代码在任何一刻都在整体上是好的。为了达到这个目标，就需要熟练掌握并使用设计模式来开发项目。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">微软的跟别的公司比起来罕有一个好处就是他会给你很多时间，让你慢慢把软件做好。而这个好的定义，当然是以功能和可维护性为重点。倘若一段代码以非常精妙的方法来高速完成一个任务，但是却复杂到哪怕写遍了注释也不能让后续维护的人看懂的话，那这段代码是没有实用价值的。一段好的代码，不在于它的设计有多么巧妙，不在于它的算法有多么高深，而在于它可以被几千个人同时开发<span lang="EN-US">10</span>年，并且在持续添加功能的过程中，不会因为过于混乱而导致出现了重写的需要。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">后来我因为一些原因申请了到微软亚洲研究院（<span lang="EN-US">MSRA</span>）的人事调动。<span lang="EN-US">2011</span>年<span lang="EN-US">1</span>月份我在获得了经理的批准之后，从上海前往北京参加研究院的面试。这一次面试仍然有五轮。这次面试很难，其中一个面试官因为在我的简历上发现了很多跟编译器有关的东西之后，决定让我实现一个<span lang="EN-US">strncpy</span>函数，要求是<span lang="EN-US">CPU</span>对内存的访问次数要最少。这包含了很多诸如带宽、对齐和二进制字节位移操作等各种问题。方法本身就已经很繁琐，再加上纸上写代码总是免不了要犯错误，所以我依然没有时间把整个程序写完。另一个面试官老外在年轻的时候也做过一些编译器的事情，让我出乎意料的是他在面试的过程中没有跟我出题目，反而就编译器的各种算法和问题聊了整整一个小时，基本上我会的知识全部都因为要回答问题而说了出来。之后我跟这个人产生了深厚的友谊。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'">不久之后我就获得了调动的批准。在做了一些包括给上海的<span lang="EN-US">SQL Server</span>团队建立单元测试标准之类的收尾工作之后，我于<span lang="EN-US">2011</span>年的<span lang="EN-US">4</span>月份前往北京，正式成为微软亚洲研究院的一员，做一些跟分布式系统相关的研究。<span lang="EN-US"><o:p></o:p></span></span></p>
<p style="text-indent: 21.2pt; margin: 0cm 0cm 0pt; mso-char-indent-count: 2.02" class="MsoNormal"><span style="font-family: '微软雅黑','sans-serif'" lang="EN-US"><o:p>&nbsp;</o:p></span></p><span style="font-family: '微软雅黑','sans-serif'; font-size: 10.5pt; mso-bidi-font-size: 11.0pt; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA">过往的这些事情给了我很多的启示。在程序员的生涯里面，最重要的就是保持对编程的热情，不要被生活的琐事所磨灭。其次是要给自己不断地创造一些足够困难但是又有办法完成的挑战，这样才可以总是让自己保持着一个快速前进的状态。最后，记得要感谢国家。</span> <img src ="http://www.cppblog.com/vczh/aggbug/162252.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2011-12-16 23:34 <a href="http://www.cppblog.com/vczh/archive/2011/12/16/162252.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>春节随想——人的标签</title><link>http://www.cppblog.com/vczh/archive/2011/02/09/139816.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 08 Feb 2011 17:09:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2011/02/09/139816.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/139816.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2011/02/09/139816.html#Feedback</comments><slash:comments>13</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/139816.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/139816.html</trackback:ping><description><![CDATA[<p>流水账。</p>
<p>今年春节回家的时候带了三本书。去年中秋节的时候外服发了200块钱的卓越卡（啊，不是公司的，啊，说出来没人信），我觉得自己还是个看书的人，因此买了8本书。不过我觉得自己大概不是个做文学的人，所以买的都是些什么数学啊物理的民科书。里面有本讲时间的，原本以为是物理，结果是本哲学书，坚持到看完了才知道。之后8本只看了1/4本，于是到了春节。家里的电脑是02年买的，到现在就跟屎一样，因此给家里添置了一台。不过在这之前的三天，只能把上网的时间用来看书了，然后把这本书给解决掉了。接下来解决了那本哲学的，现在在看一本我也不知道他在说什么的。</p>
<p>我是怀着一种什么心情来看书的呢？觉得不看书就如同有负罪感一般，因此总是千方百计想把它们看完。看完的时候是什么感觉呢？除了接收到了知识感到欣喜以外，还有一种神奇的感觉类似&#8220;如释重负&#8221;的感觉。我觉得很不舒服。</p>
<p>对比起自己更加喜爱的编程，我突然想起在大学的时候，不写程序也是有负罪感的，不过现在没有了。当时其实我也很坦白，说不写程序会有负罪感，并且引以为豪，以此证明自己是一个爱写程序的人。现在想起来是有点滑稽。人每天都说话，说话就是生活，但有一两天你没说话，你有没有负罪感呢？想必是没有的。没有人会觉得一两天不说话会对不起自己或者对不起别人。当然吃饭是不一样的，不吃饭是会死的。写程序对于现在的我来说就如同说话。写程序就是生活，但是有一两天你没写程序，其实也觉得没什么，这种事常有。但是自己是不会因此就再也不写程序的，就如同你自己不会因为一两天不说话就永远不说话一样。当然如果你一两天不吃饭，估计就永远不吃饭了&#8230;&#8230;</p>
<p>那写程序对于我是什么呢？初中的时候，觉得写程序很有意思，自己搞着搞着总能摆弄出一点事情来，没有理过微软的fellow们。高中的时候，觉得写程序是一种追求，很有远大理想，看到了微软主页上的那三十多个fellow，觉得自己将来就是想成为他们一样的人。大学的时候，觉得写程序是一种义务，虽然自己再也不拿fellow当终极目标了，觉得修炼自己就是一种快乐，觉得反正自己的目标大概是要成为一个厉害的人的，不管将来是不是fellow都无所谓。到了现在，写程序俨然变成了生活的一部分。自己仍然对自己有严厉的要求，写出来的代码要高性能要好改还要富有艺术感，但已经不觉得厉害不厉害是一件多么大不了的事情了。我就是写程序，写程序就是我，反正迟早是要厉害的，那只是时间问题，也就不执着于那个了。</p>
<p>因此我现在没有宣称自己是喜欢写程序的人了，而宣称我自己就是写程序的人。想必当年觉得&#8220;喜欢写程序&#8221;就是自己身份的一个标签，认为只要不写程序，我也就不成为我，而且对不起贴在身上的标签了。如今，程序还是在写，但标签已经没了。激情在，理想在，功夫也在，程序也一直在写。只是写程序已经变成了自己想写就写，不想写就不写，随心所欲的状态了。</p>
<p>因此对比起看书，说不定自己仍然很在意&#8220;喜欢看民科书&#8221;的标签。实际上我也是这么做的，对外宣称自己是喜欢看民科书的人，而不是看民科书的人，跟写程序相反。所以大概会有这种感觉罢。</p>
<p>想想以前也有过不少爱好，譬如4岁的时候开始的钢琴，8岁的时候开始的看民科书。10岁的时候开始的糊变形金刚，14岁的时候开始的写程序。坚持下来的也就是看民科书跟写程序了。然后经历都是差不多的，一个事情自己做得好，然后就慢慢有了追求，然后就变成了标签，如果顺利的话那个事情也就成为了生活了。</p>
<p>什么时候看民科书能成为生活呢？我想能看的民科书的数量也是有限的，大概也只能当个标签吧，说不定哪天自己写程序写爽了，也写写跟编程相关的民科书，然后就贴上了个&#8220;喜欢写民科书&#8221;的标签了。</p>
<img src ="http://www.cppblog.com/vczh/aggbug/139816.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2011-02-09 01:09 <a href="http://www.cppblog.com/vczh/archive/2011/02/09/139816.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>10年编程之路（2010年度总结）</title><link>http://www.cppblog.com/vczh/archive/2010/12/19/136903.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 18 Dec 2010 17:17:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2010/12/19/136903.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/136903.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2010/12/19/136903.html#Feedback</comments><slash:comments>33</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/136903.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/136903.html</trackback:ping><description><![CDATA[<p>距离元旦也就十几天了，2010就要过去了。从第一行Hello World到现在，已经有10年了，所幸从未中断，因此从某种意义上来讲，我已经写了10年的程序了。每个人回顾以往走过的路的时候，往往会发现今天的结果来源于之前的一些&#8220;关键步骤&#8221;。显然我也是一样的，所以这次的总结跟以往不同，就不列出之前做过的种种程序，而是聊一聊这些关键步骤和影响我的人给我带来的影响。当然算得上关键步骤的，只能是那些能够左右人生轨迹的事情。</p>
<p><strong>老爸、外婆和爷爷<br></strong>这倒不是说老爸老妈把我生下来了怎么样怎么样。老爸在我幼小的时候教我一些简单的数学，给了我很多书，还有外婆教我识字，结果就是我从大概二年级开始就能够阅读老爸留给我的一些科普读物了。这些科普读物是他小的时候看的，上面还有语录，每一篇的几位都是伟大的思想指引我们前进云云。当然这并不妨碍书本的内容的质量。老爸的书也都一直保存得很好，后来我爷爷也给我弄了一套科普的启蒙读物，现在还保留着，只不过很多翻烂了。这套书是翻译的，小日本写的，不过内容却十分丰富。里面包含了数学、物理、生物、手工和一些其他的很多东西，甚至连汽车和飞机的结构都有。加上外婆也十分赞成并且指引我看这些书，其结果就是从小就对一些科学的事情感兴趣——当然也包括数学。从三年级开始到中学，老爸就给我买一些数学奥林匹克的书。当然这并不是让我去参加竞赛用的，只是他觉得既然他小时候也喜欢搞数学那我也应该继承这个优点，从而就让我去弄那些东西了。在五年级的时候，那次全市的数学竞赛老爸也帮了我很多，我也拿了很好的成绩。维持了那么多年从不间断的强大的自信心和信念就是从这个时候开始的。人喜欢搞一些事情很大程度上都是因为那些事情曾经被搞得很好，因此我也就喜欢上数学了，后来有机会体验到了数学的定理和公式的美妙之处，让我一发不可收拾。</p>
<p><strong>汕头市华侨中学的领导们<br></strong>这是个好学校。我整个读书的生涯，唯一一次体验到什么是素质教育就是在这里。可是后来由于各种微妙的问题导致这所学校的竞争力下降，这从某种程度上来说算是悲哀吧。我第一次接触到编程就是在这里。初中二年级的时候，学校开Basic的课，但是并没有试图让我们参加竞赛——其实连提都没提，只是就这么当成正常的课来上。把编程学得好，满足下面两个条件的话基本上可以说就是在走捷径，第一个是会从心底里对公式和定理产生美的感觉，第二个就是要持续不断地在编程上体会到成就感。这也是我为什么在一篇写给师弟师妹的文章里面提到刚开始的时候学习制作软件界面也是十分重要的，因为这会让你产生源源不断的动力，好让你给以后学习算法打下精神基础。QBasic教完自然就教Visual Basic了，当然都是很浅的内容。不过我由于受到吸引，从那以后就一直往书店里面跑，去扫荡各种跟Visual Basic有关的书，后来学到了不少。我初二在新华书店很偶然的发现了那本《Visual Basic高级图形程序设计教程》，不过坦白说我其实是被插图吸引的。那个时候发现Visual Basic竟然可以仅凭代码绘制出那么漂亮的图形，从而兴趣提高了不少。不过学习这个也是很辛苦的，这导致我不得不在初三的时候就去寻找并学习立体解析几何，高中的时候提前学习数学分析，都是为了看懂这本书啊。这本书我从初二一直看到上了大学，还带去宿舍看，看了好多年才把它每一页都琢磨透。这从某种程度上来说也算是缘分吧。</p>
<p><strong>英语补习老师李培涛<br></strong>初中的英语被我一不小心搞的一塌糊涂，甚至到了快不及格的地步了，所幸当时<strong>我妈（特别感谢）</strong>非得让我找一个英语的补习老师，所以就遇到了李老师了。虽然说补习课是要交钱的，不过李老师人倒是很好，不是为了收钱而收钱，还是花了很大精力实践了因材施教的。我的英语就被他给搞好了。我们知道英语对于编程来说是不可或缺的一个重要条件，因为中文的资料从数量或者质量上来说，都远远比不上英文的资料。如果英语不好，这除了阻止知识到达你的大脑里面以外，没有好处。</p>
<p><strong>汕头市第一中学的张朝阳老师<br></strong>高一的时候是张老师给我们上的计算机课，这个时候他告诉我们有NOI这种东西，不过我着实对算法没什么兴趣，因为那个时候我对图形更感兴趣，而且绝大多数图形的算法都不是搜索算法，而是跟数学知识有着更直接的联系。因此我就没有花多少时间在算法上面了。不过其实什么时候学习算法并不重要，只要你在工作之前学了就好了。原本那个时候也想靠NOI看看能不能混个保送什么的，由于我其实也不太认真做这个，因此只好亲自高考了。但是在这里我并不是说张老师教给了我什么知识，其实那段时间我都是靠自学。只不过因为我在非NOI的编程竞赛里面的成绩很好，所以他给我大开方便之门，让我可以利用学校的各种资源。我们都知道万恶的学校经常会不知不觉做出一些扼制青少年素质全面发展的事情，因此张老师给我的方便是十分重要的，包括我可以拥有机房的钥匙以便我在任何时候可以进去使用计算机写程序。课还是要上的，但是由于我每一年都参加NOI，所以自习课我就可以跑去机房了，写代码的时间也就大大增加了，这着实是十分有好处。</p>
<p><strong>CSTC的同僚们<br></strong>CSTC我现在也搞不清楚究竟他们的使命是干啥，不过印象里面就是北京工业大学的几个写代码比较厉害的人搞起来的。我有幸在上高中的时候接触到了他们，其中曾毅和唐良两个人对我的帮助很大。曾毅告诉我为了将来的前途也好，为了自己编程能力的发展也好，搞一个好学校总是必须的。唐良是在我上了大学之后告诉我这个世界上还有《算法导论》这本书，让我的数据结构和算法知识有了十分稳固的基础。当然其实不会数据结构和算法并不是说你就写不了什么复杂的程序，而只是导致你写出来的复杂的程序质量很差性能比较低而已。在高中的时候我已经做出了一个pascal的无指针版本的解释程序了，不过在这个时候我说实话除了链表以外，什么都不知道，编译原理也不知道，所有的东西都是硬凑出来的。当然程序还是能运行的，就是写好之后就无法再修改了，实在改不下去。</p>
<p><strong>华南理工大学的陈健老师<br></strong>高三的时候写出来的pascal解释器实在是让我十分兴奋，所以在刚入学不久听说我们的班主任陈建老师教编译原理的，我就跟她说我对这方面有兴趣了，而且当时还为我的下一个解释器写了一个很长的设计文档。这份文档一开始只是写给我自己的，后来顺便就给她看了。陈老师倒是没说什么，过了许久，给了我一本《编译原理》。当然这不是龙书，说实话那本课本也非常糟糕，只是这让我知道这个世界上还有这种东西，也就足够了。大一的时候迅速看完了这本书，觉得很不爽，就把龙书搞到手，然后看了一部分。大一结束的时候就做出一个面向对象带模板和垃圾收集的静态类型脚本语言了，陈老师实在是功不可没。作为老师，能教你什么是不重要的，告诉你你还有什么不会才是最重要而且最有用的。</p>
<p><strong>华南理工大学的陈天老师<br></strong>这位老师给我们上了大一的C++课，不仅功底扎实，而且可以课也讲得很好，无奈在我大三的时候说是实在不行了，跑去做程序员了。我就不对这件事情作评论了。陈天老师不仅告诉了我《设计模式》是十分重要的，而且也经常鼓励我进行更加深入的学习，对我帮助很大。</p>
<p><strong>g9yuayon<br></strong>这是个人才啊，而且编程水平也十分地令人叹为观止。不过他对我帮助最大的莫过于告诉我这个世界上还存在着《Parsing Techniques》了。这是世界上最好的描述语法分析的书，连龙书的前几个章节都不如这本书讲得好。当然龙书还是包含了后端的，而《Parsing Techniques》是只有前端的。不仅如此，他还给了我不少论文看。其实如果看得下去的话，论文带来的帮助远比算法要大得多。因为数据结构和算法真正普遍实用的也就那么几种，其实知识量是十分少的，还比不上数学分析。既然数学分析一年就可以上完，那实用数据结构和算法其实是不需要花那么久的。不过那些更加深刻的数据结构和算法当然不在此列了——还是很多的。但是论文，是方向性十分强，而且解决的问题其实范围更狭窄的东西。只不过如果认真研读论文的话，可以学到很多知识以外的东西，譬如说作者是如何整理他们的结果的。遇到好心的作者的话你连他们怎么发现这个事情都可以知道。由于从小就喜欢数学，所以看论文的时候看得十分入迷，也就看得更加认真仔细了。g9yuayon介绍给我的论文的确都是十分漂亮的，在我掌握了知识的同时，又让我的基础变得更扎实，并且对编程也更加喜爱了。</p>
<p><strong>龚敏敏一伙<br></strong>这倒是一个共同作用的结果，也是我第一个联系比较紧密的圈子。群里面的人都分布在各大公司，而且水平都不错，并且都是在研究图形学的。至于说为什么会跟他们接触，当然是因为高中的时候对图形学特别热衷的关系了。虽然后来转去做编译器了，不过学习图形学并不是一个浪费，因为这个漫长的过程让我的数学知识变的扎实，而且也产生了很多题目让我练习编写一些至少有点小规模的程序。实践是检验真理的唯一标准，这话是不错的。</p>
<p>我还要提一下<strong>LYT同学</strong>。LYT并没有在编程上帮助我，其实是我在教LYT写代码，只是LYT肯让我教那么久着实也不容易。为了教LYT学会写简单的编译器，让我不得不将我学过的知识从头到尾整理了一遍，而且还让我思考如何使得一个人在学会编程的同时可以保持乐趣、自信心和良好的习惯。这个过程十分有意义，不仅让我有一个机会可以从头整理我学会的知识，思考一些更加深刻的东西，让自己对知识的掌握更加深刻和牢固，而且其实对被教也是有帮助的。利己利人，何乐不为。LYT经过了我三年的精心指导，从对编程什么都不知道开始，最终顺利拿到了网易的offer，而且工资也没比我低多少，实在是让我感到十分高兴。</p>
<p>在我2009年7月份毕业之后就去了Microsoft而且尚未跳槽。从毕业后开始到现在这段时间<strong>现任女朋友<span style="FONT-FAMILY: 'Calibri','sans-serif'; FONT-SIZE: 10.5pt; mso-bidi-font-size: 11.0pt; mso-ascii-theme-font: minor-latin; mso-fareast-font-family: 宋体; mso-fareast-theme-font: minor-fareast; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA" lang=EN-US>2A</span><span style="FONT-FAMILY: 宋体; FONT-SIZE: 10.5pt; mso-bidi-font-size: 11.0pt; mso-ascii-theme-font: minor-latin; mso-fareast-theme-font: minor-fareast; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri">同学</span></strong>给了我很大的支持，并没有觉得整天宅在电脑前写代码看动画片很没前途，而且还帮忙寻找各种书带我去书店鼓励我等等，对此十分感谢。</p>
<p>当然这并不是说其他人就对我没有帮助，而只是没有满足文章一开始提出来的&#8220;左右人生轨迹&#8221;的条件而已。因为对我有帮助的人其实非常多<span style="FONT-FAMILY: 宋体; FONT-SIZE: 10.5pt; mso-bidi-font-size: 11.0pt; mso-ascii-theme-font: minor-latin; mso-fareast-theme-font: minor-fareast; mso-hansi-theme-font: minor-latin; mso-bidi-font-family: 'Times New Roman'; mso-bidi-theme-font: minor-bidi; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri">，志同道合的朋友也不少</span>，在这里我就不一一列举了。</p>
<p>祝各位读者们也能够对编程感兴趣而且在这个道路上不断坚持越走越远。</p>
<p>&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;<br>后记。突然想起来我忘记写上，其实小日本的动画片都是一些非常具有教育意义的东西，这让我学会了很多黑暗的社会没有任何机会让我知道的人生的道理。大家一定要看啊。<br><br>&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;&lt;&gt;<br>后记2。今天空明流产说他是搞图形那一群人里面为数不多的还做做shader前端的，所以我再提一下，啊哈哈哈。</p>
<img src ="http://www.cppblog.com/vczh/aggbug/136903.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2010-12-19 01:17 <a href="http://www.cppblog.com/vczh/archive/2010/12/19/136903.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>读《希望对入门级的程序员有所帮助，有时间不妨看看》有感</title><link>http://www.cppblog.com/vczh/archive/2010/01/31/106896.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sun, 31 Jan 2010 11:44:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2010/01/31/106896.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/106896.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2010/01/31/106896.html#Feedback</comments><slash:comments>10</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/106896.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/106896.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp; 也是cppblog一位同学写的文章，原文在<a style="TEXT-DECORATION: underline" href="http://www.cppblog.com/chinapeter2008/archive/2010/01/30/106777.html" target=_blank>这里</a>。<br><br>&nbsp;&nbsp;&nbsp; 其实总的来说这篇文章还是没什么大的问题，你看那五点粗字标题，就是在告诉你不仅要写好的程序，还要写有用的程序。不过进了公司老板很难给你写没用的程序的，这点就忽略了。紧扣着客户的需求写是好事，不过这跟广大的大学同学们还是没什么关系，所以最后一点就忽略了。咱慢慢看前面的四点。<br><br>&nbsp;&nbsp;&nbsp; 第一点说在校期间的实习是很重要的。这一点当然是对的，不过下面的论据有点问题。先看后面的。公司要能干活的人是真的，学计算机搞创新搞研究能拿奖那也是真的，只是拿的是图灵奖不是诺贝尔奖。这个诺贝尔奖有点问题啊，没有数学没有计算机（他老人家死得太早了，原谅他），所以数学和计算机就自立门户了。<br><br>&nbsp;&nbsp;&nbsp; 在校实习可以赚工作经验。为什么这么讲呢？（华南理工大学的师弟师妹在2009年的时候告诉我们，金山公司给实习，做的东西不会拿去卖的，你们做完就完了。道听途说，谢绝跨省。）一般大公司都会给你真刀真枪的东西。写的代码会被最终用户运行，修bug的结果也是被最终用户运行的。到时候会有一大堆人指导你该怎么做的，因为如果你写的代码太烂他们也不好意思把你的代码拿去用是不是。<br><br>&nbsp;&nbsp; 但是说学校教的东西与社会脱节就不好了。要我是校长肯定会拍案而起：&#8220;你们把学校当成什么东西了，Java速成班？&#8221;学校教的很多东西都是基础知识，根据《Teach yourself programming in 10 years（想看的自己去google）》，4年是远远不够成为一名优秀的程序员的。我们的确需要花大量的时间在基础课上面，譬如说掌握一两门语言和一点API让你们可以做出真正有用的东西啦，数据结构，网络，数据库，编译原理，操作系统原理，等等等等。但现在的事实是很多高三的学生们在填志愿的时候还不知道自己学了计算机就会上了贼船，所以大量的人是大一的人才开始写代码的。4年当然不够了，所以在学习基础课的时候，我们还需要自己给自己出点难题，写点代码。在我看来，<strong style="COLOR: #ff0000"><span>学校只需要保证一个几乎把自己所有的时间投入到代码中去的人能够找到合理的工作</span>就好了</strong>。谁让他花那么多时间玩游戏的（其实我也喜欢玩，但我不会没日没夜的，写完代码才会玩的），那将来结果不尽人意只能怪自己了。<br><br>&nbsp;&nbsp;&nbsp; 需要注意一点的是，上面那句话最后几个字是&#8220;合理的工作&#8221;而不是&#8220;理想的工作&#8221;。为什么呢？这跟你学了什么东西是很有关系的。找到理想的工作还要有一个前提，跟学校无关的，就是你要挖掘出自己的兴趣所在。你往那方面不短拼命练习，就可以保证你可以只找你喜欢的工作，找到了当然是理想的了。如果你并不是特别喜欢写代码，但是也成为了一名不错的程序员的话，那只能说是合理了。好工作，但你不喜欢罢了。<br><br>&nbsp;&nbsp;&nbsp; 先总结一下，学校教基础，实用的自己去学。至于那些理论课有什么用，<span>当你一个工程的代码写到了好几万行而且里面绝大多数都不是用来处理UI和SQL的时候，你就能开始理解了</span>。<br><br>&nbsp;&nbsp;&nbsp; 第二点，思想周密谨慎。文章下面只有一句话，其实说是说对了，只是泛泛而谈也不能当指南来看。当然我并不是在批评作者，说不定人家本来就不想写指南，只是&#8220;读者有心&#8221;罢了。<br><br>&nbsp;&nbsp;&nbsp; 思想为什么要周密谨慎，因为计算机语言太低级，我们不得不去处理大量的其实跟我们要解决的问题没什么关系的细节。为了很好的掌控这些东西，就要学习学校教给你们的那些所谓与社会脱节的基础课啦。就跟学数学一样，就算你将来真的不用考计算微积分来吃饭，但好歹学那个东西还是提高了你的智商的。如果你有幸真的需要考那些基础课来混饭吃的话，那你就更会体会到它们的重要性了&#8230;&#8230;说白了还是那句话，实践出真知啊。趁着还在读书的时候赶紧写代码，等到将来被HR鄙视就晚了。<br><br>&nbsp;&nbsp;&nbsp; 第三点，不要因为代码简单就不想写。文中的一个观点就是，同一个东西，你写的次数越多，你解决它的方法就越美妙。这就是为什么我们要不断地重写重构的原因了，代码速度越快，<span>并且越容易维护的话，将来遇到需求变更你就不用觉得自己快死了一样。</span><br><br>&nbsp;&nbsp;&nbsp; 第四点就不评论了。记得在Channel9看一个叫eric的老头讲解haskell的时候，他不停的说要&#8220;Put your love in your code.&#8221;要有爱。为了能让自己的爱发挥作用，当然首先要让自己写出漂亮的代码了。<br><br>&nbsp;&nbsp;&nbsp; 总结：不要抱怨学校，高考志愿是你自己填的。总的来说文章的大道理还是对的，就是论据稍微有点什么，总之自己看着办吧。 
<img src ="http://www.cppblog.com/vczh/aggbug/106896.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2010-01-31 19:44 <a href="http://www.cppblog.com/vczh/archive/2010/01/31/106896.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>为什么要MVC</title><link>http://www.cppblog.com/vczh/archive/2010/01/08/105189.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Fri, 08 Jan 2010 11:58:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2010/01/08/105189.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/105189.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2010/01/08/105189.html#Feedback</comments><slash:comments>9</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/105189.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/105189.html</trackback:ping><description><![CDATA[<p>&nbsp;&nbsp;&nbsp; 最近在公司写了一大堆复杂的界面，终于体会到了前辈们那种上刀山下火海的感觉了。做完了之后回头想想，MVC还是有道理的。</p>
<br>&nbsp;&nbsp;&nbsp; 什么是MVC？其实可以简单的理解为一个有UI的程序可以划分为三个部分：数据层、逻辑层和应用层。当然这些名字是我乱起的。数据层顾名思义就是用来读写数据的地方，譬如说一个电话本的文件。逻辑层就是用户在界面上的操作的抽象，譬如说要通过名字来查找消息啦，给一个关键字求得筛选后的电话信息列表啦。应用层指的就是那一堆控件了。MVC三个字母分别指的是Model、View和Controller，也就是模型、视图和控制器了，分别对应于数据层、应用层和逻辑层。<br><br>&nbsp;&nbsp;&nbsp; 以前在看MVC的时候总是被一些教条主义的东西迷惑，说什么在MVC里面，MV解耦，所以M可被替换，V也可被替换。这个时候往往会感到迷惑。为什么模型，或者说数据层要被替换？为什么视图，或者说界面要被替换？其实这在一个不是复杂到神级级别的程序里面是不会发生的。但是MVC并不是为了让你能够实现模型被替换或者试图被替换而产生出来的，我觉得这个模式（其实这不是设计模式的其中一项，真的）更加重要的特点是可以让你的程序写起单元测试来更加容易。<br><br>&nbsp;&nbsp;&nbsp; 还是电话本，现在有一个要求，说在输入人的名字之后，只要系统检查出你超过0.5秒没有持续输入，那么底下的列表就会自动根据你上面的输入进行筛选。其实这有点像Outlook。这要怎么写单元测试？我们知道虽然正规的测试会有一大堆用来自动完成界面操作的工具啊，或者类库，但是作为单元测试来讲我们并不需要去做这种事情。因为单元测试是程序员写的，凡是程序员写的东西当然是需要尽快得到结果的。一般的开发方法是写一点代码，写一点测试，跑，有bug改没有bug继续。我们在开发程序的时候会不断地、频繁地跑单元测试，来看看我们的东西是不是有问题，或者在重构的时候我们对于我们的代码正确的信心会大一点。<br><br>&nbsp;&nbsp;&nbsp; 那界面怎么办呢？难道我们真的要去引入一个库来搞界面的自动测试吗？当然想要也可以，不过这毕竟太复杂，而且这一类的工具的稳定性其实都不是特别好，被误导的几率倒是大增。这仅仅是对于程序员来讲的，当然搞测试的那些人自有他们的办法。那既然我们不做界面的自动测试那怎么知道文本框被输入之后究竟筛选出来的数据对还是不对呢？<br><br>&nbsp;&nbsp;&nbsp; 答案：MVC。<br><br>&nbsp;&nbsp;&nbsp; 为什么View，也就是试图，也就是界面，可以被替换是一件很重要的事情？想一想，如果控件可以被换成单元测试的一段代码，那岂不是很爽么？举个例子，我们要告知用户说，我们的事情已经做了一半了，这个时候我们可能会去设置进度条的位置。但是&#8220;告诉用户说我们的事情已经做了一半&#8221;跟&#8220;设置进度条的位置&#8221;其实是完全无关的两件事情。因此我们的Controller要负责通知View说事情做了一半了，然后View就可以去设置进度条的位置了。现在我们把View换成单元测试的一段代码，这个时候就变成Controller通知测试程序说事情已经做到一半了，然后测试程序就会去检查说现在是不是应该做到了一半，如果应该，显然这个用例就通过了。<br><br>&nbsp;&nbsp;&nbsp; 那Model呢？Model可以简单的理解为数据源，其实当然不只是那么简单，不过这样理解会让我们更容易接受一点。数据源是什么，当你写单元测试的时候，去连接一个数据库来获得数据源，然后就操作Controller，这个时候你如果不亲自去读一下数据库，你怎么知道Controller给你的东西究竟是对的还是错的？显然Model我们也可以换掉，测试程序伪造数据成为一个Model，然后插入Controller，事情就解决了。数据是我们自己给的，那Controller应该提供什么我们也能知道了。<br><br>&nbsp;&nbsp;&nbsp; 于是，使用了MVC之后，单元测试想换Model就换Model，想换View就换View，测试什么就非常容易了。至于说用户停止输入0.5秒之后是不是会真的去进行数据的筛选，这个我们手工测试就好了，而且那些搞测试的人也会帮我们检查的。<br><br>&nbsp;&nbsp;&nbsp; 好吧，说到这里有人可能会问为什么我没有给出一个Demo？这东西太虚，实践实践自己体会一下就行了，而且MVC变形那么多，有Model-View-Presenter，还有最近兴起的Model-View-ViewModel等等，其实现都跟传说中的那个类似桥接模式的东西差别甚远。这个自己去看一看就好了。
<img src ="http://www.cppblog.com/vczh/aggbug/105189.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2010-01-08 19:58 <a href="http://www.cppblog.com/vczh/archive/2010/01/08/105189.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2005-2009年个人总结</title><link>http://www.cppblog.com/vczh/archive/2009/12/23/103835.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 23 Dec 2009 13:22:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/12/23/103835.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/103835.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/12/23/103835.html#Feedback</comments><slash:comments>40</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/103835.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/103835.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp; 今年事情比较多，第一个是自己终于从本科毕业了，第二个是自己找到了工作，拿了一份offer。虽然当初为了去Microsoft实习错过了一大堆其他公司的面试机会，只投了Microsoft，Google和百度。不过最后还是进了Microsoft，在这危机四伏的日子里，虽然说自己写了10年代码总归有点功力，但是也有运气的成分在。只是面试百度的时候我明明在简历上写了我人在上海，他非要我飞回广州面试，很不爽，拒了他。<br><br>&nbsp;&nbsp;&nbsp; 去年下半年几乎都投身Microsoft的实习，到了12月上旬回学校，于是从今年的元旦开始其实就是在学校里面混日子了。其实还好，完成了一个<a style="TEXT-DECORATION: underline" href="http://www.cppblog.com/vczh/category/6825.html" target=_blank>阉割版的Haskell编译器</a>当毕业设计，还做了一个<a style="TEXT-DECORATION: underline" href="http://www.cppblog.com/vczh/archive/2009/05/22/85424.html" target=_blank>C语言编译到机器码写入内存的编译器</a>，最后<a style="TEXT-DECORATION: underline" href="http://www.cppblog.com/vczh/archive/2009/10/09/98207.html" target=_blank>重写了Vczh Library++3.0</a>，还把它<a style="TEXT-DECORATION: underline" href="http://vlpp.codeplex.com/" target=_blank>开源</a>了，虽然还没最终完成。不过开源了之后就得补文档了，因此近期的开发进度可能会慢一点。<br><br>&nbsp;&nbsp;&nbsp; 回想大学四年，还是写了不少代码的，也足够拼成一张至少可以留住HR眼球的简历了。当初比较大的转变是我刚上大一，可怜的Borland就不行了，于是很喜欢的Delphi看来也有危机了，所以转去C++。幸好所学的知识并不是绑定在Delphi的平台上，因此刚开始也只觉得是换了个语法。不够C++实在是博大精深，里面可以用各种各样的范式写代码，比较突出的是元编程，虽然这种东西在现实生活中重要性不言而喻不过所占比例还是很小的。<br><br>&nbsp;&nbsp;&nbsp; 于是开始拿C++练手了。高中的Delphi时代写了不少游戏，积累了一个2D的游戏引擎，其实也不复杂，不过好歹也到了3.0了，里面有图形图像、音效、数据管理、脚本引擎，还有一个UI。因此C++上手了之后，自然是移植它了。移植的过程中发现C++的写法跟Delphi还是截然不同，因此Vczh Library++ 1.0基本上是失败告终，虽然那个Delphi的游戏引擎大部分都实现了。在开发的途中我曾经写了一个模仿显卡固顶管线的3D软件渲染器，不过最后一个Demo应该是在大二，用OpenGL实现了3D模型的骨骼动画，用的好像还是Halflife 1的几个模型，什么鸟啊，僵尸啊，警察啊。后来觉得实在是找不到美工，而且自己还有一项喜欢的，也就是写编译器了，所以干脆就集中力量搞编译器吧。<br><br>&nbsp;&nbsp;&nbsp; 第一个见得人的编译器应该是<a style="TEXT-DECORATION: underline" href="http://www.mscenter.edu.cn/prj/Prj_Info.aspx?id=1446" target=_blank>Vczh Jove Script</a>了。这个东西阉割了Java，然后实现了一次，主要是针对OOP，有继承，有虚函数，还有泛型。当然泛型我实现了跟C#一样的参数约束，也就是可以指定说某个类型参数必须继承与另一个类。数组使用引用计数，其他的都垃圾收集。当然最后发现数组用引用计数是不对的，会导致垃圾收集。<br><br>&nbsp;&nbsp;&nbsp; 之后我就对计算机的理论燃起了热情了，首当其冲当然是编译原理。当时受到了CSDN上那个袁泳的一点指导，其实主要不是技术上的，是方向上的，后来给我看了一本很厉害的书叫《Parsing Techniques》。很多知识都从这里面吸收了，然后就要开刀，当然是从最简单的正则表达式引擎下手。第一次写还是有点别扭，到现在一共写了三次，其中第二次是在第一次写完了之后觉得很不爽立刻重写的。写完了就轮到Syngram，是一个将文法写进C++然后自动变成语法分析器的小库。当然后来也重写了。<br><br>&nbsp;&nbsp;&nbsp; 上面的事情完成了之后就着手<a style="TEXT-DECORATION: underline" href="http://www.cppblog.com/vczh/archive/2008/07/01/55072.html" target=_blank>Vczh Free Script</a>的开发了。这是一个&#8220;纯&#8221;动态语言。为什么说纯呢，因为我坚持所有东西匿名（包括类定义，其实结果就是返回一个类型，像C#的System.Type，然后可以到处传），所以为了给一个东西命名就写一个赋值语句。当然不仅如此，我还实现了函数闭包，然后将之后的所有特性譬如说动态的Multiple Dispatch（虚函数是Single Dispatch）啊，namespace啊，类和继承什么的统统编译到函数闭包上，整个语言是匿名的。当然我还是把它是实现成一个C++的类库，如果你愿意在我的接口下面写插件的话，就可以跟Python一样直接应用到你自己的工程里面去了。<br><br>&nbsp;&nbsp;&nbsp; 在这个过程中我学习了很多关于编程语言方面的基础理论，还学了一点数学虽然我还是觉得数学有点难度。完了之后就开发一个小型的IDE，其亮点是就算代码是动态生成的，我也能捕捉到然后给你单步调试。不过这个由于稳定性并不是非常好，第一次将C++跟C#混起来用还是有点力不从心，因此就没拿出来贡献给大家了。<br><br>&nbsp;&nbsp;&nbsp; 之后就开始Microsoft的实习之旅了，在实习的过程中我首先封装了一次win32api的GUI部分，尽量达到跟Delphi一样好用，于是有了这个<a style="TEXT-DECORATION: underline" href="http://www.cppblog.com/vczh/archive/2008/08/25/59954.html" target=_blank>Demo</a>，然后做了阉割版Haskell——也就是Kernel FP了。当初叫这个名称我只是想看看实现一个最小的纯函数式语言的内核要怎么办，要包含多少功能（当然是越少越好，其他的都是语法糖或者库，不过不能让能力下降）。后来又看了一本书好像叫做《The Implementation of Functional Programmang Languages》，也很好看，学到了很多东西。<br><br>&nbsp;&nbsp;&nbsp; 于是2008年就结束了，进入2009年，做了一个CMinus，可以把C语言编译到内存里面，搞成x86的机器码，然后就能将一个写了代码的txt文件变成一个函数指针了。然后就毕业了。<br><br>&nbsp;&nbsp;&nbsp; 7月13日开始入职Microsoft，虽然说是在开发界面，不过我还是觉得需要自己仍然保持热情，于是工作结束之后自己要继续写自己的代码，也就是Vczh Library++ 3.0了。上面做了很多4个编译器，刚好针对语言的4中特性，这次看看能不能把它们综合起来，变成一个真正有用的脚本引擎。当然这不是重复劳动了，毕竟自己实现给自己带来的质的提高会比你纯粹用别人的要高很多。但至于最后怎么办，其实我还是觉得.NET的潜力比较大，总之挑战它是不明智的，但我还是想自己试一试。<br><br>&nbsp;&nbsp;&nbsp; 从第一个QBasic的Hello World到现在也差不多要10年了，初中因为不小心拿到了本QBasic的书然后戏剧性地开始了我程序员的人生，所幸中间没有间断过，而且也将对一贯来编程的激情很好的保存了下来，有增无减。至于说30岁（其实日本说的是35岁）就要转管理什么的，我还是不太相信，或者说我愿意就做一线的开发人员，或者架构师（当然这个跟通常意义上的架构师还是不一样的，有朝一日真的给我做了，我还是想跟一线的程序员一起写代码）。管理还是不适合我，毕竟我对钱（或者是权力？）没那么渴望，够花就好了，虽然我自己没多少钱。<br><br>&nbsp;&nbsp;&nbsp; 总之，要有激情，无论是对什么事情。剩下的就是要追求快乐，不同的人对快乐的定义还是不一样的，不过我目前只要能写有挑战性的代码，我就会觉得很快乐了。工作了之后因为在上海，瞬间感到了房价的压力。只是如果要我牺牲写代码的时间和乐趣去换取那些所谓的财产，我还是不太愿意的。 
<img src ="http://www.cppblog.com/vczh/aggbug/103835.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-12-23 21:22 <a href="http://www.cppblog.com/vczh/archive/2009/12/23/103835.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>李开复：我很惊讶大学生找工作要问家长</title><link>http://www.cppblog.com/vczh/archive/2009/11/16/101096.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 16 Nov 2009 08:52:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/11/16/101096.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/101096.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/11/16/101096.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/101096.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/101096.html</trackback:ping><description><![CDATA[<a href="http://news.cnblogs.com/n/51405/">http://news.cnblogs.com/n/51405/</a><br><br>　　年9月，刚刚辞去谷歌全球副总裁、大中华区总裁的李开复博士，又创立了旨在培育创新人才和新一代高科技企业的创业平台——创新工场。几个月来，李开复奔赴全国各地，四处招贤纳才。在三四十名得到面试通知的大学生中，一些人的求职理由让他感到非常惊讶。 <br><br>　　&#8220;我父母觉得我跟着李开复干就对了！&#8221; <br>　　&#8220;我父母觉得我应该去家跨国公司工作。&#8221; <br>　　&#8220;我父母希望我待在上海。&#8221; <br>　　&#8220;来应聘到底是你的决定还是你父母的决定？&#8221;听完这些大学生的回答，李开复感觉很无奈，便跟他们说：&#8220;我来发一封E－mail跟你的父母沟通一下吧。&#8221;&#8220;对不起，我的父母不会用E－mail。&#8221; <br><br>　　不会用E－mail的父母，居然告诉孩子应该去哪一个互联网公司工作，这不是很奇怪的事吗？11月14日，在第二届新东方家庭教育高峰论坛上，李开复跟大家分享了他最近的一些经历，并郑重地告诉在座的家长，在严管和压力下长大的孩子，虽然听话，但最后可能会失去管理自己的能力，甚至没法独立。 <br><br>　　他建议，在家庭教育中，家长对孩子要多信任、多放权，少严管、少施压。在当今时代，家长可能不懂下一代，不清楚孩子自己希望成为一个什么样的人。此外，如果家长帮孩子做了太多的决定，会让孩子形成一种心理：反正有父母帮我作决定，这不是我的责任。当他有一天面对独立，他的路反而会走得非常艰难。 <br><br>　　&#8220;我常常在大学演讲时听到学生举手问，你总告诉我们要追随我心，可我不知道我心是什么。你总告诉我们要学自己有兴趣的东西，但是我不知道自己的兴趣是什么。&#8221;李开复认为，过于严格的管教，已经使得一些从小生长在被动环境里的孩子，被培养成机器，他们听不到自己的声音，找不到自己的兴趣，不知道自己将成为什么样的人。 <br>　　李开复也承认，在实施家庭教育的过程中，每个人都会犯错，都会有管得太多的时候，但关键是要让孩子知道，最终的决定权掌握在他们自己手中。
<img src ="http://www.cppblog.com/vczh/aggbug/101096.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-11-16 16:52 <a href="http://www.cppblog.com/vczh/archive/2009/11/16/101096.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）架构师已死（转自UML软件工程组织） </title><link>http://www.cppblog.com/vczh/archive/2009/10/27/99578.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Tue, 27 Oct 2009 07:57:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/10/27/99578.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/99578.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/10/27/99578.html#Feedback</comments><slash:comments>56</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/99578.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/99578.html</trackback:ping><description><![CDATA[<p class=content>2006年的职场出奇的冷清，相比前几年，简历的数量和质量都大为不如，很难得找到三年工作经验以上的人，有一个不是特别笨，就是特别怪。就是么，干得好谁没事换工作啊！Simon是一家外企软件公司的总经理，最近给这个问题愁坏了。项目一个接一个的接下来，人手越来越紧张。虽然Simon是个极限编程的粉丝，但也不得不批准了一份又一份的加班申请。HR经理把这个问题归结到房价上，他的妙论是&#8220;怕失业了还不上房款，不敢跳槽&#8221;。</p>
<p class=content>这天，K项目组长Allen终于忍不住了，带了一个只有一年工作经验的小伙子要Simon面试，&#8220;很聪明！经验少了点。&#8221;</p>
<p class=content>Simon皱了皱眉毛，说：&#8220;你不知道这个职位最低要求是三年工作经验吗？&#8221;</p>
<p class=content>Allen说：&#8220;这已经是三个月里通过技术考试中最好的一个了，老大，试试吧。&#8221;Allen是Simon多年的哥们，比较随便。</p>
<p class=content>抵到面子上来，Simon只好让Allen把小伙子带进来。</p>
<p class=content>Simon的面试通常是三步曲：</p>
<p class=content>问题一：你能说说毕业后的主要工作经历吗？</p>
<p class=content>问题二：再说说你在公司的地位？</p>
<p class=content>问题三：你的发展目标是什么？等回答后，比如说构架师，他就跟着问：想象一下你当构架师的一天，说给我听听？</p>
<p class=content>小伙子回答第一问题很快很清楚，一年工作当然没什么东西。Simon觉得小伙子挺聪明。所以在小伙子回答了第二个问题后，问了一个发散性的问题：&#8220;你刚才说你在公司里处于中等水平，那比你差的人为什么会比你差呢？&#8221;</p>
<p class=content>这个问题是个陷阱。</p>
<p class=content>小伙子冒冒失失回答说：&#8220;我觉得他们每天工作是为工作而工作，工作没有责任感。&#8221;</p>
<p class=content>Simon点点头说：&#8220;是吗？那真是糟糕的员工。那你刚好比糟糕的员工好一点了？&#8221;</p>
<p class=content>小伙子的脸一下子红了，&#8220;我不是这个意思&#8230;&#8230;&#8221;</p>
<p class=content>&#8220;好了，那你说说比你好的人为什么比你强？&#8221;</p>
<p class=content>&#8220;我觉得他非常努力，工作很多年了还在学习各种构架，水平很高。&#8221;于是Simon就问那最后一个问题。果然，小伙子回答的是要成为构架师。大概70％的人想成为构架师。但是构架师是什么呢？</p>
<p class=content>Simon问道：&#8220;那你为什么要成为构架师呢？&#8221;</p>
<p class=content>小伙子一愣，大概还没有人这么置疑过他。&#8220;年纪大了，不能老写程序吧。&#8221;这个回答，让Simon想起关于他对什么是老的定义：当你希望做年轻人做的事情时，你就还年轻；如果你希望做老年人做的事情，你就老了。这和你出生了多长时间是没有关系的。</p>
<p class=content>Simon接着问：&#8220;好吧，那你说说你成为构架师以后，每天都会做什么？&#8221;</p>
<p class=content>小伙子说：&#8220;我还没想过，不过，我想应该主要是需求分析，设计构架吧&#8230;&#8230;&#8221;这大概是现在年轻人的通病，年轻人很容易追逐一些自己也不清楚的目标。</p>
<p class=content>Simon问：&#8220;那设计构架具体都做些什么呢？&#8221;</p>
<p class=content>小伙子这次的回答是：&#8220;比如，选择程序框架，决定用Spring或Struts等等。&#8221;</p>
<p class=content>&#8220;哦，那我问你，你怎么说服别人是用Spring还是Struts呢？&#8221;</p>
<p class=content>&#8220;如果我有经验，我会知道哪个更好&#8230;&#8230;&#8221;</p>
<p class=content>&#8220;是吗，但关于Spring或Struts的知识任谁都可以很容易得到。如果别人不同意你的建议，你怎么说服他？如果同意你的建议，那你不过是作出了和别人一样的认识，别人又凭什么认可你呢？&#8221;</p>
<p class=content>小伙子没想过构架师日子里还有一个说服人的工作，说：&#8220;我是构架师，我应该有权力做决定吧？&#8221;</p>
<p class=content>Simon想起权力的三种层次，第一层，任命；第二层，专业；第三层，品德。</p>
<p class=content>Simon问：&#8220;如果在一个成熟的软件企业里没有你所想象的构架师呢？或者说，构架师这种职业已经死亡或消失了呢？你会怎么定位你的职业？&#8221;</p>
<p class=content>小伙子显得很震惊。</p>
<p class=content>Simon画了一个系统构架，然后又给小伙子看了一段代码。</p>
<p class=content>&#8220;那一个更难懂？&#8221;Simon问。</p>
<p class=content>小伙子指着代码说：&#8220;代码难懂。&#8221;</p>
<p class=content>Simon的解释是：&#8220;这就是为什么实际上所谓的构架师不存在的原因。一个更简单的东西怎么会更有价值呢？每个人都能够画出这种构架图，但不是每个人都能写出好的代码。&#8221;</p>
<p class=content>送走了小伙子，Simon有点难受。他有点喜欢这个小伙子，但是，这又是一个被愚蠢的教育和误人子弟的技术杂志污染的家伙。Simon在自己的笔记本中加了一句话：中国程序员最愚蠢的认识之三：我想当构架师。前面两个赫然是：</p>
<p class=content>35岁后写不动程序了；</p>
<p class=content>我只要做Java（C＋＋）；</p>
<img src ="http://www.cppblog.com/vczh/aggbug/99578.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-10-27 15:57 <a href="http://www.cppblog.com/vczh/archive/2009/10/27/99578.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>丘奇数（Church Numerals）和lambda calculus</title><link>http://www.cppblog.com/vczh/archive/2009/05/11/82611.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Mon, 11 May 2009 12:30:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/05/11/82611.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/82611.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/05/11/82611.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/82611.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/82611.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 以前为了开发KFP，特别学习了一下lambda calculus（也就是我的博客的标题啦）。lanbda calculus是一门神奇的语言，在计算机出现之前就已经被搞出来了。这门语言只有三种语法，然后可以用这个语法来构造整数（！！！）、布尔型和很多递归数据结构等。<br><br>内含代码&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/05/11/82611.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/82611.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-05-11 20:30 <a href="http://www.cppblog.com/vczh/archive/2009/05/11/82611.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】How to make programming hard for yourself </title><link>http://www.cppblog.com/vczh/archive/2009/05/09/82432.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 09 May 2009 14:55:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2009/05/09/82432.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/82432.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2009/05/09/82432.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/82432.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/82432.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 这篇转载的文章主要讲了为什么写非工作的代码的时候，要时不时刁难自己。<br><br>内详。&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2009/05/09/82432.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/82432.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2009-05-09 22:55 <a href="http://www.cppblog.com/vczh/archive/2009/05/09/82432.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何学习编程（二）</title><link>http://www.cppblog.com/vczh/archive/2008/06/11/52880.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Wed, 11 Jun 2008 08:03:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2008/06/11/52880.html</guid><wfw:comment>http://www.cppblog.com/vczh/comments/52880.html</wfw:comment><comments>http://www.cppblog.com/vczh/archive/2008/06/11/52880.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.cppblog.com/vczh/comments/commentRss/52880.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/vczh/services/trackbacks/52880.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     接着上一篇文章继续往下讲。如果按照上一篇文章走下去的话，现在估计做了有些小软件了吧。字符串和图形都容易做大，而且对于潜意识上喜欢数学的最有希望的程序员们也是有吸引力的。但是这两种东西却不容易做好。等到程序到了一定规模的时候，维护和效率这两大问题就会凸显出来。心急吃不了热豆腐，为了解决维护和效率这两个经常会出现的问题，我们需要学习算法和架构。这两种东西是可以同时学的，但是一篇文章说不了多少东西，那么就从算法开始吧。<br><br>    程序员是需要开阔眼界的，光C#一门也是不行的，毕竟程序运行在各种平台上，有各种各样的语言。譬如Win32上的native C/C++、Delphi等，.NET上的C#和VB.NET，还有自成体系的Java，然后就是运行在mainframe上的COBOL，剩下的还有各种各样的函数式语言、脚本语言等等。熟悉了C#的人从Delphi入手不会很困难，从C/C++入手也可以了。这两门原本是本地语言的语言在编写程序的时候需要我们注意多一些的东西，典型的就是内存管理。这还是需要多加练习的，在这里就不多说了。<br><br>    说到算法，在这里首先向&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2008/06/11/52880.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/52880.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2008-06-11 16:03 <a href="http://www.cppblog.com/vczh/archive/2008/06/11/52880.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何学习编程（一）</title><link>http://www.cppblog.com/vczh/archive/2008/06/07/52414.html</link><dc:creator>陈梓瀚(vczh)</dc:creator><author>陈梓瀚(vczh)</author><pubDate>Sat, 07 Jun 2008 02:29:00 GMT</pubDate><guid>http://www.cppblog.com/vczh/archive/2008/06/07/52414.html</guid><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:     终于还是要写这种文章了。期末考试将至，写大程序没时间，写小程序没动力，只要演变成写文章了。之前的两篇字符串处理写完了仍然不过瘾，打算继续写关于递归下降法和LALR的事。后来想想还是暂时写写关于如何学习编程的好，毕竟这个问题对大家来说更加有益。<br><br>    本篇将是一个系列，重点讲述在外力很少的情况下如何自学编程，以及需要注意的一些地方。<br><br>    一般来说，一些所谓的『高手』或者老师会告诉人们算法是非常非常重要以至于会不会算法就是你会不会编程的唯一标准。不过事实上并非如此。掌握算法固然是好，只是大部分程序并不需要高深的算法，而且招人的时候仅仅要求会算法的公司也是很少的（而且很难进）。我并不是学院派的人，所以虽然我本人也推崇学习算法，但并不推崇一开始就学习算法。<br><br>    刚开始学编程的人总是不知道自己应该从哪里入手。实际上这是一个相当重要的问题。在我看来，学好变成有若干条件：<br>    ·兴趣<br>    ·数学/英语<br>    ·财力<br><br>    首先谈一谈兴趣。那些为了生计而寻找捷径学习编程的人并不&nbsp;&nbsp;<a href='http://www.cppblog.com/vczh/archive/2008/06/07/52414.html'>阅读全文</a><img src ="http://www.cppblog.com/vczh/aggbug/52414.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/vczh/" target="_blank">陈梓瀚(vczh)</a> 2008-06-07 10:29 <a href="http://www.cppblog.com/vczh/archive/2008/06/07/52414.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>