函數式語言庫模式:框架是魔鬼?

2020-12-05 CSDN技術社區

編者按:本文作者Tomas是F#語言的專家以及導師、計算機科學家,曾出版過有關F#的教程。本文重點介紹了如何設計組合化的庫以及如何避免在庫設計時進行回調。Tomas倡導以庫而不是框架的方式進行開發。以下為譯文。

框架 VS. 庫

框架和庫有什麼區別呢?兩者的主要不同之處在於如何使用它們以及編寫什麼樣的代碼。

  • 框架——框架控制了系統的運行,並定義了擴展點 (接口)來讓用戶進行實施;
  • 庫——庫把系統運行控制權交給用戶,並定義了功能和類型供用戶使用。


框架和庫之間的區別可用上圖表示。框架定義了一個結構,你不得不將其填充好;而庫則需要你圍繞其提供的結構進行編碼。

為什麼不建議使用框架?

框架最大最顯著的弱點是不可組合。如果你正在使用兩個框架,這兩者之間往往是很難兼容的;誰包含誰,誰是誰的外延也是不清晰的。

如果是庫,情況則有所不同。因為你才是決策人,所以能夠同時調用不同的庫,雖然這會增加一定的編程複雜度,但至少是能夠實現的。


框架另一個大問題是很難進行測試和探索。在F#中,載入一個庫並透過不同輸入來檢查輸出和庫的運行是很有用的。例如,可以使用Web開發庫 Suave來啟動一個簡單的Web伺服器,代碼如下:


代碼片中首先載入了庫,然後以默認方式調用startWebServer。該方式是非常有用的,因為可以讓用戶嘗試不同的參數來對輸出結果進行對比。

框架還有個問題是控制了用戶代碼的結構。一個典型的例子是如果你正在使用一個框架,它會要求你繼承一些抽象基類然後運行具體的方法。例如XNA框架中的Game類(雖然XNA框架終止了,但是其模式在另一個框架繼續使用著):

class Game {  abstract void Initialize();  abstract void Draw(DrawingContext ctx);  abstract void Update();}

在Initialize()中,需要對遊戲用到的資源進行預載;Update()在進行狀態刷新時會被反覆調用;Draw()則在更新屏幕時用到。這難道不正是命令式編程嗎?所以我們很可能會寫出類似的如下代碼:

代碼作用是讓人物往右移動。基於框架結構進行編程是有難度的,而這裡我使用了最直接的方法來實現。變量x表示的是人物位置,而mario則用於存放人物圖像資源。

雖然這在C#中或許會更加簡潔,但前提是要忽略全部的檢查。使用option目的是讓代碼更加安全(避免mario沒有定義就在Draw()中使用)。此外,誰能保證Initialize()一定在Draw()執行前就調用完畢?

如何避免框架錯誤

接下來我會講述如何使用庫而不是框架的具體原因。

即使你沒有使用F#來編寫庫,但是F#的交互性仍非常值得一試。F#不但可以用來編寫庫,其強大的交互性更使得庫的運用變得十分簡便。(如果是.NET平臺,可以嘗試 LINQPad)。

請看下面這個例子,它展示了如何使用

F#格式庫來把包含在文件夾中的F#腳本轉為HTML或對某單一文件進行操作。

如果是第一次接觸,我會首先看有關庫的說明,然後打開命名空間找到Literate,然後進行嘗試,例如輸入「.」。

我認為良好的庫都支持類似的探索步驟。再看另外一個例子, FunScript ;用於把F#代碼轉為JavaScript。以下生成的JavaScript代碼作用是為異步循環進行計數,在<tiltle>頁面按秒執行:

類似地,我們都可以遵循上一個例子的學習途徑掌握到相關用法。

接下來再看兩個例子。第一個是以標準鍊表的方式對數據進行處理;另一個是使用上述鍊表方式讀取輸入,然後檢查數據,最後再進行處理。

這兩個例子有什麼不同之處呢?對於鍊表List函數,常常是一個單一函數作為一個參數使用。而這個函數是無狀態的。

在第二個例子中,指定了兩個函數。於我而言,這通常預示著有複雜的事情發生。其次,readAndProcess要求我們返回例子1中的字符串狀態,然後把字符串作為下一函數的輸入。這會引起一個潛在的問題。如果例子2需要例子1轉入其它狀態,該如何處理呢?

