【演講稿】Understanding Typing. Understanding Ruby. by caterpillar | CodeData
top

【演講稿】Understanding Typing. Understanding Ruby.

分享:

錄影檔 …

簡報檔 …

 

非常榮幸地,小弟有機會在這邊作為 Ruby Conference 2014 的第一場 Keynote 講者,題目是從語言的資料定型來更一步瞭解 Ruby 的運用。

UnderstandingTypingUnderstandingRuby-2

這是我今天要談的範圍,扣除剛剛的第一頁,我還有 48 頁的投影片要進行。我會談到 …

什麼是定型(typing)?型態宣告(type declaration)的優缺點,像隻鴨子呱呱叫實際上代表了什麼?靜態定型與單元測試之爭,以及瞭解這一切對 Ruby 的使用有什麼幫助?

列點的順序呈現了我演溝的大致順序,由於我會從多角度來切入,因此順序上會稍微與這邊的列點有些不同…

UnderstandingTypingUnderstandingRuby-3

這是我的 Linkedin 簡歷,這張大頭照是女兒五歲左右幫我拍的,我很喜歡這張照片。

我目前是個自由工作者(free lancer),最熟悉的語言生態系(eco-system)是 Java,不過平常就愛涉獵各種程式語言,從中瞭解什麼叫作程式設計,這也是為什麼我會選「從靜態定型瞭解 Ruby 這個動態定型語言」作為題目的原因之一。

我明明最熟悉的是 Java,那我有什麼資格在 Ruby Conference 2014 擔任第一場 Keynote 講者呢?理由其實很簡單 …

拳王爭霸賽前總是要有小弟出來先暖場,不是嗎?

UnderstandingTypingUnderstandingRuby-4

我們從最簡單的開始:什麼是定型(Typing)?

其實我們都知道,電腦裏所有的一切都是位元,對於一組有用的位元資料,指定一種資料型態,也就是所謂的定型,這樣我們就可以用具體的概念來操作這一組位元,而不用直接面對 0101 的運算。

先用 Haskell 的 ghci 來作示範,2 在 Haskell 中的型態是 Num,“Justin” 的型態是字元序列(List),就算是 Lambda 函式也有型態,具有兩個參數(parameter) x、y 傳回 x > y 比較結果的 Lambda 函式,參數型態、順序與傳回型態組成了 Lambda 函式的型態。

來看各位比較熟悉的 Ruby 好了,大家都知道,Ruby 中每個東西都是物件(object),可以用 .class(dot class)取得物件是從哪個型態實例化(instantize)而來。舉例來說,2 的型態是 Fixnum,“Justin” 的型態是 String,Ruby 1.9 後新增的 Lambda 語法,建立的實例(instance)之型態其實是 Proc。

UnderstandingTypingUnderstandingRuby-5

因為現在的程式語言,都內建了許多型態,因此初學者一開始都不太需要思考如何定型的問題,動態定型語言因為少了靜態定型時,隨時會耳題面命的編譯器,更是讓初學者少了許多機會在學習語言的一開始就思考:什麼是定型?

實際上,當你開始要自定義型態之時,就是在面對定型的問題,在開始定型之前,你必須思考什麼?

當一組值具有相關性時,你才會為他們定義型態,對吧!例如,Haskell 中的布林值(boolean)具有 True 與 False 的關係,你才會為他們定義一個 Bool 型態。

除此之外,面對定型問題時,你還會思考這組有相關性的值,有哪些相關操作可以對該型態進行運算,像是 Haskell 的 Bool 型態,會有 &&(and)、||(or)、not 等函式(function)可對其進行運算。

思考定型時,必須思考值的相關性與相關操作,對多數程式語言來說都是必要的,以函數式(Functional)主要典範(Paradigm)的 Haskell 是如此,以物件導向為主要典範的 Ruby 也是如此,定義一個類別時,必須思考的,不就是相關的值與操作嗎?操作在 Ruby 中稱之為方法(method),可以用 methods 取得,對吧!

UnderstandingTypingUnderstandingRuby-6

