命令式語言和聲明式語言對比——JavaScript實現快速排序爲例

什麼是命令式編程 (Imperative Programming)?javascript

命令機器如何作事情,強調細節實現html

java、c、c++等都屬此類。java

「這些語言的特徵在於,寫出的代碼除了表現出「什麼(What)」是你想作的事情以外,更多的代碼則表現出實現的細節,也就是「如何(How)」完成工做。這部分代碼有時候多到掩蓋了咱們原來問題的解決方案。好比,你會在代碼裏寫for循環,if語句,a等於b,i加一等等,這體現出機器如何處理數據。」c++

什麼是聲明式編程(Declarative Programming)?程序員

聲明式編程告訴機器作什麼,至於怎麼作到的,你能夠不用管。算法

表明語言:prolog
特色:你只需向它提供一些事實(fact)和推論(inference),讓它爲你判斷。編程

聲明式編程包含了函數式編程和邏輯編程。好比lisp haskell prolog,也包含下面示例代碼中使用的js。js是一種多範式語言,既能夠用命令式也能夠用函數式風格編寫代碼。數組

聲明式語言來描述算法很是合適函數式編程

經過上面的簡單對比得知,聲明式編程的抽象程度更高,程序員沒必要糾纏與實現的細節,因此用來描述算法最合適了。函數

以快速排序爲例

A、快速排序以聲明式編程風格實現的例子

function quicksort(list){
  if(list.length === 0){
    return [];
  }else{
    var n = first(list)            //1
      , lt = listlet(rest(list),n) //2
      , gt = listgt(rest(list),n)  //3
    // 遞歸 & 合併
    return [].concat(quicksort(lt) //4
                    ,n
                    ,quicksort(gt))//5
  }
}
console.log(quicksort([3,9,2,1,5,4]));
// [ 1, 2, 3, 4, 5, 9 ]

代碼自己比較短小,它描述的算法是這樣的

1.取出一個元素做爲參考元素n 。見標註1

2.將這個元素以外的剩餘元素分爲兩部分,小於等於n的元素lt,大於n的元素gt。見標註二、3

3.將lt放n前面,gt放n後面,即按升序排列。可是在這樣排列以前,要遞歸的執行上面兩步,直到碰見空數組。見標註四、5

quicksort依賴下面的工具函數。前兩個工具函數自己也是聲明式風格編寫的。

function listgt(list,n){
  return list.filter(function(m){
           return m > n;
         })
}
function listlet(list,n){
  return list.filter(function(m){
           return m <= n;
         })
}
function first(list){
  return list[0];
}
function rest(list){
  return list.slice(1);
}

以listgt爲例,聲明式風格的實現爲:

function listgt(list,n){
  return list.filter(function(m){
           return m > n;
         })
}

對應的命令式風格的實現爲:

function listgt(list,n){
  var ret = [];
  for(var i=0;i<list.length;i++){
    if(list[i]>n){
      ret.push(list[i]);
    }
  }
  return ret;
}

能夠看出,上面的代碼體現了「命令機器如何作事情,強調細節實現」,包含了一個for循環, 聲明瞭額外的變量i,ret,調用了ret.push方法。感受有不少噪音在裏面。  

而前一個listgt實現爲一段聲明,過濾(filter)這個數組,過濾規則爲:這個數組元素m要大於n。過濾就是一種聲明式的描述,「告訴機器作什麼」。過濾功能可能語言自己已經提供給你了,你只須要告訴機器過濾規則就好了。 

B、快速排序以命令式風格實現的例子

function swap(items, firstIndex, secondIndex){
    var temp = items[firstIndex];
    items[firstIndex] = items[secondIndex];
    items[secondIndex] = temp;
}
function partition(items, left, right) {
    var pivot   = items[Math.floor((right + left) / 2)],
        i       = left,
        j       = right;
    while (i <= j) {
        while (items[i] < pivot) {
            i++;
        }
        while (items[j] > pivot) {
            j--;
        }
        if (i <= j) {
            swap(items, i, j);
            i++;
            j--;
        }
    }
    return i;
}
function quickSort(items, left, right) {
  var index;
  if (items.length > 1) {
    left = typeof left != "number" ? 0 : left;
    right = typeof right != "number" ? items.length - 1 : right;
    index = partition(items, left, right);
    if (left < index - 1) {
      quickSort(items, left, index - 1);
    }
    if (index < right) {
      quickSort(items, index, right);
    }
  }
  return items;
}
console.log(quickSort([4, 2, 6, 5, 3, 9]));

left和right參數的引入是由於實現算法的細節須要——「命令機器如何作事情,強調細節實現」,用於partition函數劃分數組。

partition函數也有很是多的實現細節。相較而言,不太好閱讀。

 

參考:

http://blog.zhaojie.me/2010/04/trends-and-future-directions-in-programming-languages-by-anders-2-declarative-programming-and-dsl.html

相關文章
相關標籤/搜索