使用 JavaScript 進行函數語言程式設計 (一)

oschina發表於2015-10-01

本文是函數語言程式設計系列的第一篇文章。這裡我會簡要介紹一下程式設計正規化,然後會直接介紹使用 Javascript 進行函數語言程式設計的概念,因為 JavsScript 是最被認可的函式式程式語言之一。我們鼓勵讀者通過參考資料部分進一步瞭解這一迷人的概念。

程式設計正規化

程式設計正規化是一個由思考問題以及實現問題願景的工具組成的框架。很多現代語言都是聚正規化(或者說多重正規化): 他們支援很多不同的程式設計正規化,比如物件導向,元程式設計,泛函,程式導向,等等。

使用 JavaScript 進行函數語言程式設計 (一)

函數語言程式設計正規化

函數語言程式設計就像一輛氫燃料驅動的汽車——先進的未來派,但是還沒有被廣泛推廣。與指令式程式設計相反,他由一系列語句組成,這些語句用於更新執行時的全域性狀態。函數語言程式設計將計算轉化作表示式求值。這些表示式全由純數學函式組成,這些數學函式都是一流的(可以被當做一般值來運用和處理),並且沒有副作用。

使用 JavaScript 進行函數語言程式設計 (一)

函數語言程式設計很重視以下值:

函式是一等要務

我們應該將函式與程式語言中的其他類物件同樣對待。換句話說,您可以將函式儲存在變數裡,動態建立函式,以及將函式返回或者將函式傳遞給其他函式。下面我們來看一個例子…

使用 JavaScript 進行函數語言程式設計 (一)

一個字串可以儲存為一個變數,函式也可以,例如:

var sayHello = function() { return “Hello” };

一個字串可以儲存為物件欄位,函式也可以,例如:

var person = {message: “Hello”, sayHello: function() { return “Hello” }};

一個字串可以再用到時才建立,函式也可以,例如:

“Hello ” + (function() { return “World” })(); //=> Hello World

一個字串可以作為輸入引數傳給函式,則函式也可以:

    function hellloWorld(hello, world) { return hello + world() }

一個字串可以作為函式返回值,函式也可以,例如:

return “Hello”;
return function() { return “Hello”};

高階案例

使用 JavaScript 進行函數語言程式設計 (一)

如果函式將其他函式函式作為輸入引數或者作為返回值,則稱之為高階函式。剛才我們已經看過了一個高階函式的例子。下面,我們來看一下更復雜的情況。

例1

[1, 2, 3].forEach(alert);
// alert 彈窗顯示“1" 
// alert 彈窗顯示 "2" 
// alert 彈窗顯示 "3”

例2

function splat(fun) {
   return function(array) {
        return fun.apply(null, array);
   };
}
var addArrayElements = splat(function(x, y) { return x + y });
addArrayElements([1, 2]);
//=> 3

最愛純函式

使用 JavaScript 進行函數語言程式設計 (一)

純函式不會有其他的副作用,所謂的副作用指的是函式所產生的對函式外界狀態的修改。比如:

  • 修改某個變數
  • 修改資料結構
  • 對外界某個變數設定欄位
  • 丟擲例外或者彈出錯誤資訊

最簡單的例子就是數學函式。Math.sqrt(4) 函式總是返回2。他不會用到任何其他心寒資訊,如狀態或者設定引數。數學函式從來不會造成任何副作用。

避免修改狀態

使用 JavaScript 進行函數語言程式設計 (一)

函數語言程式設計支援純粹的函式,這樣的函式不能改變資料,因此大多用於建立不可改變的的資料。這種方式,不用修改一個已存在的資料結構,而且能高效的新建一個.

你也許想知道,如果一個純粹的函式通過改變一些本地資料而生產一個不可改變的返回值,是否是允許的?答案是可以。
在JavaScript中極少的資料型別是預設是不可改變的。String是一個不能被改變的資料型別的例子:

   var s = "HelloWorld";
    s.toUpperCase();
    //=> "HELLOWORLD"
    s;
    //=> "HelloWorld"

不可改變狀態的好處

•    避免混亂和增加程式的準確性:在複雜系統內,大多數難以理解的Bug是由於狀態通過在程式中外部客戶端程式碼修改而導致的。
•    確立“快速簡潔”的多執行緒程式設計:如果多執行緒可以修改同一個共享值,你不得不同步的獲取值。這對專家來說都是十分乏味並且易出錯的程式設計挑戰。
軟體事務記憶體和Actor模型提供了直接線上程安全方式下處理修改。