讓我們進入

readAndProcess來看它執行了什麼操作;首先是進行異常處理,然後對輸入進行檢查。

如果對其進行改進,要如何做呢?我們不妨把它分解為兩個函數:

現在,validateInput變得簡化了,如果輸入是有效的則返回Some()的處理結果。而ignoreIOErrors函數仍作為參數使用。結合新函數,可以寫成:


代碼還是三行,但是更加清晰了,雖然比之前長了些。這樣一來程序變得到簡化,方便弄清楚其來龍去脈。

總的來說把函數作為參數使用是可以的,但是要注意儘量做到簡化。特別是牽涉到狀態的多次變化時,換另外一種處理方式或許會更好。

前面我們結合一個簡單的遊戲引擎講述了框架是如何影響我們編程的,如果在不使用可變域和執行指定類的情況下,又該如何處理呢?在F#中,可以嘗試異步工作流和基於事件的編程模型來代替。

其思路是使用觸發事件而不是編寫虛方法。因此,Game的定義變為:

結合F#異步處理以及庫的主動控制特長,我們可把代碼改寫為:


代碼中首先對資源和Game對象進行了初始化;然後做了循環處理,使用AwaitObservableUpdate或Draw事件進行監聽。雖然我們無法對遊戲狀態和屏幕更新進行控制,但是在初始化時我們是可以做到的,檢查遊戲何時運行以及等待事件的發生。

asnc{..}的使用是關鍵所在。我們可以使用AwaitObservable來實現在更新或重繪需要時恢復計算。這樣做的好處是可以實現更加複雜的操作,具體可參考這個例子Phil Trelford's Fractal Zoom。另外F#的agents代理可以實現類似的邏輯控制。

如果對F#不熟悉,或許會對上述代碼困惑。但我的目的是說明控制權掌握在自己手中的重要性,這樣可以寫出自己的抽象邏輯,這也是接下來要說的。

請再看看前述的Game例子,雖然低階抽象給予了充分的控制權,但是很多時候,我們希望寫出的遊戲是同時具備重繪和更新功能的。

這實現起來也不難,只需把某些部分作為參數使用:

startGame抽象實現了在初始化時把兩個函數作為參數使用。Update函數進行狀態刷新,draw函數使用DrawingContext重繪狀態。這樣一來,我們的例子可變為4行代碼:

因此只要仔細閱讀startGame的代碼,按需對其進行改動,便可實現全權控制。對比於建基於一個脆弱庫之上的程序,這種方式難道不更穩定可靠嗎?

對於庫來說,可組合屬性是我們選擇它而不是框架的原因之一。例如FsLab,這是一個用於F#的數據科學庫(包括Deedle,Math.Net Numerics),以單個腳本的方式連結呢其他的庫(原始碼)。

兩個簡單的例子是矩陣和框架的互轉Matrix.toFrame,Frame.toMatrix。

該轉換操作起來是很簡單的,因為Deedle框架和Math.Net矩陣都能轉化為一個2維數組,所以通過數組可方便地實現兩者的互轉。因此,即使是很複雜的庫,我們都應該為用戶保留足夠的庫合成權以實現更強大的功能(或者改寫)。

寫在最後

本文著重從可組合和避免回調方面對庫和框架進行比較。進一步說,框架模式不僅存在於軟體,在日常生活也是經常遇到的。例如參團遊,從一開始,交通、住宿、遊玩行程等都已經被固定了;而自由行則類似於庫的組合,任何細節都需要親力親為,從而實現全權控制。雖然參團遊很方便,但是對於我,特別是軟體開發,我還是更傾向於我的地盤我做主!


本文為CSDN編譯整理,未經允許不得轉載,如需轉載請聯繫market#csdn.net(#換成@)

