iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 16
3

隨著簡單的部分結束
一天一天構思主題變成一件痛苦的事
今天打算來聊聊物件導向函數導向
雖然這個主題有點超乎我目前的能力所及
但我還是會盡我所能,將我理解的部分呈現給各位
還請大家不吝指教


物件導向(object-oriented programming)

物件導向是1970年代為了解決當時日漸龐雜的需求,為了讓程式更好維護,同時能夠重複使用而發展出來。一般會把Smalltalk視為經典,但最早在1960年代的Simula就可以發現物件導向的蹤跡。[1]

在物件導向的思維世界當中,程式裡面的每一個元素都可以拆分為獨立又互相呼叫的小單位,這與傳統的程序思維剛好相反:程式是一個函式系列的集合。物件導向一般有下列的特徵:類別、物件、繼承、封裝:

  • 類別(Class):定義了物件的抽象模板與結構,擁有屬性與方法。
  • 物件(Object):物件是類別的實體(instance),可以使用類別中定義的方法。
  • 繼承(Inheritance):類別彼此可以繼承,子類別可以使用母類別的屬性與方法。
  • 封裝(Encapsulation):每一個方法就像一個黑盒子,不需要知道具體如何實作,只要知道使用方法即可。

因為畢竟不是教科書,這邊大略介紹一下。

函數導向(functional programming)

如果物件導向是以物件為基本組成元素的程式設計觀念,函數導向程式設計(functional programming) 便是提倡以函數為基本單元來組織程式[2]。根據「Why Functional Programming Matters」一書表示函数式導向程式語言通常有下面的特性:

不包含變數給值,變數一旦給定,就不會有變化。簡言之沒有副作用。一個函數一旦執行,除了產生結果沒有其他影響。這就消除了一個主要的bug來源,也使得執行順序無關緊要。

與傳統觀念比起來,函數導向非常在意「沒用副作用」這個精神,相同的輸入一定要有相同的結果。並且利用這個特性,將一個複雜的問題,不斷的透過純函式逐層推導出複雜的運算,而不是設計一個相對複雜的執行程序[3]

函數導向的特性:

  • 避免改變狀態
  • 避免可變的資料,沒有任何副作用
  • 純函式:傳入相同的參數會得到相同的結果
  • 延遲評估 (Lazy evaluation):參數傳入時才執行,不會預先載入

介紹到這裡,可能還是有些模糊不清
讓我們來看一個簡單的例子做比較:

// 物件導向
$ "ABCabc".downcase
> "abcabc"

// 函數導向
$ String.downcase("ABCabc")
> "abcabc"

在物件導向的世界中「萬般皆物件」
所以"ABCabc"是一個類別為String的物件
因此可以使用String的類別方法downcase

相對在,在函數導向思維"ABCabc"只是一個類別為String的參數
我們呼叫String module下個一個function donwcase並傳入參數
最後得到函數運作後的結果

雖然語言本身就已經區分特性
但事實上物件導向與函數導向是一種思維方式
所以就算是物件導向的程式語言
我們依然可以使用函數導向的思維來開發(反之我不確定可不可以)
例如React雖然是使用Javascript的框架
但其中就充滿著函數導向的特性

根據林信良的說法:

物件導向與函數式並不衝突,兩者可相輔相成。當面對職責混亂的物件,可試著以函數式概念對物件的函式進行重構,若一開始不知如何畫分物件職責,可試著先以函式為單元進行設計,再看看函式是否可進一步重構出子函式。當問題被分解為子問題,函式被切得夠細小,回過頭來會發現數個函式間的關聯性,這時無論是使用類別組織資料、將函式搬運至適當類別之中,都會有較清楚的判斷界線,從而實現更高階的物件導向概念。

目前網路框架的後端主要還是以物件導向為主(PHP, Java, Python, Ruby)
函數導向的好像只有Elixir(而且使用者還很少)
說穿了這其實是兩種不同的思維方式
在不同情境下用不同的方式解決問題

而且按照程式進化典範學習的慣例
未來的語言應該會融合兩者的特點而持續進化(例如Scala)
讓我們拭目以待吧!

參考資料:
https://www.ithome.com.tw/node/73705
https://blog.miniasp.com/post/2016/12/10/Functional-Programming-in-JavaScript.aspx


上一篇
Phoenix起步走:Changesets基本操作
下一篇
RESTful API設計模式談現代網頁框架
系列文
新時代的網頁框架比較-- 淺談Rails、Django、Phoenix、Laravel31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
froce
iT邦大師 1 級 ‧ 2017-12-22 09:14:42
// 物件導向
$ "ABCabc".downcase
> "abcabc"

// 函數導向
$ String.downcase("ABCabc")
> "abcabc"