動態定型的支持者都說他們討厭定型,這不太對,實際上他們還是得定型,他們討厭的東西之一,其實是型態檢查(Type checking),因為你得符合型態的約束(constraint),才能通過型態檢查。

舉靜態定型的 Scala 為例,isRuby 是 Boolean 型態,false 也是 Boolean 型態,year 是 Int 型態,2014 是 Int 型態,在這樣的情況下就相安無事。

如果 pi 是 Int 型態,3.14 是 Double 型態,這樣惱人的錯誤訊息就會出現,因為在 Scala 預設的定義上,Int 與 Double 之間並沒有可以直接轉換的關係。

如果 year 是 Int 型態,而 Int 定義有 toFloat 方法,那麼 year.toFloat 就不會有問題,然而如果 Int 沒有定義 toList 方法,那麼 year.toList 就會出現錯誤訊息。

你在這邊看到的示範,是在 Scala 的互動環境(Interactive shell)中進行,因為 Scala 是靜態定型語言,因此這些錯誤訊息,實際上都是在程式運行之前就被檢查出錯誤。

UnderstandingTypingUnderstandingRuby-7

根據型態檢查是發生在編譯時期或是在執行時期,可概略將程式語言分為兩類:靜態定型語言,像是 Haskell、Scala、Java;動態定型語言,像是 JavaScript、Python 與這兩日的主角 Ruby。

以 Scala 為例,如果你定義了一個 doubleMe 函式,接受 Int 引數(argument),你傳給他一個 10,就不會有事 …

你傳給他一個 3.14,那在 doubleMe 實際運行之前,編譯器就會跟你囉嗦型態不符(type mismatch)。

並不是動態定型語言就不會做型態檢查,以 Ruby 為例,雖然就傳遞引數給 doubleMe 而言,任何型態的值都可以,像是 10、3.14 等,只要它們有乘法操作,看 … 多方便 … 這確實是動態定型語言的優點之一,也就是 Duck typing,稍後我會再來討論它 …

但如果你傳給它一個沒定義乘法操作的物件,那運行時因為檢查到沒有乘法操作,還是丟出了 NoMethodError。

人們總是急躁的,在程式運行之前,就看到一堆型態檢查錯誤,總是令人感到不耐,只不過,動態定型將這種不耐,推遲到執行時期罷了 … XD

UnderstandingTypingUnderstandingRuby-8

呃 … 如果我沒記錯,今天是 Ruby Conference 對吧!?為了避免被丟水瓶,還是來多講一些靜態定型的壞話好了 … XD

其實你們應該知道很多了 … 像是 …

最常見的壞話就是 … 型態宣告(Type declaration)很囉嗦、讓程式碼沒有表達性、煩死人了。

另一個常見的壞話就是 … 明明我看到有隻鳥走路像個鴨子,游泳像個鴨子,叫起來也像個鴨子,但我就是沒辦法把這隻鳥當作鴨子來呼叫 …

再來就是 … 靜態定型就算通過了型態檢查,其實還是沒辦法保證函式的功能是正確的,當然,這是單元測試的職責,只是既然型態檢查的目的之一是要確認程式正確性,單元測試也能確認型態的正確性,那乾脆就全部都寫單元測試,在運行時程式的正確性就好了,何必在運行前白費工夫在型態檢查呢?

UnderstandingTypingUnderstandingRuby-9

呃 …你們會不會知道太多了 … XD

UnderstandingTypingUnderstandingRuby-10

靜態定型中,型態宣告真的會造成囉嗦、沒有表達性而且煩人嗎?

其實,型態宣告並不一定要寫出來,例如這是 Haskell 版本的快速排序法,沒有元素的 List 不用排序就直接回傳空 List 就好,有元素的 List 拆為首元素 x 與尾 List xs,將清單過濾出小於 x 以及大於等於 x 兩個部份,然後分別對這兩個部份做快速排序。

你有看到型態宣告嗎?沒有吧!Haskell 是靜態定型語言,由於它有強大的型態推斷(Type inference)機制,在這邊能自動推斷出每個變數的型態,因此就不用自行撰寫型態推斷。

