<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>冒号空间 &#187; 算法</title>
	<atom:link href="http://blog.zhenghui.org/tag/algorithm/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhenghui.org</link>
	<description>自然、人类、机器</description>
	<lastBuildDate>Fri, 16 Jul 2010 18:33:48 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>冒号课堂§4.2：逻辑范式</title>
		<link>http://blog.zhenghui.org/2009/09/15/colon-class-4_2/</link>
		<comments>http://blog.zhenghui.org/2009/09/15/colon-class-4_2/#comments</comments>
		<pubDate>Tue, 15 Sep 2009 05:00:43 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[Prolog]]></category>
		<category><![CDATA[控制]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[编程范式]]></category>
		<category><![CDATA[逻辑]]></category>
		<category><![CDATA[逻辑式编程]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=421</guid>
		<description><![CDATA[<b>逻辑范式</b>——当算法失去了控制（<em>再谈逻辑式编程</em>）<br/>
•	评价代码的复杂度，长短只是一个因素。程序员不是打字员，花在思考上的时间和精力远远超过花在键盘上<br/>
•	算法=逻辑+控制。其中逻辑是算法的核心，控制主要用于改进算法的效率]]></description>
			<content:encoded><![CDATA[<h1 style="text-align: center"><span style="font-family: 宋体">冒号课堂</span></h1>
<strong><span style="font-size: 13pt; font-family: 宋体">第四课 重温范式(2)</span></strong>

<!-- below comes from generated html -->
<head><link rel="stylesheet" href="http://blog.zhenghui.org/css/colonclass.css" type="text/css"></head>

<div lang="zh-CN" class="article" title="逻辑范式"><div class="titlepage"><div><div><h1 class="title"><a name="id599676"></a>4.2 逻辑范式——当算法失去了控制</h1></div><div><div class="author"><h3 class="author">郑晖</h3></div></div><div><div class="abstract" title="摘要"><p class="title"><b>摘要</b></p><p>再谈逻辑式编程</p></div></div></div><hr /></div><div class="toc"><p><b>目录</b></p><dl><dt><span class="section"><a href="#preview">！预览</a></span></dt><dt><span class="section"><a href="#question">？提问</a></span></dt><dt><span class="section"><a href="#explaination">：讲解</a></span></dt><dt><span class="section"><a href="#note">，插语</a></span></dt><dt><span class="section"><a href="#summary">。总结</a></span></dt><dt><span class="section"><a href="#reference">“”参考</a></span></dt></dl></div><div class="epigraph"><div class="literallayout"><p>道常无为而无不为</p></div><div class="attribution"><span>—<span class="attribution">《老子•道经》</span></span></div></div><div class="section" title="！预览"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="preview"></a>！预览</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    评价代码的复杂度，长短只是一个因素。程序员不是打字员，花在思考上的时间和精力远远超过花在键盘上
                </p></li><li class="listitem"><p>
                    算法=逻辑+控制。其中逻辑是算法的核心，控制主要用于改进算法的效率
                </p></li></ul></div></div><div class="section" title="？提问"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="question"></a>？提问</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>衡量软件复杂度是由代码的长度决定的吗？</p></li><li class="listitem"><p>为什么逻辑式的编码一般比过程式的更简洁？</p></li><li class="listitem"><p>逻辑式编程相比命令式编程有哪些优势和劣势？</p></li></ul></div></div><div class="section" title="：讲解"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="explaination"></a>：讲解</h2></div></div></div><p>
            问号提出：“逻辑式编程不是也很特别吗？前面似乎介绍得也不多。”
        </p><p>
            “那我们就用逻辑式语言Prolog再实现一次quicksort吧。”冒号说着将幻灯片翻页——
        </p><div class="informalexample"><pre class="programlisting">
/*快速排序法的Prolog实现 */
/* 定义划分法 */
partition(_,[],[],[]).                             /* 划分递归终点 */
partition(Pivot,[X|Rest],[X|Small],Big) :- 
X &lt; Pivot, partition(Pivot,Rest,Small,Big).        /* 比基准小的归入Small */
partition(Pivot,[X|Rest],Small,[X|Big]) :- 
X &gt;= Pivot, partition(Pivot,Rest,Small,Big).       /* 比基准大的归入Big */

/* 定义排序法 */
qsort([],[]).                                      /* 排序递归终点 */
qsort([Pivot|Rest],Sorted) :- 
partition(Pivot,Rest,Small,Big),                   /* 按基准划分子列 */
      qsort(Small,SortedSmall),                    /* 对前面的子列递归 */
      qsort(Big,SortedBig),                        /* 对后面的子列递归 */
      append(SortedSmall,[Pivot|SortedBig],Sorted)./* 子列合并 */ </pre></div><p>
            逗号挠挠头：“看不太懂哦，好在我记住了您的一句话：容忍无知。我忍了！”
        </p><p>
            大伙都乐了。
        </p><p>
            “本节课的焦点不是语言而是范式，因此对Prolog代码不详加解说。我只简单地说三点：首先，Prolog代码是由一系列事实（fact）、规则（rule）和查询（query）语句组成的<a class="link" href="#note1"><sup>[1]</sup></a>。其次，与大多数语言不同的是，大写字母或下划线开头的标识符是变量，其他的是常量或函数。请注意，这不是约定俗成，而是语法规定。最后，符号‘:-’等价于if；逗号‘,’等价于and。比如，我们可以用Prolog来表达一个断言：如果一个人未婚且为男士，那么他就是一光棍。”冒号转身在黑板上写下——
        </p><div class="informalexample"><pre class="programlisting">
/* X is bachelor if X is unmarried and male*/
bachelor (X) :- unmarried(X) , male(X). </pre></div><p>
            听见下面一阵嘀咕声，冒号忽地闪过一个念头：这个例子该不会触动了满足条件的某位同学的心事吧？顿了一会，继续说道：“逻辑式实现的排序虽不比函数式更简洁，但比起过程式来还是绰绰有余的。毕竟同属声明式，省去了不少有关变量赋值、迭代和流程控制方面的代码。我们再看一个更加典型的范例。”
        </p><p>
            黑板上出现了一幅树状图形——
        </p><div class="figure"><a name="id597056"></a><p class="title"><b>图4-1. 家谱</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://blog.zhenghui.org/img/colonclass/figure4-1.jpg" alt="家谱"></div></div></div><br class="figure-break"><p>
             冒号简作说明：“这是一个三代家谱图。已知每人的性别和父辈，要求判断任意两人之间的关系。我们先用Java来试一试——”
        </p><div class="informalexample"><pre class="programlisting">
class Person
{
    private Person parent;
    private boolean isMale;

    public Person(Person parent, boolean isMale)
    {
        this.isMale = isMale;
        this.parent = parent;
    }

    private boolean isSibling(Person other)
    {
        return parent == other.parent &amp;&amp; parent != null &amp;&amp; this != other;
    }

    public String getRelation(Person other)
    {
        if (other == null || this == other) return null;

        if (parent == other) return isMale ? "son" : "daughter";

        if (other.parent == this) return isMale ? "father" : "mother";

        if (parent == null) // this是老祖宗
        {
            if (other.parent == null) return null;

            if (other.parent.parent == this) return isMale ? "grandfather" : "grandmother";

            return null;
        }

        if (other.parent == null) // other是老祖宗
        {
            if (parent.parent == other) return isMale ? "grandson" : "granddaughter";

            return null;
        }

        // 非直系
        if (isSibling(other)) return isMale ? "brother" : "sister";

        if (parent.isSibling(other.parent)) return "cousin";

        if (parent.isSibling(other)) return isMale ? "nephew" : "niece";

        if (isSibling(other.parent)) return isMale ? "uncle" : "aunt";

        return null;
    }

    public static void main(String[] args)
    {
        Person a = new Person(null, true);
        Person b = new Person(a, true);
        Person c = new Person(a, true);
        Person d = new Person(a, false);
        Person e = new Person(b, false);
        Person f = new Person(b, true);
        Person g = new Person(c, false);
        Person h = new Person(d, true);
        Person i = new Person(d, false);
        Person j = new Person(d, true);
        // 以下省略。。。
     }
}</pre></div><p>
            “这段代码很平凡，毋需多言。再来看看逻辑式语言的做法。”冒号不愿过多地纠缠于细节，随即又换成了Prolog代码——
        </p><div class="informalexample"><pre class="programlisting">
/* 规则 */
/* 上下两代直系关系 */
father(X,Y)        :- parent(X,Y), male(X).
mother(X,Y)        :- parent(X,Y), female(X).
child(X,Y)         :- parent(Y,X).
son(X,Y)           :- parent(Y,X), male(X).
daughter(X,Y)      :- parent(Y,X), female(X).

/* 祖孙关系 */
grandparent(X,Y)   :- parent(X,Z), parent(Z,Y).
grandfather(X,Y)   :- grandparent(X,Y), male(X).
grandmother(X,Y)   :- grandparent(X,Y), female(X).
grandchild(X,Y)    :- grandparent(Y,X).
grandson(X,Y)      :- grandparent(Y,X), male(X).
granddaughter(X,Y) :- grandparent(Y,X), female(X).

/* 平辈关系 */
/* 若X与Y有相同的父辈Z，且X不是Y，则X与Y是同胞*/
sibling(X,Y)       :- parent(Z,X), parent(Z,Y), X\==Y. 
brother(X,Y)       :- sibling(X,Y), male(X).
sister(X,Y)        :- sibling(X,Y), female(X).
cousin(X,Y)        :- parent(Z,X), parent(W,Y), sibling(Z,W).

/* 上下两代旁系关系 */
uncle(X,Y)         :- parent(Z,Y), brother(X,Z).
aunt(X,Y)          :- parent(Z,Y), sister(X,Z).
nephew(X,Y)        :- parent(Z,X), sibling(Z,Y), male(X).
niece(X,Y)         :- parent(Z,X), sibling(Z,Y), female(X).

/* 定义一个普适关系relation，方便查询 */
relation(R, X, Y)       :-  relations(Rs), member(R,Rs), Q =..[R,X,Y], call(Q).

/* 事实 */
/* 关系列表 */
relations([parent,father,mother,son,daughter,grandparent,grandfather,
grandmother,grandchild,grandson,granddaughter,
                sibling,brother,sister,cousin,uncle,aunt,nephew,niece]).

parent(a,b). parent(a,c). parent(a,d).
parent(b,e). parent(b,f).
parent(c,g).
parent(d,h). parent(d,i). parent(d,j).

male(a).
male(b).
male(c).
female (d).
female (e).
male(f).
female (g).
male(h).
female (i).
male(j). </pre></div><p>
            叹号没有看出名堂：“Prolog代码并不比Java代码简短多少啊。”
        </p><p>
            “评价代码的复杂度，长短只是一个因素。程序员不是打字员，花在思考上的时间和精力远远超过花在键盘上。”冒号指出，“就拿此例来说，Java代码虽然并不复杂，但有不少的选择分支语句，次序很重要。稍有不慎，就会出现逻辑错误。另外如果我们把关系分得更细致些，比如区分叔伯舅、姑姨婶、堂兄表妹等；再加入姻亲关系，比如姑嫂婆媳、妯娌连襟等。这时你再来改写这段代码试试？”
        </p><p>
            引号听得头皮有些发麻：“那一定需要不少重重嵌套的if-else语句了。”
        </p><p>
            问号提出的问题更让人头痛：“如果我们不限于三代，再加上曾孙女、曾叔父之类的关系呢？”
        </p><p>
            逗号联想到一则笑话：“话说一对父子与一对母女联姻，作父亲的娶了那位女儿，作儿子的娶了那位母亲。本来关系已经够颠倒错乱了，雪上加霜的是这两对夫妇又各自有了子女，那位父亲终于精神崩溃了。”
        </p><p>
            大家哄笑着：这下彻底乱套啰。
        </p><p>
            “前面的Java代码之所以没有嵌套，得益于及时退出的一些return语句。如果考虑到超过三代的关系以及多重交叉的关系，许多语句都得改写。可见上述代码是多么地脆弱！” 冒号就棍打腿，“再看Prolog代码，如果要求更细的血亲关系、增加姻亲关系或三代以上的关系，只需引入新的规则和事实即可，不会影响原有代码。下面列出几个示范语句——”
        </p><div class="informalexample"><pre class="programlisting">
/* 规则 */
/* 配偶原则 */
father(X,Y)          :- spouse(Z,X), mother(Z,Y).
mother(X,Y)        :- spouse(Z,X), father(Z,Y).
husband(X,Y)      :- spouse(X,Y), male(X).
wife(X,Y)            :- spouse(X,Y), female(X).

/* 父系的堂、姑兄弟姐妹 */
paternal_cousin(X,Y) :- father(Z,X), father(W,Y), sibling(Z,W).
/* 母系的舅、姨兄弟姐妹 */
maternal_cousin(X,Y) :- mother(Z,X), mother(W,Y), sibling(Z,W).

/* 姻亲关系 */
father_in_law(X,Y) :- spouse(Y,Z), father(X,Z).
mother_in_law(X,Y) :- spouse(Y,Z), mother(X,Z).
son_in_law(X,Y)    :- spouse(X,Z), daughter(Z,Y).
daughter_in_law(X,Y) :- spouse(X,Z), son(Z,Y).

/* 曾祖孙关系 */
great_grandparent(X,Y) :- grandparent(Z,Y), parent(X,Z).
great_grandchild(X,Y)  :- grandchild(Z,Y), child(X,Z).

/* 事实 */
/* 新引入的关系 */
relations([husband,wife, paternal_cousin,maternal_cousin,
father_in_law,mother_in_law,son_in_law,daughter_in_law,
great_grandparent,great_grandchild]).

parent(pa,a).
spouse(a,as).
spouse(ds,d).
spouse(cs,c). </pre></div><p>
            句号方悟其妙：“这样的代码既无层层嵌套，也无次序分别。比起过程式，编写轻松得多，程序的可维护性和可扩展性也更高。”
        </p><p>
             “此外另有妙处。逻辑式与过程式和函数式的一个不同之处是，它没有明显的输入、输出之分。上面的程序不仅可以用来判断任意二人之间的关系，还能倒过来通过关系来找人。”冒号板书了几行字——
        </p><div class="informalexample"><p>输入查询：relation(R,a,ds)          /* a与ds的关系是什么？ */</p><p>输出结果：R=father_in_law</p><p>输入查询：great_grandparent (pa,X)  /* pa是谁的曾祖？*/</p><p>输出结果：X=e;X=f;X=g; X=h; X=i; X=j;</p></div><p>
            引号义务作翻译：“这告诉我们两件事：a与ds是翁婿关系，pa有曾孙e、f、g、h、i和j。”
        </p><p>
            “逻辑式语言着眼于关系而非函数，对付这类问题正是它的拿手好戏。”冒号声音逐渐高亢，“大家应该都听说过等式‘算法+数据结构=程序’吧？这是Pascal设计者Niklaus Wirth的一本著作的书名，它刻画了过程式尤其是结构化编程的思想。后来Robert Kowalski进一步提出：<span class="strong"><strong>算法=逻辑+控制</strong></span>。其中逻辑是算法的核心，控制主要用于改进算法的效率。在逻辑式编程中，程序员只需表达<span class="emphasis"><em>逻辑</em></span>，而<span class="emphasis"><em>控制</em></span>交给编程语言的解释器或编译器去管理。”
        </p><p>
            “所以程序员的负担大大减轻了。”问号接口道，“逻辑式编程听起来真是不错，但不知Prolog程序能否与Java程序对接呢？”
        </p><p>
            冒号回答：“任何程序之间的对接都是可能的，只是不同的对接方式在复杂度和效率上有所差异而已。除了通过程序之间的通讯（如socket）或可执行文件的直接调用外，Prolog与C、C++、Java、C#、VB、Perl、JavaScript等多种语言之间，还能借助工具进行源代码转换<a class="link" href="#note2"><sup>[2]</sup></a>或通过双向编程接口互嵌代码。具体到Java，一方面可以通过JNI （Java Native Interface）与Prolog引擎相连<a class="link" href="#note3"><sup>[3]</sup></a>，另一方面可以利用Prolog引擎的Java实现来完成JVM上的集成<a class="link" href="#note4"><sup>[4]</sup></a>。”
        </p><p>
            句号请求：“能否总结一下逻辑式编程的优缺点？”
        </p><p>
            冒号欣然应允：“由于逻辑式编程模拟人类的逻辑思维，故而在机器证明、专家系统、自然语言处理、博弈等人工智能领域如鱼得水，同时在非学术领域的知识管理、智能决策分析等方面也能大显身手。同为声明式，它与函数式一样比命令式更简洁、更抽象、更少副作用，运用得当能大大提高生产效率，还能用于<span class="term">快速原型</span>（rapid prototyping）开发。但缺点是运行效率偏低，可掌控性较差，与常规的过程式思维差异较大，更适合<span class="emphasis"><em>基于规则</em></span>（rule-based）而不是<span class="emphasis"><em>基于状态</em></span>（state-based）的应用<a class="link" href="#note5"><sup>[5]</sup></a> 。此外，相对而言逻辑式语言还不够成熟和完善。” 
        </p><p>
            逗号“抗议”道：“我怎么感觉经过这么一反刍，胃里的负担更重了？”
        </p><p>
            冒号略带歉意地笑了笑：“在所有编程范式中，函数式与逻辑式与传统思维方式的差别最大，此前的介绍又过于简单，因此今天特意多谈了些。既然有人提意见，那就我就适可而止了。最后请允许我画蛇添足：在代表计算机最高水平的人工智能领域中，这两种范式发挥着举足轻重的作用。单凭这一点，它们也是值得学习和借鉴的。好了，大家先休息十分钟，出去活动活动筋骨吧。”
        </p></div><div class="section" title="，插语"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="note"></a>，插语</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p><a name="note1"></a>
                    用数学逻辑的话来说，事实与规则是公理，查询就是待证的定理。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    如Prolog Café和P#能分别将Prolog代码转化为Java代码和C#代码。
                </p></li><li class="listitem"><p><a name="note3"></a>
                    比如JPL通过JNI与Prolog FLI （Foreign Language Interface）将Java与SWI-Prolog桥接起来。
                </p></li><li class="listitem"><p><a name="note4"></a>
                    比如JIProlog（Java Internet Prolog）是一个用Java实现的Prolog解释器，为Java和Prolog提供双向API。类似的还有JLog等。
                </p></li><li class="listitem"><p><a name="note5"></a>
                    交互式或事件驱动式应用通常是基于状态的。
                </p></li></ol></div></div><div class="section" title="。总结"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="summary"></a>。总结</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    代码的长度不是衡量软件复杂度的唯一标准。其中的逻辑结构越复杂、越微妙、受需求变化的影响越大，软件越难控制和维护。
                </p></li><li class="listitem"><p>
                    算法=逻辑+控制。逻辑式编程将算法中的控制部分大都移交给编程语言，编程人员主要关注算法的核心逻辑。这样大大减轻了程序员的负担，编码也更简洁易懂，更具可维护性和可扩展性。
                </p></li><li class="listitem"><p>
                    有别于过程式和函数式，逻辑式没有明显的输入和输出之分。
                </p></li><li class="listitem"><p>
                    逻辑式编程不仅适用于人工智能方面的学术领域，同样广泛适用于各种涉及知识管理、决策分析等方面的应用领域。
                </p></li><li class="listitem"><p>
                    相对于命令式，逻辑式更简洁、更抽象、更少副作用，能提高生产效率，还能用于快速原型开发。但在运行效率、可掌控性、语言成熟度等方面有所欠缺。另外，因其思维方式独特而鲜为人用，适合基于规则而非基于状态的应用 。
                </p></li></ul></div></div><div class="section" title="“”参考"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="reference"></a>“”参考</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
                    Michael Lee Scott．Programming Language Pragmatics．San Francisco：Morgan Kaufmann，2000．620-650
                </p></li><li class="listitem"><p>
                    Robert A. Kowalski．Algorithm = Logic + Control．Communications of the ACM，1979，22(7)：424-436
                </p></li></ol></div></div></div><a class="a2a_dd addtoany_share_save" href="http://www.addtoany.com/share_save?linkurl=http%3A%2F%2Fblog.zhenghui.org%2F2009%2F09%2F15%2Fcolon-class-4_2%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A74.2%EF%BC%9A%E9%80%BB%E8%BE%91%E8%8C%83%E5%BC%8F">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/09/15/colon-class-4_2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>冒号课堂§3.1：泛型范式</title>
		<link>http://blog.zhenghui.org/2009/09/08/colon-class-3_1/</link>
		<comments>http://blog.zhenghui.org/2009/09/08/colon-class-3_1/#comments</comments>
		<pubDate>Tue, 08 Sep 2009 14:52:31 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[STL]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[泛型编程]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[编程范式]]></category>
		<category><![CDATA[迭代器]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=369</guid>
		<description><![CDATA[<b>泛型范式</b>——抽象你的算法（<em>泛型式编程简谈</em>）<br/>
•	算法串联数据，如脊贯肉；数据实化算法，如肉附脊<br/>
•	泛型编程是算法导向的，即以算法为起点和中心点，逐渐将其所涉及的概念内涵模糊化、外延扩大化，将其所涉及的运算抽象化、一般化，从而扩展算法的适用范围<br/>
•	思想是鸡，结论是蛋]]></description>
			<content:encoded><![CDATA[<h1 style="text-align: center;"><span style="font-family: 宋体;">冒号课堂</span></h1>
<strong><span style="font-size: 13pt; font-family: 宋体;">第三课 常用范式（1）</span></strong>
<p><strong><span style="font-family: 宋体;">课前导读</span></strong></p>
<p style="text-indent: 18pt;"><span style="font-family: 宋体;">这一课介绍了四个常用的编程范式：泛型式、元编程、切面式和事件驱动式。与上节课类似，这些介绍也都是入门性的，目的是让读者认识到范式的多样性与差异性。</span></p>
<p style="margin: 0cm 0cm 0pt; text-indent: 18pt;"><span style="font-family: 宋体;">本课共分四节——</span></p>
<p style="text-indent: 18pt;"><em>1.</em><em><span style="font-family: 宋体;">泛型范式——抽象你的算法</span></em></p>
<p style="text-indent: 18pt;"><em>2.</em><em><span style="font-family: 宋体;">超级范式——提升语言的级别</span></em></p>
<p style="margin-left: 18pt;"><em>3.</em><em><span style="font-family: 宋体;">切面范式——多角度看问题</span></em></p>
<p style="margin-left: 18pt;"><em>4.</em><em><span style="font-family: 宋体;">事件驱动——有事我叫你，没事别烦我</span></em></p>

<!-- below comes from generated html -->
<head><link rel="stylesheet" href="http://blog.zhenghui.org/css/colonclass.css" type="text/css"></head>
 
<div lang="zh-CN" class="article" title="泛型范式"><div class="titlepage"><div><div><h1 class="title"><a name="id632473"></a>3.1 泛型范式——抽象你的算法</h1></div><div><div class="author"><h3 class="author">郑晖</h3></div></div><div><div class="abstract" title="摘要"><p class="title"><b>摘要</b></p><p>泛型式编程简谈</p></div></div></div><hr /></div><div class="toc"><p><b>目录</b></p><dl><dt><span class="section"><a href="#preview">！预览</a></span></dt><dt><span class="section"><a href="#question">？提问</a></span></dt><dt><span class="section"><a href="#explaination">：讲解</a></span></dt><dt><span class="section"><a href="#note">，插语</a></span></dt><dt><span class="section"><a href="#summary">。总结</a></span></dt><dt><span class="section"><a href="#reference">“”参考</a></span></dt></dl></div><div class="epigraph"><div class="literallayout"><p>以类行杂，以一行万</p></div><div class="attribution"><span>—<span class="attribution">《荀子•王制篇》</span></span></div></div><div class="section" title="！预览"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="preview"></a>！预览</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    算法串联数据，如脊贯肉；数据实化算法，如肉附脊
                </p></li><li class="listitem"><p>
                    泛型编程是算法导向的，即以算法为起点和中心点，逐渐将其所涉及的概念内涵模糊化、外延扩大化，将其所涉及的运算抽象化、一般化，从而扩展算法的适用范围
                </p></li><li class="listitem"><p>
                    思想是鸡，结论是蛋
                </p></li></ul></div></div><div class="section" title="？提问"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="question"></a>？提问</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>泛型编程有哪些优点？</p></li><li class="listitem"><p>STL有哪些要素？各自有什么作用？</p></li><li class="listitem"><p>泛型编程的泛化对象是什么？</p></li><li class="listitem"><p>泛型编程的核心思想是什么？</p></li></ul></div></div><div class="section" title="：讲解"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="explaination"></a>：讲解</h2></div></div></div><p>
            冒号重新开讲：“你们会不会经常遇到这样的情景：一遍又一遍地写着相似的代码，有心将其归并，却因种种原因无法践行。”
        </p><p>
            逗号心有戚戚焉道：“是啊，有时明明两个函数的实现几乎是一模一样的，就因为某些参数不匹配，无法合而为一。”
        </p><p>
            “有一种编程范式可以解决这个问题，它打破了不同数据类型之间的壁垒，让你的代码不再臃肿，这——就是<span class="term">泛型编程</span>。”冒号的语调和说辞不免令人联想到电视上的减肥广告，“Generic Programming，简称GP，其基本思想是：将算法与其作用的数据结构分离，并将后者尽可能泛化，最大限度地实现算法重用。这种泛化是基于<span class="term">模板</span>（template）的<span class="term">参数多态</span>（parametric polymorphism），相比OOP基于<span class="term">继承</span>（inheritance）的<span class="term">子类型多态</span>（subtyping polymorphism），不仅普适性更强，而且效率也更高。这不能不说是一种异数——我们知道，普适性往往是以效率为代价的。如果一定要找出代价的话，那就是其用法稍微复杂一些，可读性稍微差一些。GP最著名的代表是C++中的STL（Standard Template Library），其后亦为Java、C#、D等语言所吸纳<a class="link" href="#note1"><sup>[1]</sup></a>。此外，一些函数式语言如Ocaml、Standard ML、Haskell等也支持GP。”
        </p><p>
            冒号写下两段代码——
        </p><div class="informalexample"><p><span class="emphasis"><em>C++（泛型编程）：</em></span></p><pre class="programlisting">
    template &lt;typename T&gt;
    T max(T a, T b)       // 求出两个数中的较大者
    {
            return  (a &gt; b) ? a : b;
    }</pre><p><span class="emphasis"><em>C（宏定义）：</em></span></p><pre class="programlisting">
    #define max(a,b) ((a) &gt; (b) ? (a) : (b))
    </pre></div><p>
            “求两个数中的较大值是经常遇到的问题。”冒号解说着，“对于静态类型语言<a class="link" href="#note2"><sup>[2]</sup></a>来说，若参数类型不同，即使函数体相同也不能合为一体。如果语言不支持<span class="term">重载</span>（overload），还可能出现maxInt、maxLong、maxFloat、 maxDouble之类的函数名，冗赘而丑陋。尽管在C中可用<span class="term">宏定义</span>（macro definition）来实现，但无法保证类型安全，而C++模板则兼顾类型安全和代码重用，并且由于是在编译期间展开的，效率上也不损失。不止于此，C++支持运算符重载，除数值类型外，一切定义了‘&gt;’ 运算的数据类型均可调用max函数，真是一举N得，N趋向无穷大啊！”
        </p><p>
            冒号边说边比划，夸张的语气和手势逗得大家都笑了。
        </p><p>
            引号提出疑问：“Java的一切对象都是Object类型，只要将所有参数都换成Object类型，岂不也是一种泛化？”
        </p><p>
            冒号答道：“首先，基本类型如int、float等不是Object的子类，虽然Java 新增了自动装拆箱（autoboxing/unboxing）的功能，但要付出性能的代价。更重要的是，这将不可避免地需要类型的<span class="term">显式转换</span>（explicit conversion或cast），无法在编译期间施行严格的类型检查，由此丧失了静态类型语言的优势，为bug大开方便之门。这也是Java最终引入泛型的原因，虽然有些姗姗来迟。类似地，C/C++中的通用指针void *也有类型安全问题。”
        </p><p>
            句号发表他的看法：“泛型虽好，似乎只是某些局部才用到的技术，不具有前面几种范式的渗透性。”
        </p><p>
            冒号听罢不语，返身在黑板上写下几道题——
        </p><div class="informalexample"><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
                        从一个整数数组中随机抽取十个数，对其中的素数求和
                    </p></li><li class="listitem"><p>
                        将一个无序整数集中所有的完全平方数换成其平方根
                    </p></li><li class="listitem"><p>
                        从学生成绩表中，列出门门都及格且平均分在70分以上的学生名单
                    </p></li><li class="listitem"><p>
                        在一个着色二元树中，将所有的红色结点涂成蓝色
                    </p></li><li class="listitem"><p>
                        将一个字符串从倒数第三个字符开始反向拷贝到另一个字符串中
                    </p></li><li class="listitem"><p>
                        每从标准输入读取一个非数字的字符X，于标准输出打印“X不是数字字符”
                    </p></li></ol></div></div><p>
            句号暗忖，这有何难？不过是些常规题罢了。不料冒号的问题却出人意表：“请问它们之间有何共同之处？能否共享同一段代码？”
        </p><p>
            见众人缄默已久，冒号接着投影出一段代码——
        </p><div class="informalexample"><pre class="programlisting">
template &lt;class Iterator, class Act, class Test&gt;
void process(Iterator begin, Iterator end, Act act, Test test)
// 对容器中在给定范围内（即起于begin止于end）所有满足给定条件的元
//素（即test（元素）==true）进行处理（即act（元素））
{
    for ( ; begin != end; ++begin)    // 从头至尾遍历容器内元素
        if (test(*begin)) act(*begin); // 若当前元素满足条件，则对其采取行动
}</pre></div><p>
            “STL有三要素：算法（algorithm）、容器（container）和迭代器（iterator）。算法是一系列切实有效的步骤；容器是数据的集合，可理解为抽象的数组；迭代器是算法与容器之间的接口，可理解为抽象的指针或者游标。”冒号讲述道，“<span class="strong"><strong>算法串联数据，如脊贯肉；数据实化算法，如肉附脊</strong></span>。只有抽象出表面的数据，算法的脊梁才能显现。以上几题看似风马牛不相及，若运用泛型思维，便可发现它们的共性：对指定集合中满足指定条件的元素进行指定处理。用模板语言，寥寥数行即勾勒完毕。”
        </p><p>
            问号诧异道：“相比前面的max模板，这儿连元素的数据类型T都不见了？”
        </p><p>
            冒号回答：“元素被容器封装了。”
        </p><p>
            问号追问：“可连容器也看不到啊？”
        </p><p>
            冒号料有此问：“容器通过它的迭代器参与算法。”
        </p><p>
            句号豁然开朗：“通过模板，泛化了容器——可以是数组、列表、集合、映射、队列、栈、字符串等等；泛化了元素——可以是任何数据类型；泛化了处理方法和限定条件——可以是任何函数。”
        </p><p>
            冒号提醒道：“补上两点：这里的处理方法和限定条件不限于函数，还可以是<span class="term">函子</span>（functor）<a class="link" href="#note3"><sup>[3]</sup></a>——自带状态的函数对象；另外迭代器也被泛化了——可以从前往后移动，可以从后往前移动，可以来回移动，可以随机移动，可以按任意预先定义的规律移动。”
        </p><p>
            叹号由衷感叹：“果然强悍无比啊！”
        </p><p>
            逗号倒也心细：“最后一题中标准输入也算容器吗？”
        </p><p>
             “为什么不呢？只要一个对象配备了迭代器，它就可以作为容器来对待。I/O流上就有现成的迭代器，当然你也可以自行定制。索性我们来看看这道题的解法吧。”冒号给出了第六题的实现代码——
        </p><div class="informalexample"><pre class="programlisting">
#include &lt;iostream&gt;
#include “process.h” // 前述process所在的头文件

using namespace std;

// 判断字符是否为非数字字符
bool notDigit(char c)
{
    return (c &lt; '0') || (c &gt; '9');
}

// 打印非数字字符
void printNondigit(char c)
{
    cout &lt;&lt; c &lt;&lt; "不是数字字符" &lt;&lt; endl;
}

int main()
{
    process(istream_iterator&lt;char&gt;(cin), istream_iterator&lt;char&gt;(),
            printNondigit, notDigit);

    return 0;
}</pre></div><p>
            逗号打量了半天：“这里完全看不到I/O读取的过程，也看不到通常的迭代循环，简洁得难以置信。”
        </p><p>
            冒号补充道：“不光是代码简洁，它还让人摆脱了底层编码的细节，在更高更抽象的层次上进行编程设计。”
        </p><p>
            引号发觉：“开始谈起泛型编程时，您特别强调它对数据类型的抽象。现在看起来，它也能对函数进行抽象呢。”
        </p><p>
            “说得没错，条件是被抽象的函数或者方法具有相同的签名（signature）或接口（interface）。不过别忘了，在C和C++中的函数——准确地说是函数指针——也能作为数据类型。但不管怎样，这都表明泛型编程不仅能泛化<span class="emphasis"><em>概念</em></span>，还能泛化<span class="emphasis"><em>行为</em></span>。”冒号目光转向句号，“现在还有人认为泛型编程的渗透性不够强吗？”
        </p><p>
            句号腆然一笑。
        </p><p>
            “这些只是泛型编程的冰山一角。重要的是，我们不是在玩弄花哨的技巧，而是在用一种新的视角去审视问题。”冒号总结道，“泛型编程是<span class="emphasis"><em>算法导向的</em></span>（Algorithm-Oriented），即以算法为起点和中心点，逐渐将其所涉及的<span class="emphasis"><em>概念</em></span>（如数据结构、类）内涵模糊化、外延扩大化，将其所涉及的<span class="emphasis"><em>运算</em></span>（函数、方法、接口）抽象化、一般化，从而扩展算法的适用范围。这非常类似数学思维——当数学家证明完一个定理后，总会试图在保持核心思想的前提下，尽可能地放宽题设，增强结论，从而推广定理。外行人常以为数学定理最重要，其实数学思想才是数学的精髓。比如举世皆知的哥德巴赫猜想和费尔马大定理，人们在攻克它们的过程中产生的新思想、新理论、新方法，已远远超过了定理本身的意义。数学家们甚至不愿这些猜想被过早地解决，怕扼杀了会下金蛋的鸡。在他们眼里，<span class="strong"><strong>思想是鸡，结论是蛋</strong></span>。这也无怪乎STL会出自一位学数学的人之手了<a class="link" href="#note4"><sup>[4]</sup></a>。”
        </p><p>
            “我怎么觉得更像是出自一位菜场大妈之口呢？” 逗号打趣道，“不信你们听嘛，算法好比脊骨、数据好比猪肉、思想好比母鸡、结论好比鸡蛋。我没说错吧？”。
        </p><p>
            众人哑然失笑。
        </p></div><div class="section" title="，插语"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="note"></a>，插语</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p><a name="note1"></a>
                    但它们的实现机制却大不相同：C++和D采用类型模板（template），Java采用类型擦除（type erasure），C#采用类型具化（reification），相应的表现也有显著差异。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    静态类型语言在编译期间或运行之前施行类型检查（type checking）。后有详论。
                </p></li><li class="listitem"><p><a name="note3"></a>
                    又称function object，在C++中指重载了函数调用算符（operator()）的类，在Java和C#中可通过interface来实现。
                </p></li><li class="listitem"><p><a name="note4"></a>
                    STL的发明者Alexander Stepanov曾是莫斯科大学数学系的学生（1967-1972）。
                </p></li></ol></div></div><div class="section" title="。总结"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="summary"></a>。总结</h2></div></div></div><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>
                    泛型编程能打破静态类型语言的数据类型之间的壁垒，在不牺牲效率并确保类型安全的情况下，最大限度地提高算法的普适性。
                </p></li><li class="listitem"><p>
                    STL有三要素：算法、容器和和迭代器。算法是一系列可行的步骤；容器是数据的集合，是抽象化的数组；迭代器是算法与容器之间的接口，是抽象化的指针。算法串联数据，数据实化算法。
                </p></li><li class="listitem"><p>
                    泛型编程不仅能泛化算法中涉及的概念（数据类型），还能泛化行为（函数、方法、运算）。
                </p></li><li class="listitem"><p>
                    泛型编程是算法导向的，以算法为中心，逐渐将其所涉及的概念内涵模糊化、外延扩大化，将其所涉及的运算抽象化、一般化，从而提高算法的可重用性。
                </p></li></ul></div></div><div class="section" title="“”参考"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="reference"></a>“”参考</h2></div></div></div><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>
                    Bjarne Stroustrup．The C++ Programming Language, Special ed.．Reading, MA：Addison-Wesley，2000．507-516，549-560
                </p></li></ol></div></div></div><a class="a2a_dd addtoany_share_save" href="http://www.addtoany.com/share_save?linkurl=http%3A%2F%2Fblog.zhenghui.org%2F2009%2F09%2F08%2Fcolon-class-3_1%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A73.1%EF%BC%9A%E6%B3%9B%E5%9E%8B%E8%8C%83%E5%BC%8F">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/09/08/colon-class-3_1/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>
