摘要:開發者在設計庫時,應遵循如下幾大原則:迭代設計、可組合的、避免回調、抽象級別。此外,在你的庫中,應該提供高級函數來幫助用戶處理80%的任務。對于剩下的15%,應該提供一個低級API。
之前,我們曾發表《函數式語言庫模式:框架是魔鬼?》該文論述了庫與框架之間的區別,及如何設計組合化的庫。而本文作者在此之前,還發表了一篇《Library patterns: Multiple levels of abstraction》,結合具體實例,向大家非常詳細地介紹了庫設計模式及庫設計中的多級抽象思想。
以下為具體譯文:
庫設計模式
對于庫設計理論來說,有幾點在我的實際工作過程中體會深:
迭代設計——首先,不要為了一個庫而設計庫。在F#中,你可以把多個功能都放入一個腳本然后按需進行引用或復制至其它項目。這是好的庫需求分析途徑。一旦你想出更好的點子,就可以把它以文件形式加入到一個新項目中;
可組合的——可組合性是函數式編程的關鍵理論,其重要性等同于庫設計。一個庫應當可以讓用戶以簡單的方式來進行二次開發,實現更多更復雜的功能;
避免回調——回調是很容易影響可組合性的。當你編寫一些復雜的函數時(例如處理Markdown文檔),你可能會受到誘惑而進行參數化回調(例如置入一個預處理器來對文檔進行解析和翻譯間的轉換)。回調的問題是會使你的代碼被加上太多的結構。這不但不會帶來靈活性,反而隨著回調的增多而使設計變得更加復雜;
抽象級別——那么我們怎么才能找到簡單易用的API并在不同場合進行使用?問題的關鍵是能提供多級抽象。
我認為上述幾點是能影響庫設計好壞的。本文將先就抽象級別一點展開論述。
庫是如何被使用的?
每個庫都對應著一定的典型應用場合。比方說,F# Formatting格式工具可以對一個目錄下的所有文件進行文檔生成,這占到使用頻率的80%。有時我們可能需要以不同的方式來處理個別文件(例如使用不同模板)。一個完整的庫應能兼顧該需求。還有某些時候我們需要以別的方式來處理某個文件,如添加一個自動生成內容表(TOC)。
對于類似的情況,我找到一種行之有效的處理方法—以多級功能抽象的方式來創建庫。在高級,單一個函數調用應處理80%的應用場合。然后如果有需要,你可以再多建一級來處理額外15%的應用場合。后如果還有需要,就再多建一級來處理后4%的應用場合。對于后的1%,我的建議是發送一個pull request!
該設計模式在核心功能庫中是被深度采用的,例如F#鏈表庫。遵循該模式,還能使我們的庫成為領域的特定語言從而更具可讀性。
示例#1:鏈表
用鏈表來闡述多級抽象是很有代表性的。
高級:高頻函數
在高級抽象,可以使用高頻函數來對鏈表進行處理(或在C#中使用LINQ)。例如從0到100的整數中獲取正的sin值鏈表,可以這樣編寫:
類似List.map和List.filter的高頻函數在鏈接處理中有80%頻率會用到甚至更高。但有時我們可能需要非高頻操作。
低級:遞歸和模式匹配
例如根據符號變更對鏈表進行分拆,例如把[1; 4; -3; 2]分為[1; 4]和[-3; 2]。如果不借助低頻API以遞歸模式匹配進行處理,單靠高頻函數是很難實現的:
在loop中,進行了三種處理:
全部元素都是同符號;
發現有一個符號發生變更;
在符號變更前已經遍歷所有元素。
如果是使用C#中的IEnumerable<T>,或許操作起來略顯復雜,但是仍有一些低級API可供使用(使用GetEnumerator進行臨時集合和轉化)。
從低級轉到高級
集合API設計的好處是可以實現從低級到高級的轉換。splitAtSignChange函數在根據鄰近函數值進行拆分時,可以看成是一個更綜合操作的一個實例:
該函數與前個版本十分相似—不同的是增加了額外的參數f來判定什么時候斷開鏈表。雖然函數本身使用了低級API,但提供了轉為高級的途徑。
再回頭看高級的定義是能包含更簡單的編程方式。也就是說,根據X軸的臨界值來對從1到10的sin值進行鏈表進行拆分:
可見這就實現了到高級的轉換—使用兩個簡單明了的函數處理拆分問題。
示例#2:3D的領域特定語言
這是另一個典型的例子,特別是在進行自定義對象建模時。
超高級:創建城堡
其實現代碼如下:
在高級抽象層面,我們僅僅使用4行代碼就把城堡創建好了!但這僅能進行非常受限制的創作(規矩的城墻和塔組成的城堡),或許我們想做得更多。
高級:3D對象組合
如果想使用不同的塔外形該如何處理呢?要查看低級3D渲染代碼嗎?不必。tower函數本身就是根據另一種語言或抽象等級來進行編寫的。一個塔就是一個填色后的圓柱體加上一個填色的有正確朝向的圓錐體:
使用庫時,一開始是高級抽象的,但熟悉之后,我們可以進入下一級。在這級中,我們可以打造自己的語言(抽象)例如前述的List.splitAt。
低級:使用OpenGL進行面部渲染
在進行3D渲染時存在一個低級操作。以該庫為例,調用OpenGL原始操作是低級的(使用OpenTK包裝),這稍微有點復雜:
如果想以更簡單的方式來生成圖形,我們可以在3D原始操作和渲染之間加多一層,但這不是我們要討論的。即便如此,我們還是可以看出多級抽象的重要性。明顯的一點是可以從已創建的事物中發現如何實現高級到低級的轉換(例如塔是如何組成的),然后以其它方式使用低級原始操作。
換言之,如果你直接以調用OpenGL的方式來生成塔,那么是很難再生成其它形態塔的。但如果有多級抽象機制,這就不是問題了。
綜述
本文主要論述了庫設計中的多級抽象思想。在你的庫中,應該提供高級函數來幫助用戶處理80%的任務。對于剩下的15%,應該提供一個低級API。關鍵的一點是高級API可根據低級API來呈現。這樣不但滿足了日常需求,還給用戶留有二次開發的空間,是更加吸引和友好的。 (編譯/伍昆 責編/張紅月)
英文來自:Tomas Petricek's blog
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。