不過,為了讓程式碼的意圖更清楚,Haskell 的文化上,反而建議你要將型態宣告寫出來,讓閱讀程式碼的人一看就知道,qsort 接受一個 List,傳回一個 List。想想看,有多少次你看 Ruby 的 API 文件,只是為了確認一個函式,接受什麼型態的引數,傳回值又是什麼型態呢?

UnderstandingTypingUnderstandingRuby-11

型態推斷,基本上就是編譯器可以從開發者如何使用變數的前後情境(Context)中,推斷出變數應該是何種型態,因此,就不用程式設計者自行宣告型態。

不只 Haskell,現代越來越多靜態定型語言中,都具備有型態推斷的能力,例如,這邊的 Scala 示範中,實際上編譯器可以從 1 推斷出,number 應該是 Int 型態,從 “Justin” 推論,name 應該是 String,從 (1, “Justin”, 38) 推斷,personalInfo 應該是 (Int, String, Int) 型態,就 Scala 的語意來說,是一個沒有名稱的型態。

UnderstandingTypingUnderstandingRuby-12

就算是囉嗦的 Java,也一直朝更好的型態推斷努力中,雖然還不完美,不過仍有幫助。

舉例來說,如果 persons 是 List<Person> 型態,那麼在呼叫 filter、maptToInt 時,都不用宣告 person 的型態 …

因為編譯器可以從 persons 的型態 List<Person> 中推斷,person 應該是 Person 型態。

如果語言中具備的型態推斷能力越強大,是否做型態宣告就更有選擇性,當型態宣告越有選擇性時,就突顯了型態宣告的本質就是提供型態資訊 …

UnderstandingTypingUnderstandingRuby-13

Java 界的名人 Martin Fowler 曾經在 2005 年發表過一篇〈DynamicTyping〉 的文章,裏面就提到,即使是在撰寫良好的 Ruby 程式碼中,如果缺少型態資訊的話,他就得經常問自己「現在到底是在哪了?」

UnderstandingTypingUnderstandingRuby-14

如方才談到的,型態宣告的本質就是在提供型態資訊。別以為動態定型語言中不會有型態宣告的需求 …

為了提到型態資訊給某個對象,動態定型的領域中,還是隨處可見型態宣告,雖然語法上沒辦法直接宣告型態,但你可能透過各種方式來宣告型態,以提供型態資訊 …

像是 Python 中可透過 python-rightarrow 這個程式庫,提供函式的型態資訊 …

有趣的是,我略為搜尋了一下 Ruby 領域中有沒有類似的東西,還真的有 … 你們應該很熟這種用法 … typesig 其實就是一個方法 …

UnderstandingTypingUnderstandingRuby-15

型態資訊的提供也可能透過註解中,事先定義好的格式來標示,型態資訊的對象不一定是人,不一定只是單純地為了可讀性,也可能是提供某個工具,先前看到的 python-rightarrow,其型態資訊只要工具有支援,就可以如這邊看到的,可以提供實質的智慧提示選單。

別以為你沒看過型態宣告,動態定型語言的 API 文件中就有一大堆,即使那是透過命名慣例,但名稱上確實提供了型態資訊,從這邊你可以看到 fill 可以接受 object,傳回 array,也可以接受 range,傳回 array,start、index、length 等,都暗示了它們的型態是 Fixnum 整數型態。

UnderstandingTypingUnderstandingRuby-16

對於靜態定型,變數上也會有型態資訊,這讓函式或方法重載(overloading)在實作時方便許多,也就是允許數個方法具有相同的名稱,但是方法的參數與傳回型態各有不同,當呼叫者以對應的引數型態呼叫時,編譯器可以藉由引數型態來確定呼叫者想呼叫的版本。

函式重載也可以根據引數的個數來區分,這麼一來數個方法也可以具備相同的名稱。

UnderstandingTypingUnderstandingRuby-17

