程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 冒號和他的學生們(連載6)——基本范式

冒號和他的學生們(連載6)——基本范式

編輯:關於JAVA

基本范式

人生最偉大的目標是行動   ——《洛克菲勒的忠告》

第二課伊始,冒號開門見山:“首先介紹的是最基本的兩種編程范式:命令式和聲明式,其中命令式又稱過程式。通俗點說,命令式編程由命令序列組成,即一系列祈使句:‘先做這,再做那’,強調‘怎麼做’;聲明式編程由相關表達式組成,即一系列陳述句:‘已知這,求解那’,強調‘做什麼’。學術點說,命令式編程是電腦(von Neumann機)運行機制的抽象,即有序地從內存中獲取指令和數據然後去執行;聲明式編程是人腦思維方式的抽象,即利用數理邏輯或既定規范(specification)對已知條件進行推理運算。”

引號嘟囔著:“大多數語言好像都是命令式的。”

冒號接口道:“語言的演化是漸進的,大多數語言追根溯源是匯編語言的升級,而作為與機器語言一一對應的匯編語言自然是命令式的,因而這種范式最為傳統和普及。聲明式語言則發轫於人工智能的研究,主要包括函數式語言和邏輯式語言。其實它們的出現並不比命令式的晚多少——最早的函數式語言Lisp(LISt Processor)已有半個世紀的歷史,最早之一的邏輯式語言Prolog(PROgramming in LOGic)也與C同齡。只是由於大多數更多地用於學術研究而非商業應用,頗有些‘養在深閨人未識’。起源的不同決定了這兩大類范式代表著迥然不同的編程理念和風格:命令式編程是行動導向(Action-Oriented)的,因而算法是顯性而目標是隱性的;聲明式編程是目標驅動(Goal-Driven)的,因而目標是顯性而算法是隱性的。為便於說明,下面分別用三種代表性的語言來實現階乘(factorial)運算。”

冒號在黑板上打出投影——

C(命令式):

int factorial(int n)
{
   int f = 1;
   for (; n > 0; --n) f *= n;
   return f;
}

Lisp(函數式):

(defun factorial(n)
(if (= n 0) 1             // 若n等於0,則n!等於 1
   (* n (factorial(- n 1)))))   // 否則n!等於n* (n-1)

Prolog(邏輯式):

// 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.

冒號提問:“撇開語法細節,大家說說以上三段代碼區別在哪裡?”

句號沉思片刻,答道:“C明確給出了階乘的迭代算法,而Lisp僅描述了階乘的遞歸定義,Prolog則陳述了兩個關於階乘的斷言。”

冒號很滿意:“一語中的!第二個問題:你們更習慣哪一種思維方式?”

逗號不加思索:“當然是第一種!”

冒號微笑著說:“這證明你至少是受過一定訓練的程序員。大家回想一下,當你們初學編程時,是否習慣這種思維方式?”

歎號沉吟道:“好像不太習慣i = i + 1之類的語句。”

“對!”冒號的一嗓子嚇了眾人一跳,“我們最早接觸的變量是代數方程中的x、y、z等,本質上是抽象化的符號,變量值是該符號在給定約束條件下的允許值。而命令式編程中的變量本質上是抽象化的內存,變量值是該內存的儲存內容。通俗地說,前者好比姓名,所指之人是固定的;後者好比住址,所住之人是變化的。此外,等號在代數中是一種約束,而在許多命令式語言中則表示賦值。因此i = i + 1可以在命令式編程中出現,但絕不可能在數學推理中出現——除非在反證法中。”

歎號又道:“現在回頭再看代數,反倒有些不習慣了。”

“這就是思維的定勢效應。”冒號感慨道,“聲明式編程讓我們重回數學思維,其中函數式編程類似代數中的表達式變換和計算,邏輯式編程則類似數理邏輯推理。其中的變量也如數學中的一樣,是抽象符號而非內存地址,因此沒有賦值運算,不會產生變量被改寫的副作用,也不存在內存分配和釋放的問題。這既簡化了代碼,也減少了調試——不妨想一想,有多少bug是由於某個變量被意外改寫或內存管理不慎而造成的?”