相關焦點

  • 反對函數式編程的政治正確
    許多這樣不負責任的偏激言辭,造就了當前社區中對於面向對象與命令式編程的 Stereotype 刻板印象。實際上,把函數式編程與面向對象 / 命令式編程對立的觀點,其本身在分類上就是不嚴謹的。姑且不論這點,這些論調也相當武斷,忽略了技術的適用場景。例如,對於函數式編程的最主要的讚譽之一,就在於純函數的無狀態性質。
  • vk-uni-cloud-router v1.6.2 發布,uniCloud 雲函數路由框架
    sort_id=32686134、【優化】時間函數均支持時區,完美解決雲函數中的時區問題5、【優化】SKU組件已更新,支持切換主題風格,地址:https://ext.dcloud.net.cn/plugin?id=28486、【新增】action執行後的自定義中間件。
  • 2018考研高數邏輯框架梳理:多元函數微積分學
    ­  數學複習一定要注重基礎,第一輪基礎複習要把握好公式理論,掌握科目知識點邏輯框架。作為佔比最大的高數,難度也不小,考生一定要奠好基。新東方在線整合分享高數各部分知識點邏輯框架圖,大家先來整體把握。下面是多元函數微積分學部分邏輯框架圖。
  • 為什麼我不再使用 MVC 框架?
    SAM 是一個函數式反應型的編程模式,它致力於簡化數據 Model 和 View 之間的交互。它究竟有何優點值得作者棄用 MVC 呢?話題起因在我最近的工作中,最讓人抓狂的就是為前端開發人員設計 API。
  • 初學AI神經網絡應該選擇Keras或是Pytorch框架?
    一、發展演變歷程keraskeras出身就像是一個天生麗質的姑娘,是多個計算後臺框架的」前端」。keras是神經網絡的一個模型計算框架,嚴格來說不是神經網絡框架。其中神經網絡的各個層需要單獨定義,還有一些激活函數、損失函數等概念。看到這些對於一個AI剛入門的開發者確實有些茫然。keras是google的一個大佬開發的一個高度封裝的模型框架,已開源到github上。
  • 今日Paper|點雲分類框架;多模式Transformer;神經網絡;有序神經元等
    目錄用於行人重識別的三元組在線實例匹配丟失用於DSTC8 AVSD挑戰的帶指針網絡的多模式TransformerPointAugment:一種自動增強的點雲分類框架尋找稀疏、可訓練的神經網絡在線實例匹配(OIM)損失函數和三元組(Triplet)損失函數是行人重識別問題的主要方法。但這兩個損失函數都有缺點,OIM損失對所有樣本均等對待,沒有關注困難樣本,三重損失以複雜且繁瑣的方式來處理批處理樣本,因此收斂速度很緩慢。針對這些問題,這篇論文提出了三元組在線實例匹配(TOIM)損失函數,該函數能著重於困難樣本並能有效地提高行人重識別模型的準確性。
  • 為損失函數定個框架,碼隆CVPR 2019提出圖像檢索新範式
    本文將對碼隆科技的 CVPR 2019 論文進行解讀,介紹碼隆是如何為圖像搜索任務提出一個通用的損失函數框架,並將該領域近十年的損失函數都統一在該框架下。對於很多研究者而言,以前我們針對圖像搜索任務設計損失函數並沒有統一的框架,很多研究者都通過直觀理解嘗試新的損失函數。
  • 周小川:運用連續函數實現金融系統穩定
    通過運用連續函數,將會更容易實現金融系統穩定。 周小川表示,現在的規制特別是金融多使用黑白式或臺階式應對,較少運用連續函數。金融監管多參考法律體系思維,是黑白式的。但從調控角度、系統穩定性角度,連續函數更容易實現金融系統穩定。通過使用連續函數,金融系統更難出現不可計量部分,多數內容可計量,少數不可計量內容可以用專家打分法加權計算。
  • 函數式編程很難,所以你要學習它
    很 奇怪不是,很少有人每天都使用函數式程式語言。如果你用Scala,Haskell,Erlang,F#或某個Lisp方言來編程,很可能沒有公司會花錢 聘你。這個行業裡的絕大部分人都是使用像Python,Ruby,Java或C#等面向對象的程式語言——它們用起來很順手。不錯,你也許會偶然用到一兩 個「函數式語言特徵」,例如「block」,但人們不會去做函數式編程。
  • 華為深度學習框架MindSpore正式開源:自動微分不止計算圖
    自動微分:不止計算圖自動微分是深度學習框架的靈魂,有了它我們寫模型就只需要關注前向傳播,將所有複雜的求導、反傳過程都留給框架。一般而言,自動微分指一種自動求某個函數其導數的方法。在機器學習中,這些導數可以更新權重。在更廣泛的自然科學中,這些導數也能用於各種後續計算。
  • 爆發無周期性,大型淺層地震似乎遵循「魔鬼階梯」數學公式
    根據經典地震學模型預測,地震是隨構造應力的累計與釋放循環產生,具有周期性,然而美國密蘇裡大學團隊最新研究指出,大型淺層地震似乎遵循著「魔鬼階梯(Devil’s staircase)」這個數學函數公式——被較長但不規則的間隔隔開。
  • 二次函數公式:頂點式、交點式、兩根式
    一般地,自變量x和因變量y之間存在如下關係:      (1)一般式:y=ax2+bx+c (a,b,c為常數,a≠0),則稱y為x的二次函數。頂點坐標(-b/2a,(4ac-b^2)/4a)   (2)頂點式:y=a(x-h)2+k或y=a(x+m)^2+k(a,h,k為常數,a≠0).
  • go 學習筆記之學習函數式編程前不要忘了函數基礎
    標準的函數式編程具有濃厚的數學色彩,幸運的是,Go 並不是函數式語言,所以也不必受限於近乎苛責般的條條框框.簡單來說,函數式編程具有以下特點:不可變性: 不用狀態變量和可變對象函數只能有一個參數純函數沒有副作用摘自維基百科中關於函數式編程中有這麼一段話:
  • Nature Neurosci: 神經科學的深度學習框架是什麼?
    為什麼建立神經科學的深度學習框架,神經科學的深度學習框架的內容(是什麼),我們應該如何在深度學習框架下發展神經科學(怎麼做)。神經科學的深度學習框架內容深度神經網絡包括三個基本成分:目標函數,描述了學習系統的目標,是神經網絡中節點權重和數據本身的函數,但他們並非在特定的數據集上定義的。
  • 一文讀懂函數解析式的求法
    函數解析式的求法向來是高中數學的重點和難點部分,該部分內容在高考中經常遇到,能夠快速的求到函數解釋式對於高考想要拿高分的同學是必不可少的。本節就主要介紹各種函數解析式的求法,希望對大家有所幫助!函數解析式的求法函數解析式的求法主要分為以下幾類:構造法
  • 難度超高的二元函數微分偏導題!
    題幹說函數f(x,y)可微,那麼從這一點你能想到什麼呢?不賣關子了,函數可微意味著函數在定義域內是連續的,且可偏導的。題幹中又給出函數對y的偏導數小於0,這意味著當x給定時,函數f隨著自變量y遞減。又因為f(0, 0)=0,f(2, 1)>3,可以知道f(0, 1)<0,此時最重要的一步來了,現在對函數f(x, 1)在閉區間[0, 2]上用拉格朗日中值定理,得:根據上式,以及f(2, 1)>3,f(0, 1)<0,有以下關係式:因此答案選D。下面給出小編在做這道題時的思路邏輯框架圖。
  • 寫Python 代碼不可不知的函數式編程技術
    選自 Medium作者:Raivat Shah參與:魔王、Jamin本文對 Python 中的函數式編程技術進行了簡單的入門介紹。近來,越來越多人使用函數式編程(functional programming)。因此,很多傳統的命令式語言(如 Java 和 Python)開始支持函數式編程技術。
  • 初二上學期,一次函數對稱性求解函數解析式的兩種方法,結論秒殺
    有粉絲留言讓我梳理一下求一次函數關於x軸、y軸和原點對稱的解析式求法,還有粉絲髮私信讓我梳理下一次函數平移求解析式的方法,這一節我先講一下求一次函數對稱的直線解析式的兩種方法,可以將結論記下來,方便不易出錯。下一節來繼續介紹一次函數平移性求函數解析式的方法。
  • 初二上學期,一次函數平移性求解函數解析式,八字真言謹記在心
    在上一節內容中,我們介紹了一次函數對稱性求解函數解析式的兩種方法。這一節來介紹剩下的一次函數平移性求解析式的兩種方法,其中一種仍然是待定係數法,第二種方法還是需要記住結論,只有八個字,完全可以解決平移性類型問題。
  • 如何選擇神經網絡激活函數:有效的改善模型學習模式的能力
    學習過程會嘗試使機器具有學習能力,而無需進行顯式編程。 這是ANN的作用。什麼是人工神經網絡?典型的人工神經網絡(ANN)是受人腦工作啟發而設計的受生物啟發的電腦程式。 這些ANN稱為網絡,因為它們由不同的功能組成,這些功能通過使用過去稱為訓練示例的經驗來檢測數據中的關係和模式來收集知識。