那麼,當你想要重載函式時,你心裡想要的是什麼?也許你想要的是 ad-hoc polymorphism,這種多型是指一個多型函式內可以依引數型態的不同,而有著特定或截然不同的流程處理, ad-hoc polymorphism 比較多人沒聽過,然而,大多數人知道的函式重載,就可以用來實現 ad-hoc polymorphism。

舉例來說,在這個 Java 程式範例中,方法名稱都是 valueOf,然而參數型態各不相同,這是一個 ad-hoc polymorphism 不錯的實現範例,因為 boolean、char、int 等不同型態,在 valueOf 中有著截然不同的處理方式。

UnderstandingTypingUnderstandingRuby-18

再來看這個例子,雖然在這邊,表面上 show 方法重載了,然而進一步看看兩個 show 方法的實作內容,除了變數名稱與型態不同外,並沒有其他相異處,這種情況下,並不適合使用重載來解決這個問題。

你應該檢視一下目前的設計,看看 SwordsMan 與 Magician 的實作中,是不是適合將相同的實作提昇(Pull up)至一個父類別 Role,如果是的話,那麼就可以用一個 show 方法來重構上頭的兩個 show 方法 …

對於許多人來說,這種多型就很熟悉了,也就是使用次型態(Subtype)多型來解決這類需求,會是比較好的方式。

UnderstandingTypingUnderstandingRuby-19

在運用函式重載時,有時可以思考一下,是不是想要的東西其實是預設值(default value)?例如,在 Java 這種沒有預設值語法的語言中,很多情況下,重載之目的其實是為了實作出預設值的概念,例如 TreeSet 的建構式(Constructor)實作中,可以接受 NavigableMap 實例,另一個建構式會自動建立 TreeMap,也就是具備 NavigableMap 行為的實例後,呼叫另一個建構式 …

也就是說,呼叫不需引數的 TreeSet 建構式時,實際上是在提供預設值 …

UnderstandingTypingUnderstandingRuby-20

那麼動態定型語言中怎麼做函式重載呢?靜態定型語言的函式重載,其實是在編譯時期就決定了呼叫的方法版本,因而重載經常被說是靜態時期多型…

相對來說,因為動態定型變數上沒有型態,就沒辦法有靜態時期,也就是編譯時期檢查參數型態,因而只能在執行時期檢查型態或者是…呃…檢查特性(property)…

不少動態定型語言都有提供預設值的語法,不過其實沒有也沒關係,例如,JavaScript,其實也可以透過選項物件(option object)的方式來實現預設值語法 …

舉個例子來說,Ruby 的 Array 有個 fill 方法,就實現了重載 … 你知道它是怎麼實現的嗎?…

UnderstandingTypingUnderstandingRuby-21

這是我找到的原始碼,可以看到參數的部份,有三個設定了 nil 做為預設值,藉由判斷參數值是否為 nil,就可以知道呼叫者有沒有提供對應的引數給 fill,這實現了依引數個數來重載 …

至於依參數型態來重載的部份,自然就得在執行時期進行型態檢查,不過,注意到,不一定要用 is_a? 方法,在這邊你可以看到,它用了 respond_to?,也就是說,不一定是 Range 實例,只要像 Range 的,有 begin 與 end 行為的物件,都可以用來呼叫 fill 方法 … Duck typing 對吧!既然動態定型可以 Duck typing,你當然也就得思考一下,什麼時候要用 is_a?,什麼時候要用 respond_to? … 而不是只會說 … 我要 Duck typing,而實際行動卻不完全是那麼一回事 …

如果你有寫過 JavaScript,應該很熟悉特性偵測這手法,其實在 Ruby 中許多場合也可以運用 …

當你檢查引數型態或特性時,就要思考一下,你是不是想要實現 ad-hoc polymorphism,如果是的話,記得再確認一下你的程式流程,有沒有依不同的引數型態或特性,而有著截然不同的實作?…

UnderstandingTypingUnderstandingRuby-22

既然談到了 Duck typing,就多來談一點 Duck typing 好了 …