這函數導向的例子我覺得舉的不好。
String.downcase("ABCabc")裡,String也是物件,downcase()是物件裡的方法。
"ABCabc".downcase則是物件裡的屬性。

函數導向的話,就我的理解會像這樣:

downcase("ABCabc")

純函數導向你會看到像是這樣的東西,拿簡單的知覺器來舉例:

def XOR(x1, x2):
    return AND(NAND(x1, x2), OR(x1, x2))

程式是由初始的輸入(x1, x2)來組成,盡量不改變其輸入值,中間不去另外指派(x1, x2),這樣能確保沒有全域變數被亂改的情形。
另外把各種執行階段都拆解成函數也可以很輕易的改成異步執行。
盡量把程式解構成函數也可以確保程式碼的可讀性和可重用性,將主程式拆解成像流程圖一樣,遇到問題再去找相關的函數修改。
也很利於單元測試。

真的純函數導向你還會看到有些神人連if、for都不用,map、reduce、filter就解決一切。

實際上的話你可以把物件導向看成資料封裝,在函數中你可以用物件當成輸入,然後去處理,得到一個傳回值,也可以在物件導向中的方法利用函數導向去解構,其實不衝突。

taiansu iT邦新手 3 級 ‧ 2018-01-01 01:34:46 檢舉

來幫忙平反兼廣告,在 elixir 的例子裡 String.downcase/1 跟物件無關。String 可以看做是一個 namespace,把一堆跟字串操作相關的純函式蒐集在一起。這東西我們叫做 module模組。

如果 ifcase 可以更清楚表達程式的意圖的話,還是會用啦。只是你會發現如果有 pattern matching,加上你說的那些高階函式,足以解決大部份的問題了,跟神不神人無關 XD

這篇底下的 JavaScript 跟 Elixir 的對比也許就比較符合你的原意?

https://ithelp.ithome.com.tw/articles/10194973

0
taiansu
iT邦新手 3 級 ‧ 2018-01-01 02:04:30

都平反了,也來一些修正。

延遲評估 (Lazy evaluation):參數傳入時才執行

不, lazy evaluation 是說當真的有需要值的時候,才進行求值。請參考 https://ithelp.ithome.com.tw/articles/10195170

函數式的 Web 框架其實行之有年,例如 Haskell 的 yesod,Clojure 的 Luminus。真的要算 Scala 也有 lift 跟 play。不講框架,史上第一個 web application "Viaweb" (後來的 Yahoo! Store) 就是 Paul Graham 用 lisp 刻的。早期的 reddit 也是 lisp。只是函數式編程從來就是小眾中的小眾。


[個人意見區]
另外我覺得物件導向跟函數式編程是衝突的。這兩種 paradiam 各自的強項,剛好是對方的弱項。只是因為近來每個語言都有 lambda,學習某種程度的函數式編程,可以讓你把一些技巧用在 OO 語言裡。也能學著從完全不同的角度來看待程式如何運作這件事。但是因為本質上的差異,在函數式編程裡用不到,也無法使用 OO 的概念。(除非要用 Erlang 作者 Joe Armstrong 的解釋法,不過那就是另一個議題了。)

不,在可預見的將來,我覺得不太會有融合這種事。Scala 是好一陣子的語言了,個人意見的話,我覺得他反而讓事情變得太過複雜了。

希望有幫上忙~ :D

Bater iT邦新手 4 級 ‧ 2018-01-01 11:57:57 檢舉

感謝回饋!

taiansu iT邦新手 3 級 ‧ 2018-01-02 03:24:08 檢舉

/images/emoticon/emoticon41.gif

其實我後來在餵小孩的時候,有大概想到為什麼你那時會那樣寫。過陣子整理一下再來這邊回 XD

taiansu iT邦新手 3 級 ‧ 2018-01-04 05:57:50 檢舉

我們先假設「參數傳入才執行」這句話為真。

在 JavaScript 或是 Ruby 裡,如果我們寫這樣的程式:

y = h(g(f(x)))

其中 x 應該可以理解為脈絡中的參數*。那麼程式會x 傳進 f 求值。得到的結果再傳入 g,以此類推。這就是 eager evaluation。

* 雖然事實上 f(x) 也是 g 的參數,而 g(f(x)) 是 h 的參數。

那麼在 haskell 中,會寫成這樣

y = h.g.f x

因為預設 lazy evaluation,且語法上也明示了,會先合成 hgf 得到一個函式。再將參數 x 傳入此合成函式,視為開始執行。這樣的話,我們的前提就得證了。

但是我們的前提不管在 eager evaluation 及 lazy evaluation 都是真的,因此重點不在什麼時候執行,而在於什麼時候傳入。

我要留言

立即登入留言