設計/構建大型功能程序的好方法是什麼,特別是在Haskell中? html
我已經閱讀了不少教程(本身編寫一個方案是我最喜歡的,Real World Haskell緊隨其後) - 可是大多數程序都相對較小,並且是單一目的。 另外,我不認爲它們中的一些特別優雅(例如,WYAS中的大量查找表)。 git
我如今想要編寫更大的程序,包含更多移動部件 - 從各類不一樣來源獲取數據,清理數據,以各類方式處理數據,在用戶界面中顯示,持久化,經過網絡進行通訊等。一個最好的結構,這樣的代碼是易讀,可維護,並適應不斷變化的要求? github
有大量文獻針對大型面向對象的命令式程序解決了這些問題。 像MVC,設計模式等的想法是實現普遍目標的理想規定,例如在OO風格中分離關注點和可重用性。 此外,較新的命令式語言適合於「隨着您的成長而設計」的重構風格,在個人新手看來,Haskell彷佛不太適合。 數據庫
Haskell有相同的文獻嗎? 如何在功能性編程(單子,箭頭,應用等)中使用異域控制結構的動物園最好地用於此目的? 你能推薦什麼最佳實踐? 編程
謝謝! 設計模式
編輯(這是Don Stewart回答的後續行動): 安全
@dons提到:「Monads在類型中捕獲關鍵的建築設計。」 網絡
我想個人問題是:如何在純函數式語言中考慮關鍵的架構設計? 數據結構
考慮幾個數據流的示例和幾個處理步驟。 我能夠將數據流的模塊化解析器編寫爲一組數據結構,我能夠將每一個處理步驟實現爲純函數。 一個數據所需的處理步驟將取決於其值和其餘數據。 一些步驟以後應該是GUI更新或數據庫查詢等反作用。 架構
什麼是以正確的方式綁定數據和解析步驟的「正確」方法? 人們能夠編寫一個大功能,爲各類數據類型作正確的事情。 或者可使用monad來跟蹤到目前爲止已處理的內容,並讓每一個處理步驟從monad狀態得到接下來須要的任何內容。 或者能夠寫不少單獨的程序併發送消息(我不太喜歡這個選項)。
他連接的幻燈片有一個咱們須要的東西子彈:「將設計映射到類型/函數/類/ monad上的成語」。 什麼是成語? :)
也許你必須退後一步,想想如何將問題的描述轉化爲設計。 因爲Haskell是如此高級,它能夠以數據結構的形式捕獲問題的描述,將過程的動做和做爲函數的純轉換捕獲。 而後你有一個設計。 編譯此代碼並在代碼中查找有關缺乏字段,缺乏實例和缺乏monadic轉換器的具體錯誤時,開始開始,由於例如,您在IO過程當中須要某個狀態monad的庫中執行數據庫Access。 瞧,有節目。 編譯器提供您的心理草圖,並使設計和開發保持一致。
經過這種方式,您從一開始就受益於Haskell的幫助,編碼很天然。 若是你想到的是一個具體的普通問題,我不會在作「功能性」或「純粹」或足夠的通常性事物。 我認爲過分工程是IT中最危險的事情。 當問題是建立一個抽象一組相關問題的庫時,狀況就不一樣了。
Gabriel的博客文章可擴展程序架構可能值得一提。
Haskell設計模式與主流設計模式的區別在於一個重要方面:
傳統架構 :將A類的幾個組件組合在一塊兒,生成B類「網絡」或「拓撲」
Haskell架構 :將A類的幾個組件組合在一塊兒,生成相同類型A的新組件,其特徵與其取代部分沒法區分
一般狀況下,一種看似優雅的建築每每會從圖書館中脫穎而出,這種圖書館以自下而上的方式展示出這種良好的同質感。 在Haskell中,這一點尤爲明顯 - 傳統上被認爲是「自上而下的架構」的模式每每會被捕獲在像mvc , Netwire和Cloud Haskell這樣的庫中。 也就是說,我但願這個答案不會被解釋爲嘗試取代這個線程中的任何其餘人,只是結構選擇能夠而且應該理想地由域專家在庫中抽象出來。 在我看來,構建大型系統的真正困難在於評估這些圖書館的建築「善」與全部實際問題。
正如liminalisht在評論中提到的那樣, 類別設計模式是Gabriel關於該主題的另外一篇文章,相似地。
我在Haskell的工程大項目 以及XMonad的設計和實現中談到了這一點。 大型工程是關於管理複雜性。 Haskell中用於管理複雜性的主要代碼結構機制是:
類型系統
剖析器
純度
測試
Monads用於結構化
鍵入類和存在類型
併發和並行
par
到你的程序打,方便,可組合並行的競爭。 重構
明智地使用FFI
元編程
包裝和分銷
警告
-Wall
來保持代碼清潔氣味。 您還能夠查看Agda,Isabelle或Catch以得到更多保證。 對於相似lint的檢查,請參閱偉大的hlint ,它將提出改進建議。 使用全部這些工具,您能夠處理複雜性,儘量多地刪除組件之間的交互。 理想狀況下,你有一個很是大的純代碼基礎,它很容易維護,由於它是組合的。 這並不是老是可行,但值得瞄準。
一般: 將系統的邏輯單元分解爲可能的最小參考透明組件,而後在模塊中實現它們。 組件集(或組件內部)的全局或本地環境可能會映射到monad。 使用代數數據類型來描述核心數據結構。 普遍分享這些定義。
在Haskell中設計大型程序與在其餘語言中進行設計沒有什麼不一樣。 大型編程是將您的問題分解爲可管理的部分,以及如何將這些部分組合在一塊兒; 實現語言不過重要。
也就是說,在大型設計中,嘗試利用類型系統以確保您只能以正確的方式將各個部分組合在一塊兒是一件好事。 這可能涉及newtype或phantom類型,以使看起來具備相同類型的東西不一樣。
當你進行重構代碼時,純度是一個很大的好處,因此儘可能保持儘量多的純代碼。 純代碼很容易重構,由於它與程序的其餘部分沒有隱藏的交互。
Don給出了上面的大部分細節,但這是我在Haskell中執行系統守護進程等很是實用的有狀態程序時的兩分錢。
最後,你住在monad變換器堆棧中。 最底層是IO。 在此之上,每一個主要模塊(在抽象意義上,而不是文件中的模塊意義)將其必要狀態映射到該堆棧中的層。 所以,若是您將數據庫鏈接代碼隱藏在模塊中,則將其所有寫入MonadReader類型鏈接m => ... - > m ...而後您的數據庫函數始終能夠得到其鏈接而無需其餘函數模塊必須意識到它的存在。 您可能最終獲得一個承載數據庫鏈接的層,另外一個配置,第三個用於解決並行和同步的各類信號量和mvars,另外一個用於日誌文件處理等。
首先找出你的錯誤處理。 Haskell在大型系統中目前最大的弱點是過多的錯誤處理方法,包括像Maybe這樣糟糕的錯誤處理方法(這是錯誤的,由於你不能返回任何關於出錯的信息;老是使用Either而不是Maybe除非你真的只是意味着缺失值)。 弄清楚如何首先完成它,並從庫和其餘代碼使用的各類錯誤處理機制中設置適配器到最後一個。 這將爲您節省一個悲傷的世界。
附錄 (摘自評論;感謝Lii和liminalisht ) -
更多關於將大型程序切割成堆棧中的monad的不一樣方法的討論:
Ben Kolera爲這個主題提供了一個很好的實用介紹, Brian Hurt討論了lift
monadic動做lift
到你的自定義monad的問題的解決方案。 George Wilson展現瞭如何使用mtl
編寫適用於任何實現所需類型類的monad的代碼,而不是自定義monad類。 Carlo Hamalainen撰寫了一些簡短有用的筆記,總結了喬治的演講。