在這個 Ruby 的 drawQuack 方法中,只要 duck 參考(refer)的物件有 quack 方法,那麼就可以呱呱叫 … 對吧!

靜態定型語言沒辦法這麼做,對吧?…

在這個 Scala 範例中,Duck 與 Man 是沒有關係的兩個類別,然而都定義了 quack 方法,drawQuack 可以接受具有 quack 方法的物件,不管物件是何種型態,這是 Scala 用來支援 Duck typing 的語法,稱之為 Structural typing …

靜態定型語言沒辦法這麼做 … 真的嗎?

UnderstandingTypingUnderstandingRuby-23

有人會說 … 喂 … 喂 .. 別騙人了 … 以為我不懂嗎?Structural typing 其實是靜態時期就決定好了吧!充其量只是在靜態時期模彷 Duck typing 吧!

嗯 … 你現在看到的 Java 程式碼,也可以做 Duck typing,只要傳給 doQuack 的物件有 quack 方法就可以了,這是利用到 Java 的反射(Reflection) API,可以在執行時期對任何具有 quack 方法的物件,呼叫其 quack …

當然啦!跟動態定型比起來,真的是囉嗦多了,畢竟 … 這才是 Java … XD

UnderstandingTypingUnderstandingRuby-24

我說 …這才是 Java … 不只是在嘲笑 Java 真的比較囉嗦 … Java 本身就不是一個用來展現優雅的語言 … 在一些場合,囉嗦甚至反而會成為 Java 的優點,Java 在 Duck typing 的實現上讓開發者麻煩了許多,多半也表示這門語言,不太希望你使用 Duck typing …

那麼反過來看,為什麼你需要 Duck typing?彈性,特別是在一些需求變化快的 start-up 場合,在定義函式的實作內容時,如果不用特別去在型態上定義一些操作,儘管在函式中增加需要的操作,對開發初期而言,真的是非常非常地爽快 …

只是在爽快的感覺過後,專案已經渡過了 start-up 之後,大家的地位好像就拉近了 … 這時你該想的是,你現在的 Duck typing 運用中,實際上使用了一組操作…

那麼這組操作應該屬於誰?

某個類別的實例?

或者是不同類別的實例?

UnderstandingTypingUnderstandingRuby-25

如果這組操作是屬於某個類別的實例呢?舉例來說,也許你會為了將實例 duck 傳給 drawQuack 方法執行,而臨時為實例定義 quack 單例方法(singleton method),實際上,你是在 duck 的匿名單例類別(anonymous singleton class)上定義了 quack 方法。

因而,上頭的單例方法定義,實際上也可以使用 Ruby 的開放類別語法,在開啟了 duck 的匿名單例類別之後,定義 quack 方法,也可以達到相同的效果。

UnderstandingTypingUnderstandingRuby-26

所以,在享受過 Duck typing 與單例方法的方便性之後,你應該重構(refactor)程式碼 …

例如,如果在程式已經過 start-up 之後,你發現這樣的程式碼,但是你沒有權利或團隊慣例上不建議直接修改 Duck 類別所在的原始碼 …

可以考慮開啟 Duck 類別,在其中定義 quack 方法 …

如果你有權利直接定義 Duck 類別,慣例上也不違反,那就直接定義在 Duck 類別…

UnderstandingTypingUnderstandingRuby-27

如果這組操作不屬於某個特定類別,而是可能在某幾種類別的實例之間共享呢?

在 Java 中,對於規範這組操作的公開協定(protocol),我們會使用 interface。

例如,我們會定義 DuckLike interface …

然後讓需要擁有這組操作的類別去擁有這個 interface 並實作操作細節 …

doQuack 方法接受擁有 DuckLike 行為的實例,也就是實作了 DuckLike interface 的實例,這麼一來,方法本體就簡潔多了 …

Java 囉嗦的好處就是,沒有擁有 DuckLike 行為的物件傳給 doQuack,編譯器絕對不會放過它,也就是說,執行時期的型態錯誤在這種情況下不會發生 … doQuack 的呼叫者,被強制規範了,至少,在開發成員眾多、水準不一、慣例之類的約束難以貫徹或確實傳達的環境中,這種強制規範算是種好處 …

