程式日常-指令式與宣告式程式語言的差異
分不清指令式與宣告式程式語言?
Lack of direction, not lack of time, is the problem. We all have twenty-four hour days.
― Zig Ziglar
今天要來和各位聊聊指令式(Imperative)和宣告式(Declarative)程式語言,聽起來會有點像講古喔!但,仔細想一想,學習一門外語,最難的並不是會使用那門語言,而是語言背後的文化才是真正難以摸透的。我想,程式語言也一樣!因為它也是「語言」!我相信在了解程式語言的歷史後,對於自己在寫的程式會更有深入的理解,而不是在瞬息萬變的世界下走馬看花。
- 指令式程式語言(Imperative Programming)
- 低階指令式
- 高階指令式
- 宣告式程式語言(Declarative Programming)
- 控制流程(Control flow)
- 資料庫語言(Database Programming)
- 函式語言(Functional Programming)
- 結語
指令式程式語言(Imperative Programming)
Imperative programming is a programming paradigm that uses statements that change a program's state.
我們來仔細思考一下這一句擷取自Wiki頁面的敘述。
指令式程式語言是使用陳述式語法去改變程式狀態的一種程式範式(典範)。嗯…相當的文謅謅,可不可以用更直覺的方式來理解指令式程式語言? 沒問題,我們馬上看下面的程式碼:
指令式程式語言有分低階與高階
低階指令式程式語言(Low-Level)
廣義上來說,低階與高階是相對的概念,以現今來看,就像是C++相對於Python語言就能夠劃分成低階與高階。但狹義上來說,我們劃分低階與高階指令式,可以用組合語言與高階語言來劃分。我們看看下方的例子:
1 | ITER EQU 10 ; number of iterations |
以上的程式碼是組合語言,在我們的定義上來說就是低階的程式語言。現在前後端工程師比較少碰到組合語言,基本上都是處理硬體、韌體時比較有機會接觸到底層的程式語言,也就是低階指令式語言,大部分的情況都是為了使硬體效能最佳化,才會去撰寫組合語言。不過小弟對於組合語言也是一知半解,如果有誤,還請多多指正!
高階指令式程式語言(High-Level)
1 | #include<stdio.h> |
上面的程式要確認某一個數字是不是回文(Palindrome),舉例來說:12321。上述就是指令式程式語言的一種。可以看到說我們必須一步一步地去規劃程式的每一個步驟。首先,我們建立另外一個新的數值,利用取餘(Mod %)的方式,去反向依序填入x的值,最後在經過數值比對去核對數字是否一樣,若一樣則回傳True,相反則回傳False。這就是現今比較常看到的高階指令式程式語言。
指令式程式語言基本上的概念就像上述所說,可以劃分為低階與高階。特別注意的是,在C語言後出現的C+、C++以及Java都屬於OOP(Object-Oriented Programmin)的程式語言,但它們本質上依舊屬於指令式程式語言,只是又更上一層樓變得更為高階。其實所謂的指令式沒有想像中的難理解,我們日常在料理的過程,本質上一樣是指令式程式語言。倒不如說,指令式程式語言是從現實生活中,逐步發展出來才是正確的理解方式,有點做什麼(What to)的意味。
宣告式程式語言(Declarative Programming)
同樣地,我們來看看Wiki上面如何定義宣告式程式語言。
A style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow.
一種不需描述控制流程即可表達運算邏輯的電腦架構與元素風格。嗯…翻譯的似乎不是很到位,不過反覆思考這段話,什麼是不需描述控制流程(Control flow))的架構? 我們先來看看何謂控制流程(Control flow):
控制流程(Control flow)
控制流程可以用一種方式去理解,那就是「順序」。當程式碼的擺放位置不同時,執行過程就會不同,這就是控制流程的概念。
我們來簡單舉個JS的例子:
1 | const world = 'Hello'; |
上述程式碼就是控制流程的概念,可以看到如果改動case的順序,程式流向就會改變。我們暫且討論到這邊,因為重點還是在宣告式程式語言。
宣告式語言基本上就是由指令式語言逐步發展過來,因為它的最大區別在於避免副作用(Side Effect)。一般來說,撰寫程式免不了會出現副作用,白話一點就是改了這邊的程式碼,壞了那邊的程式碼的概念,因為副作用的其中一種可能性便是改變程式執行的順序,而導致某些程式片段無法成功讀取定義好的函數、物件、陣列等等。
資料庫語言(Database Programming)
最容易理解也不會令人困惑的非屬於資料庫語言,如:MySQL、PostgreSQL等等的資料庫語言。
同樣舉例來看:SELECT name FROM tw.male ORDER BY name
這就是宣告式程式語言的一種,因為我們並不知道SELECT以及FROM還有ORDER BY的內部程式碼,就算我們更改name以及tw.male的欄位,都不會對整體程式造成任何副作用,頂多回報格式不符合或是查無資料的訊息。
函式語言(Functional Programming)
函式語言同樣是宣告式程式語言,你正拿來寫網站的JS就是其中的一名。可是你會說寫JS可能會有副作用啊? 沒錯,確實有可能產生,但那只是符合宣告式定義的一種。因為副作用不是必要條件,而是充分條件。換句話說,沒有副作用一定是宣告式程式語言,但有副作用也有可能是宣告式程式語言。直接來看看JS的範例吧!
1 | const filterNums = (...args) => { |
這段程式碼很眼熟對吧! 因為在ES6快速入門那篇文章也有提到。我們仔細看一下回傳過後的那段程式碼return args.filter(el => el > 5);
這段程式碼讀來沒有任何問題,但是仔細想想filter是什麼? 它是一個函數,可是函數就不會有副作用嗎? 不一定,但至少在這裡它確實不會有副作用,因為不管傳入什麼樣的陣列,都不會影響filter()本身。
結語
今天概略講過指令式和宣告式程式語言的差別,因為在Udemy上課程時,講師剛好提到React是宣告式程式語言,就很好奇到底什麼是宣告式程式語言。從React是宣告式語言這一點來看,我們可以知道HTML、CSS、JS都可以歸類為宣告式語言,甚至是XML,畢竟JSX就是JS-XML形式的程式碼,具有標記語言的性質。
這一篇希望對能了解這兩個概念的讀者有點幫助,一開始查資料時,覺得內容蠻多而且還有點乏味。不過仔細去品味程式語言的整個發展,就像發現新世界一樣,一切都是環環相扣。礙於篇幅,還有許多沒有提到,如:DSL(Domain-Specific Language)就沒有提到,但是加上DSL其實就會變得不單純。
那今天就先這樣囉! 我們下回見!
組合語言程式片段來源: https://www.csie.ntu.edu.tw/~b5506058/optimize.html