使用遞迴而非迴圈呼叫

使用 JavaScript 進行函數語言程式設計 (一)

遞迴是最有名的函數語言程式設計技術。如果您還不知道它的話,那麼可以理解為遞迴函式就是一個可以呼叫自己的函式。

替代反覆迴圈的最經典方式就是使用遞迴,即每次完成函式體操作之後,再繼續執行集合裡的下一項,直到滿足結束條件。遞迴還天生符合某些演算法實現,比如遍歷樹形結構(每個樹枝都是一顆小樹)。

在任何語言裡,遞迴都是一項重要的函數語言程式設計方式。很多函式語言甚至要求的更加嚴格:只支援遞迴遍歷,而不支援顯式的迴圈遍歷。這需要語言必須保證消除了尾端呼叫,這是 JavasSrip 不支援的。

惰性求值優於激進計算

使用 JavaScript 進行函數語言程式設計 (一)

數學定義了很多無窮集合,比如自然數(所有的正整數)。他們都是符號表示。任意特定有限的子集都在需要時求值。我們將其稱之為惰性求值(也叫做非嚴格求值,或者按需呼叫,延遲執行)。及早求值會強迫我們表示出所有無窮資料,而這顯然是不可能的。

很多語言都預設是惰性的,有些也提供了惰性資料結構以表達無窮集合,並在需要時對自己進行精確計算。

很明顯一行程式碼 result = compute() 所表達的是將 compute() 的返回結果賦值給 result。但是 result 的值究竟是多少隻有其被用到的時候才有意義。

可見策略的選擇會在很大程度上提高效能,特別是當用在鏈式處理或者陣列處理的時候。這些都是函式式程式設計師所喜愛的程式設計技術。

這就開創可很多可能性,包括併發執行,並行技術以及合成。

但是,有一個問題,JavaScrip 並不對自身進行惰性求值。話雖如此,Javascript 裡的函式庫可以有效地模擬惰性求值。

閉包的全部好處

所有的函式式語言都有閉包,然而這個語言特性經常被討論得很神祕。閉包是一個函式,這個函式有著對內部引用的所有變數的隱式繫結。換句話說,該函式對它引用的變數封閉了一個上下文。JavaScript 中的閉包是能夠訪問父級作用域的函式,即使父級函式已經呼叫完畢。

   function multiplier(factor) {
      return function(number) {
          return number * factor;
      };
   }
  var twiceOf = multiplier(2);
    console.log(twiceOf(6));
//=> 12

宣告式優於指令式程式設計

函數語言程式設計是宣告式的,就像數學運算,屬性和關係是定義好的。執行時知道怎麼計算最終結果。階乘函式的定義提供了一個例子:

factorial(n)       = 1 if n = 1

                            n * factorial(n-1) if n > 1

該定義將 factorial(n) 的值關聯到 factorial(n-1),是遞迴定義。特殊情況下的 factorial(1) 終止了遞迴。

var imperativeFactorial = function(n) {
    if(n == 1) {
        return 1
    } else {
        product = 1;
        for(i = 1; i <= n; i++) {
              product *= i;
        }
        return product;
     }
}
var declarativeFactorial = function(n) {
       if(n == 1) {
             return 1
       } else {
             return n * factorial(n - 1);
      }
  }

從它實現階乘計算來看,宣告式的階乘可能看起來像“命令式”的,但它的結構更像宣告式的。

命令式階乘使用可變值、迴圈計數器和結果來累加計算後的結果。這個方法顯式地實現了特定的演算法。不像宣告式版本,這種方法有許多可變步驟,導致它更難理解,也更難避免 bug 。

使用 JavaScript 進行函數語言程式設計 (一)

函式式JavaScript庫

有很多函式式庫:underscore.js, lodash,Fantasy Land, Functional.js, Bilby.js, fn.js, Wu.js, Lazy.js, Bacon.js, sloth.js, stream.js, Sugar, Folktale, RxJs 等等。

函式式程式設計師工具包

map(), filter(), 和 reduce()函式 構成了函式式程式設計師工具包的核心。 純高階函式成了函式式方法的主力。事實上,它們是純函式和高階函式應該仿效的典型。它們用一個函式作為輸入,返回沒有副作用的輸出。

相關文章