UnderstandingTypingUnderstandingRuby-28

所以了,再說一次,當你使用 Duck typing 時,實際上,你正在使用一組操作,Java 的 interface,只是迫使你思考,這組操作形成的集合,是不是要有個適當的名稱,也就是 interface 的名稱,然後要求實作類別都得實作出來 …

那麼,如果發現到,不同的類別實例,其實它們的一組方法實作有著類似的內容,你會怎麼做呢?

UnderstandingTypingUnderstandingRuby-29

對於 Java 來說,JDK8之前可以透過設計模式(design pattern)來解決這類問題,而在 JDK8 中,在 interface 語法上多了個 default method 可以更漂亮地解決,你可以在介面讓方法有限度的實作了 …

例如,許多物件都會有比大比小的需求,實際上,像 lessThan、lessOrEquals 等方法,都可以基於 compareTo 來實作 …

因此,當你在 JDK8 如這邊 Comparable 實作之後,任何想要比大比小的實例,只要讓其類別實作 Comparable 的 compareTo 方法,就可以擁有 lessThan、lessOrEquals 等現成的實作了 …

UnderstandingTypingUnderstandingRuby-30

在 Ruby 中,你會怎麼做?情勢應該很清楚了 … 用 module …

你可以定義一個 Comparable module,讓小於、小於等於等運算子的定義,基於 spaceship (<=>)這個運算子,然後讓想擁有小於、小於等於等運算子的類別,include Comparable module,並實作 spaceship 運算子,就可以小於、小於等於等現成的實作了 …

Duck typing 很有彈性,不過不表示,你在享用過彈性之後,任何進一步的思考都不用做 …

UnderstandingTypingUnderstandingRuby-31

靜態定型如果遇上物件導向,就會引發更囉嗦的事情,來看看 Java 的 Generics 好了,在 Java 還沒有 Generics 之前,如果你想要定義一個可收集各種容器的容器,必須用 Object …

問題來了,因為編譯器看你 get 回來的是 Object 型態,不能指定給 String 的 text,本來會抱怨些什麼 … 不過 …

你用了 CAST 語法讓編譯器住嘴了,因為你認為自己知道在做什麼 … 很多人覺得 CAST 語法好像很玄 … 那沒什麼 … 多數情況下,大家只是拿它來封住編譯器的嘴罷了 …

UnderstandingTypingUnderstandingRuby-32

只是,既然你要編譯器住嘴了,那麼在指鹿為馬的時候,編譯器就算千百個不願意,也只能忍氣吞聲 …

要是發生了 ClassCastException 的轉型錯誤,那一定是你的錯 … 跟編譯器沒關係了 …

Java 提出了 Generics 來拯救這個世界了 … 嗎?

Generics 可以讓你指定型態參數(type parameter),例如,在這邊你指定 ArrayList 裏都是 String,那麼,你就只能在裏頭放 String … 如果你斗膽在裏頭放別的,像是整數 …

那編譯器就會馬上告訴你,你正在做一些無腦的事情,而且,你沒辦法叫它住嘴 … YA!

UnderstandingTypingUnderstandingRuby-33

事實上,Generics 可以摧毀程式碼,看著這堆角括號,我頭都痛了 … XD

如果你可以指定型態參數,其實就是對型態的一種擴充,ArrayList<String> 會是一種型態,ArrayList<Integer> 會是一種型態 …

如果型態參數再遇上物件導向,那無疑就是對型態的一種超巨大的擴充,你得考慮繼承的情況,得考慮正變性(Covariance)、逆變性(Contravariance)…

每次看到這種 Generics 應用,連我都很想跳到 Ruby 的世界了 … XD

UnderstandingTypingUnderstandingRuby-34

Generics 其實是一種參數多型(Parametric polymorphism)的實現,只不過,在物件導向的世界中實現本來就很複雜,如果遇上如 Java 這種在 Generics 設計上不太優良的情況,就會更加複雜 …