問號問道:“聲明式語言與命令式語言看來是兩個世界的產物,它們有何相通之處?”

冒號答道:“首先,所有高級語言都建立於低級語言之上,最終轉化為機器語言,聲明式語言也不例外。其次,聲明式語言與命令式語言並非泾渭分明,而是互相交叉滲透的。一些‘非純粹’ 的聲明式語言也提供變量賦值和流程控制,而一些命令式語言也在逐漸發展,通過調用其他軟件或增加新的語言特征來實現聲明式編程。總的說來,在命令式語言中融入聲明式的元素應當是一種趨勢,尤其是函數式,它的一些特征已經在許多命令式語言中得到了支持。比較而言,聲明式編程重目標、輕過程,專注問題的分析和表達而不致陷入算法的迷宮,其代碼也更加簡潔清晰、易於修改和維護。”

逗號仍然有些疑惑:“既然聲明式編程有這麼多好處,為什麼命令式語言不僅占大多數,而且流行程度也不減呢?”

冒號回答:“編程語言的流行程度與其擅長的領域關系密切。聲明式語言擅長基於數理邏輯的應用,如人工智能、符號處理、數據庫、編譯器等,對基於業務邏輯的、尤其是交互式或事件驅動型的應用就不那麼得心應手了。而大多數軟件是面向用戶的,交互性強、多為事件驅動、業務邏輯千差萬別,顯然命令式語言在此更有用武之地。”

大家頻頻颔首。

問號突然想到了什麼,指著投影問:“這裡用Lisp實現階乘的方法不也可以用在C上嗎?”

冒號點點頭,寫下一段代碼——

int factorial(int n)
{
   return n == 0 ? 1 : n * factorial(n - 1);
}

“這是C的遞歸實現。”冒號娓娓道來,“除了細微的語法差別外,二者的確很相似,這說明用命令式語言也可以講出聲明式的味道。實際上,命令式語言提倡迭代而不鼓勵遞歸,早期的Fortran 甚至都不支持遞歸。一則迭代比遞歸更符合命令式的思維模式,因為前者貼近機器語言而後者貼近數學語言;二則除尾遞歸(tail recursion)外,一般遞歸比迭代的開銷(overhead)大。相反,聲明式語言提倡遞歸而不支持迭代。就語法而言,它不允許迭代中的循環變量;就視角而言,迭代著眼微觀過程而遞歸著眼宏觀規律。”

歎號輕歎:“原來貌似普通的迭代和遞歸有那麼多道道!”

“任何語言都難脫命令式或聲明式的窠臼,因此上述三種范式最為基本。”冒號歸納道,“歸根結底,編程是尋求一種機制,將指定的輸入轉化為指定的輸出。三種范式對此提供了迥然不同的解決方案:命令式把程序看作一個自動機,輸入是初始狀態,輸出是最終狀態,編程就是設計一系列指令,通過自動機執行以完成狀態轉變;函數式把程序看作一個數學函數,輸入是自變量,輸出是因變量,編程就是設計一系列函數,通過表達式變換以完成計算;邏輯式把程序看作一個邏輯證明,輸入是題設,輸出是結論,編程就是設計一系列命題,通過邏輯推理以完成證明。繪成表格如下——”

范式 程序 輸入 輸出 程序設計 程序運行 命令式 自動機 初始狀態 最終狀態 設計指令 命令執行 函數式 數學函數 自變量 因變量 設計函數 表達式變換 邏輯式 邏輯證明 題設 結論 設計命題 邏輯推理

冒號見眾人微顯難色,寬慰道:“這部分理論性稍微強了些,對函數式和邏輯式也僅作了描述性說明,並未深入。不解之處,大可不必介懷,撒下的種子總有一天會萌動。先休息片刻,不要走開,廣告之後更精彩。”

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved