一個人的蜜糖,可能是另一個人的毒藥
Lucretius
正如第二天所說的,宣告式典範(declarative paradigm)與命令式典範(imperative paradigm)並非絕對的二元對立,而是依照情況而定的。
截至目前為止,我們已經看過許多例子,而這些例子皆告訴我們一些不錯的原則,以利達成FP的風格(但如果你反對FP,這些就可能是爛規則!)。
不可否認的是,事實上很多時候,我們開發時寫的程式要比這幾天的例子更為複雜。這時候,FP的風格(例如:避免使用let關鍵字,不重新賦值變量,不改變loop中的數據狀態等等)非常可能產生繁瑣、冗長、難以閱讀的code。
我們不希望你編寫程序時,單單只是考量某種風格本身(例如:FP本身)。因為這很可能讓情況變得更糟。
事實上,我們有時還是需要命令式的寫法。在這個時候,一個好的做法是,我們可以使用函數包住我們的邏輯(abstraction),然後在需要它的地方,以declarative的方式使用這個函數。
即使這個函數沒有要被重複使用,但將邏輯打包並讓內部各個變數狀態影響範圍只在函數內部仍是一個不錯的做法。如此一來,這個函數將不會造成任何函數外部的副作用,也就是該函數是pure的。
更精確的說,我們可以在函數內部(1)對變數重新賦值、(2)在迴圈外改變資料、(3)改變array的資料。但我們仍避免用class、方法、實例、等等OOP的屬性。換句話說,還函數內部的code是procedural(亦屬imperative code)並在最後返回結果。
我們直接看一個例子,會最為直接:
問題描述 給定一個二維平面點集合,計算其第一二象限點之質心座標(假定各個點的質量一致),確認質心是否二軸值皆大於給定座標。
針對此問題我們先建構一個functionisCentroidGreater
。
function isCentroidGreater({ vertices, givenPoint }) {
let centroidX = 0;
let centroidY = 0;
let sumX = 0;
let sumY = 0;
let eligPoints = [];
for (let i = 0; i < vertices.length; i++) {
// y value is greater than 0
if (vertices[i][1] > 0) {
eligPoints.push(vertices[i]);
sumX += vertices[i][0];
sumY += vertices[i][1];
}
// calculate centroids
if (i === (vertices.length - 1)) {
centroidX = sumX / eligPoints.length;
centroidY = sumY / eligPoints.length;
}
}
return (centroidX > givenPoint[0]) && (centroidY > givenPoint[1])
}
事實上,你當然可以針對上述的code進行FP重構,但必要性其實不大,因為:
isCentroidGreater
本身就是pure的了。接著,我們可以用declarative的方式使用這個函數。
const answer = isCentroidGreater({
vertices: [[-1, 1],
[-1, -1],
[5, 5],
[3, 3]],
givenPoint: [1, 1]
});
console.log(answer) // true
總結來說,盡可能地使用Functional Programming風格。針對命令式典範的coding風格需要我們的批判性思維,在可能和有意義的情況下努力避免。