複雜度不會憑空消失,被隱藏起來的複雜度,一定會在某處兌現,反過來說,如果有苦命的 API 設計者幫你兌現了複雜度,身為 API 的客戶端,就能得到方便性,像是在這邊的範例,其實就獲益於 Generics 與 Lambda 的型態推斷能力,而能讓程式碼簡潔許多 …

而且,就如剛剛所看到的,Generics 將型態檢查的工作,從執行時期推向了編譯時期,這麼一來,就可以避免執行時期錯誤 ClassNotFoundException 的發生…

UnderstandingTypingUnderstandingRuby-35

談 Generics 做什麼?我相信各位當中,有些人是因為看到 Java 的 Generics 就沒力,因而放棄了 Java 而進入 Ruby 的世界 … 只是我想藉由剛剛簡短的 Generics 介紹,讓各位想想,誰應該為型態檢查負出責任?

靜態定型語言的支持者認為,編譯器能在編譯時期執行全面的型態檢查,確保某些因錯誤型態而引起的錯誤不致於發生 …

動態定型語言的支持者認為,呼叫函式時就算確定傳遞的型態是正確的,也不保證函式的結果會是正確的,只有單元測試才能保證型態與行為都是正確的 …

UnderstandingTypingUnderstandingRuby-36

這下子就導到了靜態定型與單元測試之爭了 … 如果從這個角度來看 … 編譯器就像是在作型態的斷言(assertion)測試,而且,如果語言本身擁有很好的型態推論能力的話,這個型態斷言測試幾乎可以不付出太代價,甚至免費的擁有。

實際上,編譯器提供的型態錯誤訊息越多,表示你越無法通過型態的斷言測試,表示你沒有仔細思考型態,為了通過編譯器這邊,你需要更仔細地思考有關型態的問題。

有時開發者以為自己對型態思考的夠多了,應該沒什麼問題了,實際上並不是如此,這種情況在 Haskell 這個特別重視型態正確的語言中,更容易發生,要通過 Haskell 的編譯如此之難 …

甚至還有這種玩笑話,嘿!編譯通過,可以出貨了 … XD

UnderstandingTypingUnderstandingRuby-37

在使用動態定型語言時,開發者免去了型態宣告的負擔,不過 … 變成得負擔型態檢查的工作 …

那麼,你知道程式碼中實際上要進行哪些型態檢查嗎?這個語言中採用了哪些型態?針對型態檢查,你知道如何做單元測試嗎?

你會想要,或者你能不能寫更多的單元測試,用以確保程式中的型態與行為都是正確的?

請問在場的各位,你有沒有寫單元測試的習慣?

UnderstandingTypingUnderstandingRuby-38

型態檢查該檢查什麼呢?回顧一下剛剛看過的 fill 方法 …

為什麼 fill 方法中,用 respond_to? 來檢查,而不是用 is_a? 來檢查?

UnderstandingTypingUnderstandingRuby-39

既然談到了單元測試,那就來看看怎麼測試這個 doubleMe 方法好了,到底要怎麼測試 … 施主 … 這個問題得問你自己 …

你有真的想過 doubeMe 到底要 double 的對象是誰嗎?Fixnum、String、Array 的實例嗎?Range 可不可以?喔!應該不行吧!Range 沒有定義 + 這個方法。

那麼,如果有個物件沒有定義 + 這個方法,你的 doubleMe 裏要不要做這項檢查?檢查出不具 + 這個方法時要不要拋出例外?還是 … 應該在被傳入的物件上定義 method_missing 方法呢?

在動態定型語言中,在單元測試時,可以多問問自己有關型態的問題,畢竟沒有編譯器在一旁嘮嘮叨叨了 …

UnderstandingTypingUnderstandingRuby-40

Joshua Bloch說「既然寫正確的程式那麼難,我們就應該盡力取得幫助。所以,能減少bug的所有東西都是好的。這就是我是靜態型別語言和靜態分析的信徒的原因」…

也就是說 … 在動態定型語言中 …

