美思 [Raku] 程式設計教學:函數式程式設計 (Functional Programming)

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

函數式程式設計 (functional programming) 是一種程式設計的模範 (paradigm),主要見於 Lisp 和 ML 家族語言。由於函數式程式易於平行化處理,近年來許多主流語言也吸收了一些函數式程式的概念。一些大數據框架,像是 Hadoop 和 Spark 等,也用到函數式程式的概念。本文介紹以 Raku 撰寫函數式程式。

比起傳統的指令式程式,函數式程式一開始比較難理解,如果讀者覺得過於困難,可以先略過這個章節。

函式物件

函數式程式的前提在於函式為第一級物件 (first-class object),簡單地說,函式可以做為值。由於函式可以做為值,函式可以做為其他函式的參數,也可以做為回傳值。

以下是實例:

或者是將現有的函式的參考傳入:

也可以用以下較為簡略的寫法,算是一種語法糖:

閉包

閉包 (closure) 是一種帶有狀態的 (stateful) 副程式。以下是實例:

以下例子使用閉包生成費伯那西數列:

以下實例用閉包生成質數數列:

以下例子將雜湊包在閉包中,做為簡易的快取:

高階函式

高階函式 (higher-order function) 是指使用函式做為參數或用函式做為回傳值的函式。我們先前的閉包也是一種高階函式。以下是一個以副程式為參數的實例:

filter 副程式以一個陣列和一個副程式為參數,回傳符合該副程式的新陣列。

由於 Perl 6 已經將一些常見的高階函式寫好,我們不需要每次都從頭撰寫,以下是一些例子:

  • grep:傳入串列,回傳符合特定條件的串列
  • map:將串列經轉換後,回傳新串列
  • reduce:將串列縮減為純量
  • sort:依照特定條件排列後回傳新串列
  • zip:將兩個串列組合,回傳新串列

以下為實例:

高階函數間可進一步組合,如下例:

上例是將處理順序倒著寫,一開始會覺得較不易閱讀。也可以將其改為方法呼叫的方式,如下:

迭代器

Perl 6 提供 gather ... take 的語法,可用來撰寫迭代器。gather 區塊內每次碰到 take 時,都會跳出該區塊,並取出一個值,下一次迭代時,會跳回原先的位置,繼續走迴圈。

我們將先前的 filter 副程式改寫如下:

這樣子看起來和先前的寫法沒啥差異,不過,迭代器有 laziness 的特性,如以下實例:

由於 gather 區塊有 laziness 的特性,即使是無限長的串列,也是可以順利執行。如果用傳統的迴圈則會變成無窮迴圈,無法無法執行。

以下例子用 gather 重寫質數數列,可用更簡潔的語法取質數:

以下例子用 gather 區塊重寫費伯那西數列:

由此可見,take 不一定要在 gather 區塊的最尾端,可使程式寫法更靈活。

關於作者

身為資訊領域碩士,美思認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

美思喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,美思將所學寫成文章,放在這個網站上和大家分享。