Haskell 是一門風格極其獨特的語言。網絡上我們往往能夠看到各式各樣對其函數式特性的評價,我相信不少人對其的印象就是一門“函數式純度高、學術風格濃厚、學習曲線陡峭”的語言。
最近機緣巧合下我嘗試了一下這個語言。從我個人的感覺而言,這門語言其實並沒有想象中那麼難,很多特性的引入不僅在我看來很自然,對於代碼抽象程度和表達能力的提升也是肉眼可見的。
這篇文章我將以一個有其他函數式語言經驗的 Haskell 初學者視角,談談我的看法。
如果你還沒有決定是否要學這門有趣的語言,或者剛剛開始你的 Haskell 旅程,我想看完本文以及計劃中的系列文章後,你會有一些全新的想法。
1. 為什麼要學 Haskell?
對我而言,理由其實很簡單:之前接觸了不少語言,有些是混合範式語言中具備不少函數式風格,有些雖然是函數式語言,但由於各種設計上的考量最後還是引入了一些沒那麼函數式的特性。隨着對函數式風格理解的逐漸深入,最終接觸最純粹的函數式語言 Haskell 幾乎是必然的。
當然,除了對我自己,我始終覺得對於任何一個開發人員而言,學習掌握函數式風格語言獨特的抽象方式都是有價值的。
1.1 有助於學習其他新語言
首先,學習掌握函數式語言,特別是 Haskell,對於深入理解其他語言在新版本中引入的特性是有很大幫助的。
比如,掌握了模式匹配、匿名函數等概念,在寫 Python 的時候就會發現,新版本引入的模式匹配和匿名函數語法糖其實還是很有用的,合理使用可以提高代碼的表達能力和可讀性。
再比如,理解 Haskell 的高階函數以及應用場景,會發現可以在 JavaScript 等很多語言用高階函數簡化層層嵌套的循環結構。
許多主流語言在近十年間積極吸收函數式語言的發展成果,這使得學習函數式語言,客觀上也達到了學習其他語言創新成果的目的。而且,由於 Haskell 的純粹性和體系性,這些函數式風格的威力和精妙之處,往往在 Haskell 編碼實踐中才能得到最深刻的體會。
總而言之,即便你以極其功利的目的,學習 Haskell 也不虧。從來沒有浪費的學習。
1.2 有助於獲得全新的抽象思維
其次,由於函數式風格和傳統面向對象的差異很大,使用的抽象思維方式也大相徑庭。學習 Haskell 可以讓我們在面臨同樣的工程需求時用截然不同的抽象視角來完成工作。
面向對象需要工程師思考類之間的職責劃分、繼承層次和對象交互;而函數式編程則依賴代數數據結構的設計、不同函數的組合以及副作用的嚴格隔離等。
在一些工程中的重要問題上,Haskell 的函數式特性也帶來了和其他語言間的巨大差距。例如,全局不可變性和無副作用函數徹底規避了共享狀態競爭,設計併發程序兼具簡潔性和工業級效率;基於代數數據類型和 Monad 抽象的 Maybe/Either 等類型,將錯誤處理編碼進類型系統,通過嚴格的編譯期檢查覆蓋所有分支,避免了許多運行時崩潰。
顯然,二者所需的工程思維和架構方法是完全不同的,對於同一個現實問題,掌握函數式編程的人就可以使用一種風格迥異、許多時候表達能力更強的方式將問題轉化成邏輯和代碼語言。
1.3 堅若磐石的穩定性
進一步來説,我覺得即便你不喜歡上述所有提到的特性,有一點是必須説明的:Haskell 通過嚴格的類型系統使得所有能編譯運行的代碼有相當的質量保障。
Haskell 的嚴格編譯期檢查,有一些人可能不太喜歡,會覺得編譯期檢查影響了編寫代碼的速度。
實際上,這些檢查的邏輯在於把很多運行時測試才可能發現的問題提前到編譯期一起解決掉,確保可編譯代碼的質量,極大提升了軟件上線後的穩定性和可維護性。
實際上,如果我們充分對比動態語言軟件和 Haskell 在測試、維護、重構與拓展等多階段的情況,我們很可能發現後者的嚴格檢查雖然在編譯期需要更多時間處理,但從全局來看反而是提高了效率的。
當然,Haskell 社區內流傳的傳説“編譯通過即正確”固然是誇張的説法,畢竟編譯期無法驗證軟件業務邏輯是否正確,該有單元測試、集成測試一樣都不能少。然而,編譯通過意味着代碼排除了所有編譯期涵蓋的錯誤類別(如類型不匹配、分支覆蓋不完備、副作用處理不當等)。這種編譯期給開發者帶來的安全感和對核心正確性的信心,是其他語言無法比擬的。
從這個角度而言,我個人覺得唯一可以和 Haskell 實現類似“把錯誤提早到編譯期”理念的語言就是 Rust。不過 Rust 為了實現底層內存操作,引入了所有權、生命週期等概念。如果你覺得某個項目不追求手動控制內存帶來的極致性能,可以接受由 GC 來回收內存垃圾帶來的便利性和開銷,那麼 Haskell 是在更高抽象層次上踐行把問題提前解決的極佳選擇。
1.4 極高的 AI 驅動編程契合度
順着上一點往下説,我一直覺得強調嚴格編譯期檢查的語言,特別適合 AI 輔助編程,乃至完全由 AI 智能體驅動的開發。
原因很簡單:編譯期錯誤信息通常是確定性的、局部的,並且直接指向代碼中違反語言規則的具體位置。大語言模型可以相對容易地通過上下文和詳細報錯信息,嘗試推斷問題根源並修復錯誤。
對於 Haskell 和 Rust 而言,如果代碼有問題,可以直接向大語言模型提供詳盡的編譯器報錯信息,讓 AI 根據報錯和上下文自主排查錯誤根源,排除類型錯誤。
反之,如果是 Python 的代碼,可能有些時候看似正確的代碼其實是有問題的,只有進入運行時才能夠被發現。運行時排查問題,除非這個軟件本身就有完善的日誌系統可以清晰向 AI 提供相關信息和問題復現條件,很多時候解決問題還得靠人類開發者。
當然,這不是説 Haskell 生成的代碼就一定比 Python 好。實際上,由於 Python 訓練語料更多,一般而言 Python 代碼的生成質量是不錯的。
但即便 Python 的代碼是正確的,我在使用前往往需要大量測試才敢用;對於 Haskell,AI 如果能生成可編譯的代碼並且人工複核業務邏輯無誤,那麼代碼的基礎健壯性就有了一定的保證,顯著降低 AI 生成代碼引入隱蔽運行時錯誤的可能性。
2. 核心概念一覽表 (中英對照)
學習任何具備一定體系性的知識,首先都需要把“要學什麼”的路徑圖規劃清楚。
Haskell 作為一種語法設計高度統一的語言,把握住正確的學習方向和路徑就尤為重要。而把學習方向的第一步,就是要對核心概念有一個基本的印象。
由於 Haskell 中文社區很多大佬的文章往往會混用一些中文表達的概念和英文表達的概念,我覺得有必要提供一個簡單的中英文概念對照。很多時候讓你一頭霧水的某個概念,可能換一個語言環境解釋就清楚了。
這裏,我提供一箇中英文對照的概念表,以及一個非常簡略的介紹。我認為初學階段需要掌握的概念包括:
| 中文概念 | 英文概念 | 説明 |
|---|---|---|
| 函數定義 | Function Definition | 使用 = 定義函數、參數、返回值 |
| 基本類型 | Basic Types | Int, Char, Bool, String/List/Tuple 等 |
| 模式匹配 | Pattern Matching | 可以代替大量嵌套 if...else... 結構,舒適定義代碼的控制流 |
| 匿名函數 | Anonymous Function | \x->x + 1,不需要命名就可以使用的函數 |
| 高階函數 | Higher-Order Function | 接受函數作為參數,或者返回函數的函數,如 map/filter/foldl |
| 柯里化 | Currying | 所有函數本質上只有一個參數,(a,b)->c 等價於 a->b->c |
| 部分應用 | Partial Application | 只提供函數部分參數,與柯里化高度相關 |
| 代數數據類型 | Algebraic Data Type (ADT) | data 關鍵字定義類型,可以是枚舉、結構體或它們的組合 |
| Maybe 類型 | Maybe Type | Maybe a = Just a|Nothing |
| 類型類 | Typeclass | 類似接口,定義一組函數,類型通過實現函數來聲明支持該行為 |
| 惰性求值 | Lazy Evaluation | Haskell 一大語法特點 |
| 函子 | Functor | 可被映射的數據容器或上下文 |
| 應用函子 | Applicative | 一種承載了函數的容器,可被應用於 Funtor |
| 單子 | Monad | 代表順序計算鏈,用於處理副作用、可選值、錯誤等上下文中的計算 |
| do 語法 | do Notation | 好用的編寫 Monad 計算的語法糖 |
這個列表大體上遵循了從上到下由易到難,如果你還沒有開始學 Haskell,我覺得完全可以按圖索驥一步一步掌握語言的核心思想。
掌握這個列表中的概念,是學習 Haskell 語言和閲讀社區代碼的關鍵基礎。當然,實際項目中還需要理解更多細節和工具鏈的使用,但把握住這些概念就具備了構建複雜軟件的基本能力。
3. Haskell 怎麼學
我覺得 Haskell 學習過程中最重要的一點,就是多找教程交叉比對。
我之前專門寫過一個回答論證“你學不明白不見得是你的問題,可能是沒找對教程的問題”,我覺得理解這一點在學習 Haskell 中十分重要。
Haskell 語言很多高級抽象工具,像是 Monad,想要單憑一篇文章講明白其實是很困難的,但如果你讀了二三十篇技術博客談這一個概念,即便仍然不能完全理解,起碼多篇不同側重點的文章建立起對一個概念的立體理解。
總而言之,找到適合自己理解的教程是相當重要的,而唯一能確保這一點的就是先瀏覽大量材料,再從中挑選契合的文章重點精研。
此外,我還想説一點就是實踐優先,在做中學。
Haskell 本身的語言設計可能有不少學術成分,網絡上一些人也有論及 Haskell 必談抽象代數、範疇論的習慣。
我不是説理解背後的深層次數學思想無用,只是如果你對這些數學工具瞭解不多,大可不必被嚇到。實際上,Haskell 選擇這些語言特性更多是出於實踐因素的考量,只有不斷在項目中實踐,才能夠真正理解深層次的設計理念。
須知,不必深入理解數學思想,也可以高效運用 Haskell 解決問題。理解底層數學原理有助於揣摩設計動機,但絕不是使用 Haskell 的門檻。
最後,如果你對這個語言感興趣,歡迎關注我,我將在未來的一段時間持續講解編寫 Haskell 的心得體會和實踐經驗。