UnderstandingTypingUnderstandingRuby-41

要找出自己對型態檢查到底有哪些實際需求,而且不單只是依靠單元測試,在必要的時候,不要只會ㄍ一ㄥ …

找看看 Ruby 的生態系中,有沒有像 python-rightarrow、PEP3107 – Function Annotations 這類的工具 …

UnderstandingTypingUnderstandingRuby-42

如果真的需要,強者也是可以借用一下 IDE 的啦!… XD

UnderstandingTypingUnderstandingRuby-43

甚至是 … 找些像是 TypeScript 這類的語言來作協助 … 這個語言有點意思,它基於 JavaScript 的語法直接作擴充,有個 tsc 編譯器用來將程式碼編譯為 .js 檔案…裏頭當然是 JavaScript 的原始碼 …

TypeScript 其實是動態定型語言,不過你可以在必要的時候加註型態,對於有加註型態的程式碼,TypeScript 可以做型態推斷,也就是你可以有編譯時期檢查,它甚至也有 Generics 之類的東西 …

如果不想加註型態也沒關係,就當作動態語言來用罷了 … 你擁有選擇的權利 …

選擇的權利?對!選擇的權利!動態定型語言與靜態定型語言相比較,最重要的是前者擁有型態檢查的時機、工具等選擇的權利 … 而不像靜態定型語言那樣,被強制的情況比較多 …

既然你擁有選擇的權利,就別輕易放棄 … 多做些型態上的思考總是好的 …

UnderstandingTypingUnderstandingRuby-44

看看時間,差不多是該做結論了 … 不過有個小小的問題留給各位 …

有機會的話,請各位想想,在 Java 中,checked exception 與 unchecked exception 的差別在哪?對!全世界只有 Java 特別將例外區分為這兩大類 … 也造成了不少麻煩 …

只不過,當編譯器介入例外的檢查之後,其實倒是有機會讓我們想想,哪些例外是客戶端可處理的,哪些例外是程式的 bug …

如果能從 Java 的 checked exception、unchecked exception 對例外多一些思考,那麼回到 Ruby 之後,想必對各位在處理錯誤的時候也會更有所幫助!

UnderstandingTypingUnderstandingRuby-45

這是我的經驗 … 我愛研究各種語言生態,就是因為這些語言之間總能交相刺激我的思考 …

在定型這塊,我往往會發現到,想要更好的運用動態定型,就需要瞭解靜態定型 … 實際上在準備這場演講的過程中,我不斷地思考靜態定型裏的東西,在 Ruby 中要怎麼做,然後我又更深入認識了 Ruby …

只有在知道靜態定型的優點之後,也才能進一步學習到動態定型的優缺點 …

能理解靜態定型為什麼要有那些限制,也能發掘出更多你在使用動態定型語言時,應當負有的責任 …

UnderstandingTypingUnderstandingRuby-46

Ruby 有著這麼多具強大威力的特性 … 動態定型、物件個體化、開放類別、執行時期自省等 …

手上握有 Ruby,你就握有這麼大的能力,那麼 … 有想過你應該負起的責任是什麼呢?

UnderstandingTypingUnderstandingRuby-47

“We‘re all consenting adults here.” 對吧!我們都知道自己在作什麼,對吧!

不過,這可不只是一個口號而已!

UnderstandingTypingUnderstandingRuby-48

記得!能力越大,責任越重!

UnderstandingTypingUnderstandingRuby-49

如果你想更深入地瞭解 Ruby,記得,有時間的話,多進一步認識靜態定型!

很感謝各位聽完我今天的分享!謝謝大家!

分享:
按讚!加入 CodeData Facebook 粉絲群

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

Code & Data04/28

don't check whether it IS-a duck: check
whether it QUACKS-like-a duck, WALKS-like-a duck,
etc, etc, depending on exactly what subset of duck-like
behaviour you need to play your language-games with.

https://groups.google.com/forum/?hl=en#!msg/comp.lang.python/CCs2oJdyuzc/NYjla5HKMOIJ

熱門論壇文章

熱門技術文章