<?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/logic-programming/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>冒号课堂§2.4：并发范式</title>
		<link>http://blog.zhenghui.org/2009/09/07/colon-class-2_4/</link>
		<comments>http://blog.zhenghui.org/2009/09/07/colon-class-2_4/#comments</comments>
		<pubDate>Mon, 07 Sep 2009 05:14:57 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[函数式编程]]></category>
		<category><![CDATA[对象式编程]]></category>
		<category><![CDATA[并发式编程]]></category>
		<category><![CDATA[线程]]></category>
		<category><![CDATA[编程范式]]></category>
		<category><![CDATA[过程式编程]]></category>
		<category><![CDATA[进程]]></category>
		<category><![CDATA[逻辑式编程]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=361</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: 宋体">第二课 重要范式(4)</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="id599705"></a>2.4 并发范式——合作与竞争</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>并发式编程有哪些好处？</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>
            “不但有，而且有很多。”冒号喝了一口水，悠悠地说，“<span class="term">并发式编程</span>（concurrent programming）就是其中之一。”
        </p><p>
            叹号有些惊讶：“并发式编程也算一种范式？它似乎更像是提高运行效率的一种手段。” 
        </p><p>
            “大谬不然。”冒号摇摇头，“真正的并发式编程，绝不只是调用线程<a class="link" href="#note1"><sup>[1]</sup></a>API或使用synchronized、lock之类的关键字那么简单。从宏观的架构设计，到微观的数据结构、流程控制乃至算法，相比通常的串行式编程都可能发生变化。随着硬件性能和用户需求的双重提升，并发式编程已成为不可回避的主题。毫不夸张地说，并发式编程是继OOP之后又一场思想和技术上的革命。只是相比OOP，尽管年龄相仿，但语言上不够支持，标准上不够统一，理论上不够完善，因而这场革命更具<span class="emphasis"><em>破坏性</em></span>和<span class="emphasis"><em>建设性</em></span>。现在我们来看一个例子，比较两种烧水泡茶的方案。”
        </p><p>
            说着冒号在黑板上写下——
        </p><div class="informalexample"><p>方案一：洗茶杯；放茶叶；灌水壶；烧水；水开后泡茶。</p><p>方案二：灌水壶；在烧水的同时，洗茶杯；放茶叶；水开后泡茶。</p></div><p>
            引号见多识广：“我记得这好像是运筹学中的例子，显然方案二更佳。从编程的角度来看，方案一是串行式编程，方案二是并发式编程——烧水的线程与洗茶杯放茶叶的线程是同时进行的。”
        </p><p>
            “如果方案一也用并发式编程呢？”冒号追问。
        </p><p>
            引号一愣，随即道：“必须先洗茶杯后放茶叶，洗茶杯放茶叶的同时也没法烧水，至于泡茶，更得等水开之后了。”
        </p><p>
            句号明白了冒号的用意：“这就是说，单凭并发式编程并不能保证提高程序性能，还必须在程序设计上下功夫。” 
        </p><p>
            问号继续深究：“即便如此，如果硬件不支持，并发式编程也未必能提高效率啊。比如在一台单处理器的主机上，多个线程只能是模拟的、逻辑上的并行，而非真正物理上的并行。”
        </p><p>
            “这话有一定道理。”冒号有限度地表示赞同，“但设计者应该未雨绸缪，总不能等到有了多处理器才想到并发式编程吧？再者，没有多处理器，是不是可以利用多台单处理器的主机同时运算呢？退一步讲，即使在一台单处理器的情况下，并发式编程有时也是必不可少的。它能保证不同用户、不同程序之间的公平竞争，这对多用户、多任务的系统而言尤为重要。此外，采用并发式编程同样可能提高性能。最典型的一个例子：当一个线程因为等待某种资源而被堵塞时，可以切换到其他线程而不至让CPU闲置。更重要的一点是，难道除了缩短程序运行时间外，并发式编程就没有别的好处吗？譬如打开一个网页，你是希望浏览器边下载边显示网页呢，还是下载完成后再显示？”
        </p><p>
            “当然是边下载边显示啦。” 叹号毫不犹豫地说，“如果每个网页都是下载完成后再显示，那还不把人给憋坏了！”
        </p><p>
            逗号加了一句：“浏览器加载文字和加载图像也应分开在不同的线程，如果用户对文字部分不感兴趣，不等图像显示就可直接关闭网页了。”
        </p><p>
            “如果网页被提前关闭，那也是假用户之手变相缩短了程序运行时间。”冒号接过话来，“还有一个常见的例子，包括浏览器在内的许多应用软件在运行中都会显示一个进程条。该进程条的更新需要一个单独的线程，从效率上看它起的作用是负面的，但大大提高了用户体验，是软件人性化的表现。再举一例，媒体播放器一般都会提供暂停、快进、倒退、快放、慢放等按钮，如果不采用多线程，它们将形同虚设。此处并发式编程的作用在于提高了软件的响应能力，也改善了用户体验——有谁愿意驾驶一辆启动后不能刹车、不能倒车、不能变速、油尽方停的汽车呢？”
        </p><p>
            “岂止是不愿意，简直是恐怖！”叹号加重了语气。
        </p><p>
            “对于操作系统、各种实时系统和诸如数据库、网络服务器等基于服务的系统来说，在响应用户请求的时间上和资源的合理分配上有着极高的要求，并发式编程更是不可或缺。”句号接着补充。
        </p><p>
            问号紧接着又问：“并发式编程还有其他用处吗？”
        </p><p>
            冒号应道：“不同编程范式采用不同的视角和方法来设计和开发软件。并发式编程<span class="emphasis"><em>以进程为导向</em></span>（Process-Oriented）、以任务为中心将系统模块化。我们都知道，模块化在编程中是个好东西。”
        </p><p>
            引号心存疑惑：“过程式中引入了函数模块，对象式中引入了对象模块，但并发式似乎没有<span class="emphasis"><em>在语法上</em></span>引入新型模块，比如C和Java中的线程其实不过是函数或方法的包装而已。”
        </p><p>
            “我猜你的意思是：在定义一个线程之前，其主函数已经模块化了，不能把功劳记在并发式编程上，对吧？”冒号笑问，“你不能孤立静止地看待每个模块，还要考虑到模块之间的相互关联和作用。相比串行式，并发式在模块之间引入了新的通讯和控制方式。也就是说，原先的一些模块的定义和划分一定是建立在线程机制的基础上的。如果失去线程的支持，它们的合理性自然会打上问号，说不定整体设计都会受到牵连。这也体现了编程范式的<span class="emphasis"><em>渗透性和全局影响力</em></span>。”
        </p><p>
            教室上空弥漫的疑云一消而散。
        </p><p>
            冒号进一步指出：“除了用户主观上的需求和硬件客观上的可能外，并发式显得格外重要的另一深层原因是：许多程序是现实世界的模拟，而我们生活的世界不折不扣是并发式的。从某种意义上看，并发式的模拟比对象式的模拟更贴近世界。”
        </p><p>
            引号再次提问：“并发式编程在设计上有什么要求？”
        </p><p>
            “并发式编程<span class="strong"><strong>以资源共享与竞争为主线</strong></span>——又是对当今世界形势的一个逼真模拟。这意味着程序设计将围绕进程的划分与调度、进程之间的通讯与同步<a class="link" href="#note2"><sup>[2]</sup></a>等等来展开。合理的并发式设计需要诸多方面的权衡考量。”冒号说着，放出一段幻灯片——
        </p><div class="informalexample"><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem"><p>软件易于重用、维护、测试</p></li><li class="listitem"><p>公平有效地利用资源，优化程序性能如增大吞吐量、减少响应时间、提高效率等</p></li><li class="listitem"><p>保障进程安全，防止<span class="term">竞态条件</span>（race condition）</p></li><li class="listitem"><p>保持进程活性，避免死锁、饥饿、活锁、资源枯竭等</p></li><li class="listitem"><p>减少锁开销、上下文切换等带来的性能损失</p></li><li class="listitem"><p>妥善处理多进程在算法、调试等方面带来的复杂性</p></li></ul></div></div><p>
            叹号蹙眉：“并发式编程好是好，就是太复杂。”
        </p><p>
            “天下没有免费的午餐。有所得，必有所失。” 冒号淡淡地说，“并发式编程当然不容易，但也并非难以掌握。最重要的是，作为一个程序员，你不得不面对它。即使你不直接用并发式编程，你依赖的代码和依赖你的代码也可能用到；即使现在没有用并发式，将来也可能用到。如果采取避而不理的鸵鸟政策，早晚会被人点中你的死穴。虽然目前不会对并发式编程作更深入的探讨，但希望能引起你们足够的重视。”
        </p><p>
            句号谈及他的感受：“相比OOP在语言上得到的支持，并发式的支持力度好像很不够。”
        </p><p>
            冒号点头称是：“这是由并发式的复杂性和成熟度决定的。另外，在究竟是在语言级别上支持并发、还是交由操作系统处理的问题上，仁者见仁，智者见智。Ada、Java和C#等选择前者，在语法上对并发式编程有一定的支持；C和C++等选择后者，除了关键字volatile外，主要靠库函数支持。专门为并发式而设计的语言大多仅限于学术研究而非商业应用，Erlang语言<a class="link" href="#note3"><sup>[3]</sup></a>是少数的例外。顺带提一下，前面提到的声明式语言具有无副作用的特性，尤其适合于并发式编程。”
        </p><p>
            问号提了一个听似奇怪的问题：“并发式与前面提到的对象式有无共通之处？”
        </p><p>
            “并发式与对象式虽是互相正交的两种范式，倒真有些相通呢。”冒号回答，“它们均与三大核心范式正交，并且越来越广泛地向它们渗透着；均为传统编程的一种推广——并发式进程的个数为一时即为传统的串行式编程，对象的方法个数为为零即为传统的数据类型；均将整个程序系统分解为<span class="emphasis"><em>若干独立的子系统</em></span>，不同的是一个以<span class="emphasis"><em>任务</em></span>为单位，一个以<span class="emphasis"><em>对象</em></span>为单位；子系统之间均能交流与合作，不同的是一个以<span class="emphasis"><em>竞争</em></span>为主题，一个以<span class="emphasis"><em>服务</em></span>为主题。如果将程序系统视作公司，那么并发式系统是<span class="emphasis"><em>产品型</em></span>公司，每个进程是一名工人，其职责是执行<span class="emphasis"><em>单一</em></span>任务；对象式系统是<span class="emphasis"><em>服务型</em></span>公司，每个对象是一名服务员，其职责是提供<span class="emphasis"><em>系列</em></span>服务。由此可见，一名优秀的程序设计师也应该是一名优秀的管理者。”
        </p><p>
            句号提出：“迄今为止我们谈到了五种范式，能否对它们简单概括一下？”
        </p><p>
            “你提问的时机把握得不错嘛。”冒号开始如数家珍，“为便于记忆，我们不妨用‘一二三四五’来概括编程范式之最：最传统的<span class="emphasis"><em>一个</em></span>是命令式；最基本的<span class="emphasis"><em>两个</em></span>是命令式和声明式；最核心的<span class="emphasis"><em>三个是</em></span>命令式、函数式和逻辑式；最主要的<span class="emphasis"><em>四个</em></span>是命令式、函数式、逻辑式和对象式；最重要的<span class="emphasis"><em>五个</em></span>是命令式、函数式、逻辑式、对象式和并发式。最后，我们来对比一下五大范式。”
        </p><p>
            少顷，黑板上出现几行排比句——
        </p><div class="informalexample"><p>过程式：以过程为模块的君主体系，模块之间互相授命与听命</p><p>函数式：以函数为模块的数学体系，模块之间互相替换与合成</p><p>逻辑式：以断言为模块的逻辑体系，模块之间互相归纳与演绎</p><p>对象式：以对象为模块的民主体系，模块之间互相交流与服务</p><p>并发式：以进程为模块的生产体系，模块之间互相竞争与合作</p></div><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>
                    并发式编程中以进程（process）或线程（thread）为基本单位。尽管它们有很大的差异，但为简明起见，这里不加区分地交替使用两个术语。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    <span class="term">同步</span>（synchronization）只在采用<span class="term">共享内存</span>（shared memory）的并发模型中需要，在采用<span class="term">消息传递</span>（message passing）的并发模型中并不需要。本文主要以前一种并发模型为讨论对象，它也是大多数语言或库（包括C、C++、Java、C#等）所支持的模型。
                </p></li><li class="listitem"><p><a name="note3"></a>
                    Erlang是由爱立信开发的一种通用编程语言，支持函数式和并发式，采用消息传递的并发模型。
                </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"><div class="table"><a name="id640460"></a><p class="title">五大重要范式对比：</p><div class="table-contents"><table summary="五大重要范式对比" border="1"><colgroup><col><col><col><col></colgroup><thead><tr><th>范式</th><th>体系</th><th>模块</th><th>模块关系</th></tr></thead><tbody><tr><td>过程式</td><td>君主体系</td><td>过程</td><td>授命与听命</td></tr><tr><td>函数式</td><td>数学体系</td><td>函数</td><td>替换与合成</td></tr><tr><td>逻辑式</td><td>逻辑体系</td><td>断言</td><td>归纳与演绎</td></tr><tr><td>对象式</td><td>民主体系</td><td>对象</td><td>交流与服务</td></tr><tr><td>并发式</td><td>生产体系</td><td>进程</td><td>竞争与合作</td></tr></tbody></table></div></div><br class="table-break"></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>
                    Abraham Silberschatz，Peter Galvin．Operating System Concepts，5ed.．Reading, MA：Addison-Wesley，1998．155-235
                </p></li></ol></div></div></div>

<!-- below is edited manually -->
<strong><span style="font-family: 宋体">课后思考</span></strong>
<ul style="margin-top: 0cm; list-style-type: none">
    <li>02-01 在你所掌握的语言当中，有哪些是命令式的？哪些是声明式的？其中，命令式语言里有哪些声明式的特征？声明式的语言里有哪些命令式的特征？它们各自有哪些优点和缺点？</li>
    <li>02-02 你编写的程序符合结构化编程的原则吗？</li>
    <li>02-03 你接触过函数式语言和逻辑式语言吗？如果没有，试着阅读相关的入门书籍，体会它们与过程式迥然不同的风味。（函数式语言推荐Haskell和 Scheme，逻辑式语言推荐Prolog）</li>
    <li>02-04 相比纯过程式的编程语言（如C语言），你认为OOP语言主要有哪些优势？又有哪些劣势？</li>
    <li>02-05 你认为在并发式编程设计中，最重要的是什么？最困难的是什么？</li>
</ul><a class="a2a_dd addtoany_share_save" href="http://www.addtoany.com/share_save?linkurl=http%3A%2F%2Fblog.zhenghui.org%2F2009%2F09%2F07%2Fcolon-class-2_4%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A72.4%EF%BC%9A%E5%B9%B6%E5%8F%91%E8%8C%83%E5%BC%8F">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/09/07/colon-class-2_4/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>冒号课堂§2.2：声明范式</title>
		<link>http://blog.zhenghui.org/2009/09/04/colon-class-2_2/</link>
		<comments>http://blog.zhenghui.org/2009/09/04/colon-class-2_2/#comments</comments>
		<pubDate>Fri, 04 Sep 2009 15:48:57 +0000</pubDate>
		<dc:creator>hui</dc:creator>
				<category><![CDATA[冒号课堂]]></category>
		<category><![CDATA[函数式编程]]></category>
		<category><![CDATA[命令式编程]]></category>
		<category><![CDATA[声明式编程]]></category>
		<category><![CDATA[编程范式]]></category>
		<category><![CDATA[逻辑式编程]]></category>

		<guid isPermaLink="false">http://blog.zhenghui.org/?p=341</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: 宋体">第二课 重要范式(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="id671200"></a>2.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">Archimedes</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>什么是函数式和逻辑式？</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="explaination"></a>：讲解</h2></div></div></div><p>
            冒号迅速转移了话题：“下面我们来谈谈与命令式编程相对的<span class="term">声明式编程</span>（declarative programming）。顾名思义，声明式编程由若干<span class="term">规范</span>（specification）的声明组成的，即一系列陈述句：‘已知这，求解那’，强调‘做什么’而非‘怎么做’。声明式编程是人脑思维方式的抽象，即利用数理逻辑或既定规范对已知条件进行推理或运算。”
        </p><p>
            问号询问：“声明式产生的背景是什么呢？”
        </p><p>
            “声明式编程发轫于人工智能的研究，主要包括<span class="term">函数式编程</span>（functional programming，简称FP）和<span class="term">逻辑式编程</span>（logic programming，简称LP）。其中，函数式编程将计算描述为数学函数的求值，而逻辑式编程通过提供一系列事实和规则来推导或论证结论。其实支持它们的语言出现得并不比命令式的晚多少——最早的函数式语言Lisp（<span class="strong"><strong>LIS</strong></span>t <span class="strong"><strong>P</strong></span>rocessor）已有半个世纪的历史，最早之一的逻辑式语言Prolog（<span class="strong"><strong>PRO</strong></span>gramming in <span class="strong"><strong>LOG</strong></span>ic）也与C同龄。只是由于大多数更多地用于学术研究而非商业应用，颇有些‘养在深闺人未识’啊。”冒号有些惋惜，“起源的不同决定了这两大类范式代表着迥然不同的编程理念和风格：<span class="strong"><strong>命令式编程是行动导向（Action-Oriented）的</strong></span>，因而<span class="strong"><strong>算法是显性而目标是隐性的</strong></span>；<span class="strong"><strong>声明式编程是目标驱动（Goal-Driven）的</strong></span>，因而<span class="strong"><strong>目标是显性而算法是隐性的</strong></span>。为便于说明，我们分别用三种代表性的语言来实现阶乘（factorial）运算。”
        </p><p>
            冒号在黑板上打出投影——
        </p><div class="example"><a name="id671308"></a><p class="title"><b>阶乘的三种实现：</b></p><div class="example-contents"><p><span class="emphasis"><em>C（命令式）</em></span>——</p><pre class="programlisting">
    int factorial(int n) 
    {
        int f = 1;
        for (; n &gt; 0; --n) f *= n;
        return f;
    }</pre><p><span class="emphasis"><em>Lisp（函数式）</em></span>——</p><pre class="programlisting">
    (defun factorial(n)
      (if (= n 0)
        1                               //  若n等于0，则n!等于1
        (* n (factorial(- n 1)))))      //  否则n!等于n* (n-1)</pre><p><span class="emphasis"><em>Prolog（逻辑式）</em></span>——</p><pre class="programlisting">
    // 0! 等于1
    factorial(0,1).
    // 若M等于N-1且 M!等于Fm且F等于N*Fm，则N! 等于F
    factorial(N,F) :-   M is N-1, factorial(M,Fm), F is N * Fm. </pre></div></div><br class="example-break"><p>
            冒号提问：“撇开语法细节，大家说说以上三段代码区别在哪里？”
        </p><p>
            句号沉思片刻，答道：“C明确给出了阶乘的迭代算法，而Lisp仅描述了阶乘的递归定义，Prolog则陈述了两个关于阶乘的断言。”
        </p><p>
            冒号很满意：“一针见血！第二个问题：你们更习惯哪一种思维方式？”
        </p><p>
            逗号不加思索：“当然是第一种！”
        </p><p>
            冒号微笑着说：“这证明你至少是受过一定训练的程序员。大家回想一下，当你们初学编程时，是否习惯这种思维方式？”
        </p><p>
            叹号沉吟道：“好像不太习惯<code class="code">i = i + 1</code>之类的语句。”
        </p><p> “对！”冒号的一嗓子吓了众人一跳，“我们最早接触的变量是代数方程中的x、y、z等，本质上是<span class="emphasis"><em>抽象化的符号</em></span>，变量值是该符号在给定约束条件下的允许值。而命令式编程中的变量本质上是<span class="emphasis"><em>抽象化的内存</em></span>，变量值是该内存的储存内容。通俗地说，前者好比<span class="emphasis"><em>姓名</em></span>，所指之人是固定的；后者好比<span class="emphasis"><em>住址</em></span>，所住之人是变化的。此外，等号在代数中是一种约束，而在许多命令式语言中则表示赋值。因此<code class="code">i = i + 1</code>可以在命令式编程中出现，但绝不可能在数学推理中出现<a class="link" href="#note1"><sup>[1]</sup></a>——除非在反证法中。”
        </p><p>
            叹号又道：“现在回头再看代数，反倒有些不习惯了。”
        </p><p>
            “这就是思维的<span class="emphasis"><em>定势效应</em></span>。”冒号感慨道，“声明式编程让我们重回数学思维：函数式编程类似代数中的表达式变换和计算，逻辑式编程则类似数理逻辑推理。其中的变量也如数学中的一样，是抽象符号而非内存地址，因此没有赋值运算，不会产生变量被改写的<span class="emphasis"><em>副作用</em></span>（side-effect），也不存在内存分配和释放的问题。这既简化了代码，也减少了调试——不妨想一想，有多少bug是由于某个变量被意外改写或内存管理不慎而造成的？”
        </p><p>
            问号问道：“声明式语言与命令式语言看来是两个世界的产物，它们是否有相通之处？”
        </p><p>
            冒号答道：“首先，所有高级语言都建立于低级语言之上，最终转化为机器语言，声明式语言也不例外。其次，声明式语言与命令式语言并非泾渭分明，而是互相交叉渗透的。一些‘非纯粹’ 的声明式语言也提供变量赋值和流程控制，而一些命令式语言也在逐渐发展，通过利用其他程序或增加新的语言特征来实现声明式编程。总的说来，在命令式语言中融入声明式的元素应当是一种趋势。尤其是函数式，它的一些特征已经在许多命令式语言中得到了支持。比较而言，声明式编程重目标、轻过程，专注问题的分析和表达而不致陷入算法的迷宫，其代码也更加简洁清晰、易于修改和维护。从这种意义上说，声明式语言天然地就比命令式语言更高级。上节课提到的前三代计算机语言基本上都是命令式的，而后两代基本上都是声明式的，由此可见一斑。”
        </p><p>
            句号一拍脑袋：“命令式是模拟电脑的，声明式是模拟人脑的，人脑当然比电脑高级啦。”
        </p><p>
            冒号另有佐证：“早在命令式语言引入函数从而进化为过程式语言时，就已经开始向声明式过渡了。何以见得？比方说调用一个函数的语句：<code class="code">doWhat()</code>，这不正是在声明‘what to do’吗？至于‘how to do’，即函数的具体实现细节，则不劳调用者费心。这种声明式的风格，提高了语言的抽象能力和开发效率，促成了语言的升级。”
        </p><p>
            逗号仍然有些疑惑：“既然声明式编程有这么多好处，为什么命令式语言不仅占大多数，而且流行程度也不减呢？”
        </p><p>
             冒号回答：“编程语言的流行程度与其擅长的领域关系密切。声明式语言——尤其是函数式语言和逻辑式语言——擅长基于数理逻辑的应用，如人工智能、符号处理、数据库、编译器等，对基于业务逻辑的、尤其是交互式或事件驱动型的应用就不那么得心应手了。而大多数软件是面向用户的，交互性强、多为事件驱动、业务逻辑千差万别，显然命令式语言在此更有用武之地。”
        </p><p>
            大家频频颔首。
        </p><p>
            “值得指出的是，声明式编程并不仅仅局限于函数式和逻辑式。”冒号旋即补充道，“比方说，C#中的attribute、Java中的annotation和XDoclet库等采用的也是具有声明式特征的<span class="term">属性导向式编程</span>（Attribute-Oriented Programming，简称@OP）。再比如，Prograph<a class="link" href="#note2"><sup>[2]</sup></a> 、SISAL<a class="link" href="#note3"><sup>[3]</sup></a>等<span class="term">数据流语言</span>（dataflow language）采用的<span class="term">数据流式编程</span>（Dataflow Programming）与函数式编程有不少共同点，同样属于声明式的范畴。还有一些语言如Oz、CHIP等支持与逻辑式编程相交的<span class="term">约束式编程</span>（Constraint Programming）<a class="link" href="#note4"><sup>[4]</sup></a>。此外，大家熟悉的数据库语言SQL，<span class="term">样式语言</span>XSLT、CSS，<span class="term">标记语言</span>HTML、XML、SVG，<span class="term">规范语言</span>IDL（Interface Description Language）等等都是声明式的。算上它们，声明式语言所占的比例也是非常可观的。此前之所以没有提及，一方面，不少声明式语言采用的范式并没有专门的名称；另一方面，这些语言大多是<span class="term">领域特定语言</span>，并且不少并非<span class="term">图灵完备的</span>，有的连运算都没有。毕竟，目前我们的重点还是放在<span class="term">通用编程语言</span>上。”
        </p><p>
            问号突然想到了什么，指着投影问：“这里用Lisp实现阶乘的方法不也可以用在C上吗？”
        </p><p>
            冒号点点头，写下一段代码——
        </p><pre class="programlisting">
int factorial(int n) 
{
    return n == 0 ? 1 : n * factorial(n - 1);
}</pre><p>
            “这是C的递归实现。”冒号娓娓道来，“除了细微的语法差别外，二者的确很相似，这说明用命令式语言也可以讲出声明式的味道。实际上，命令式语言提倡迭代而不鼓励递归，早期的Fortran 甚至都不支持递归。一则迭代比递归更符合命令式的思维模式，因为前者贴近机器语言而后者贴近数学语言；二则除<span class="term">尾递归</span>（tail recursion）<a class="link" href="#note5"><sup>[5]</sup></a>外，一般递归比迭代的开销（overhead）大。相反，声明式语言提倡递归而不支持迭代<a class="link" href="#note6"><sup>[6]</sup></a>。就语法而言，它不允许迭代中的循环变量；就视角而言，迭代着眼微观过程而递归着眼宏观规律。”
        </p><p>
            叹号轻叹：“原来貌似普通的迭代和递归有那么多道道！”
        </p><p>
            “任何语言都难脱命令式或声明式的窠臼。事实上，凡是非命令式的编程都可归为声明式编程。因此，命令式、函数式和逻辑式是最核心的三种范式。为清楚起见，我们用一幅图来表示它们之间的关系。”说罢，冒号换了一个幻灯片——
        </p><div class="figure"><a name="id671551"></a><p class="title"><b>图2-4. 编程范式的简单分类</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://blog.zhenghui.org/img/colonclass/figure2-4.jpg" alt="编程范式的简单分类"></div></div></div><br class="figure-break"><p>
            末了，冒号归纳道：“归根结底，<span class="strong"><strong>编程是寻求一种机制，将指定的输入转化为指定的输出</strong></span>。三种范式对此提供了截然不同的解决方案：命令式把程序看作一个<span class="emphasis"><em>自动机</em></span>，输入是<span class="emphasis"><em>初始状态</em></span>，输出是<span class="emphasis"><em>最终状态</em></span>，编程就是设计一系列指令，通过自动机执行以完成状态转变；函数式把程序看作一个<span class="emphasis"><em>数学函数</em></span>，输入是<span class="emphasis"><em>自变量</em></span>，输出是<span class="emphasis"><em>因变量</em></span>，编程就是设计一系列函数，通过表达式变换以完成计算；逻辑式把程序看作一个<span class="emphasis"><em>逻辑证明</em></span>，输入是<span class="emphasis"><em>题设</em></span>，输出是<span class="emphasis"><em>结论</em></span>，编程就是设计一系列命题，通过逻辑推理以完成证明。绘成表格如下（如表2-1所示）——” 
        </p><div class="table"><a name="id671610"></a><p class="title"><b>表2-1. 三种核心编程范式的比较</b></p><div class="table-contents"><table summary="三种核心编程范式的比较" border="1"><colgroup><col><col><col><col><col><col></colgroup><thead><tr><th>范式</th><th>程序</th><th>输入</th><th>输出</th><th>程序设计</th><th>程序运行</th></tr></thead><tbody><tr><td>命令式</td><td>自动机</td><td>初始状态</td><td>最终状态</td><td>设计指令</td><td>命令执行</td></tr><tr><td>函数式</td><td>数学函数</td><td>自变量</td><td>因变量</td><td>设计函数</td><td>表达式变换</td></tr><tr><td>逻辑式</td><td>逻辑证明</td><td>题设</td><td>结论</td><td>设计命题</td><td>逻辑推理</td></tr></tbody></table></div></div><br class="table-break"><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>
                    数学中应该用类似<span class="mathphrase">i<sub>n+1</sub> = i<sub>n</sub> + 1</span>的表示法。
                </p></li><li class="listitem"><p><a name="note2"></a>
                    Prograph即<span class="strong"><strong>Pro</strong></span>gramming in <span class="strong"><strong>Graph</strong></span>ics，是一种可视化的、对象导向（OO）的数据流语言，它用图表（diagram）来取代文本编码。
                </p></li><li class="listitem"><p><a name="note3"></a>
                    SISAL即<span class="strong"><strong>S</strong></span>treams and <span class="strong"><strong>I</strong></span>teration in a <span class="strong"><strong>S</strong></span>ingle <span class="strong"><strong>A</strong></span>ssignment <span class="strong"><strong>L</strong></span>anguage，是一种函数式数据流语言，擅长并行科学计算。
                </p></li><li class="listitem"><p><a name="note4"></a>
                    约束式编程通过数据之间的约束关系来进行运算。它可以借助逻辑推理机制或其他数学和算法技巧来实现。有一种观点认为：用函数式、逻辑式或约束式三者之一的语言来编写程序，即是声明式编程。这种以编程语言反过来定义编程范式的说法值得商榷，但从中可看出约束式编程的代表性。
                </p></li><li class="listitem"><p><a name="note5"></a>
                    尾递归是一种特殊的递归，其递归调用出现在函数的最后一步运算（尾部）。这类递归很容易通过手工或编译器转化为迭代形式，以优化性能。
                </p></li><li class="listitem"><p><a name="note6"></a>
                    有些声明式语言（例如Lisp的一个变种Scheme）虽然支持迭代，但一般是用尾递归来实现的。从本质上说，这只是一种语法上的甜头（syntactic sugar）。它与普通迭代的区别在于：前者的循环变量是重新绑定（rebind）的，而后者的循环变量是重复赋值（reassign）的。
                </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><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 class="simpara"> 三种核心编程范式采用如下不同的机制——</p><p class="simpara">命令式：自动机机制，通过设计指令完成从初始态到最终态的转变。</p><p class="simpara">函数式：数学变换机制，通过设计函数完成从自变量到因变量的计算。</p><p class="simpara">逻辑式：逻辑证明机制，通过逻辑推理完成从题设到结论的证明。</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>
                    Elena Bolshakova．PROGRAMMING PARADIGMS IN COMPUTER SCIENCE EDUCATION．International Journal &#8220;Information Theories &amp; Applications&#8221;，2005，Vol.12：285-290
                </p></li><li class="listitem"><p>
                    Ravi Sethi．Programming Languages: Concepts &amp; Constructs(英文版第2版)．北京：机械工业出版社，2002．301-340,423-470
                </p></li><li class="listitem"><p>
                    Wikipedia．Declarative programming．<a class="link" href="http://en.wikipedia.org/wiki/Declarative_programming" target="_top">http://en.wikipedia.org/wiki/Declarative_programming</a>
                </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%2F04%2Fcolon-class-2_2%2F&amp;linkname=%E5%86%92%E5%8F%B7%E8%AF%BE%E5%A0%82%C2%A72.2%EF%BC%9A%E5%A3%B0%E6%98%8E%E8%8C%83%E5%BC%8F">分享/保存</a>]]></content:encoded>
			<wfw:commentRss>http://blog.zhenghui.org/2009/09/04/colon-class-2_2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
