Haskell學習心得
話說程序員要每年學一門編程語言.2020年,目標Haskell.
特性
- 柯里化
Haskell的函數,只有單參數函數.它的多參數函數,其實只是返回了另一個函數.
所以在進行部分參數應用的時候,會非常的自然. - 運算符
所有的運算符也是函數,和函數不同的是,運算符的結合性和優先級可以自定義.函數的結合性和優先級是固定的. - 強類型和類型推導
定義函數時,可以聲明類型,也可以不聲明類型.如果不聲明類型,Haskell會根據上下文推導出來類型.它的自動推導功能十分強大. - 函數調用不需要括號,參數之間用空格分隔
和其他語言用括號和逗號的風格不同,Haskell裏面調用函數通過空格分隔函數名和參數,也通過空格分隔多個參數.配合柯里化,函數應用符$,可以實現另人驚豔的效果,比如其他語言中f(g(h(x)))這樣的調用,可以寫成f $ g $ h x,將代碼的嵌套風格變成了扁平的風格. - 支持模式匹配
十分方便解構數據,判斷類型,代碼寫起來也非常優雅. - 支持lambda表達式
- 不可變變量
變量一旦定義不可重新賦值. - 前綴運算符,中綴運算符,後綴運算符,混合位置運算符
前綴運算符用中綴形式調用.中綴運算符可以使用前綴形式調用.
if then else是一個混合位置運算符 - 惰性求值
在真正需要的時候才進行計算,很容易定義無限列表. - 隔離純函數和非純函數
Haskell中嚴格區分純與不純函數.一旦一個函數變成不純函數,那麼它的調用者也是不純函數.從純函數取結果和從不純函數取結果的方式不一樣. - 函數調用符$
這個可以實現f(g(x))寫成f $ g x這樣的效果. - 管道運算符|>
可以實現參數寫在前面,函數寫在後面的效果.
比如text |> lines |> map (++"!!!")的語義爲,將文本text按換行符分隔,得到一個列表,然後列表中每個字符串後面拼接3個感嘆號. - 類型,類型類,kind
如果拿java作比喻的話,Haskell中的類型(type)類似於java的class
Haskell中的類型類(typeclass)類似於java的interface(可以定義默認實現的那種)
Haskell中的kind,就是類型的類別(好吧,不理解沒關係,就是個概念而已). - 類型構造器和數據構造器
Haskell中區分類型構造器和數據構造器.
在java中構造器必須和類型名稱相同.Haskell中一個類型可以有多個數據構造器. - 代碼結構
Haskell由多個模塊組成.一個模塊由模塊導入,模塊和導出聲明,類型定義,函數定義,運算符定義,變量定義組成.函數由函數聲明和函數體組成.函數體由表達式組成.Haskell中沒有語句.最後一個表達式的值就是函數的返回值. - 列表生成器
類似於python中的列表生成器,python應該是借鑑Haskell這類語言的. - 容器與盒子
雖然沒有明確的定義,但是一般我們把參數化類型當成容器.把參數化類型構造出的類型的值當做盒子. - Functor,Applicative,Monad
這些類型類,定義了相關的處理盒子的函數.因爲非純函數操作,異常處理操作等,Haskell會將它們的值裝進盒子,定義一系列操作盒子的函數,可以方便調用非純函數.
心得
- 思維方式的改變.命令式編程,我們告訴程序問題如何解決.函數式編程我們告訴程序問題是什麼.
- 函數參數都是基於位置的,不支持命名參數和默認參數.這對代碼的可讀性是一個非常大的挑戰.從一個函數的聲明中,我們只能看到類型信息.
一門語言的函數調用形式如何設計,其實是需要很講究的.柯里化形式有它的優勢(簡潔,可扁平化,可實現管道風格-相當於oop裏面的鏈式調用吧,易實現DSL),也有它的劣勢(需要記住各位置填什麼參數.想想用shell命令時經常弄錯參數位置的情形吧).從工程角度看,基於位置的方式可讀性會差很多. - Haskell中各種運算符,比如
>>=,>>>,>>,<=<,=<<,<$>,<*>,*>,|>,<|>,<*,
,它們的語義太豐富,晦澀難懂.讀源代碼的時候,大腦需要處理太多信息.如果再加點料,配上一些高階函數,那"酸爽",誰用誰知道.
如果程序的簡潔性需要以可讀性爲代價,那麼我寧願不那麼簡潔. - 副作用的處理.Haskell中強制隔離pure操作和IO操作.這個思想非常好,用於java中也獲益匪淺.可以大大提高代碼的易測試性,降低耦合性.
- 異常處理.有一種異常處理思想深得我心,let it crash.這種思想認爲,程序出異常了,大部分情況下我們並不需要在方法裏面捕獲它,我們只需要不管它,讓它一直往上拋即可.Haskell喜歡用Maybe,Either來表示異常.我對這種不能打印異常棧的處理方式深表懷疑.
- 命名空間.在java中,不推薦import *.當然,haskell裏面也可以import SomeModule (f1,f2)這樣解決.但是,不同模塊的函數名衝突了,就需要起別名了.
- 多行字符串.貌似沒有看到關於Haskell中處理多行字符串的方案.
- 惰性求值與棧溢出.容易寫出看上去沒啥問題,實際上會棧溢出的代碼.
- Haskell中有專門用於寫編譯器的庫.嗯…這個用來創造自己的編程語言,真的太棒了.
- 有些經典算法的描述,是通過命令式方式描述的.要用haskell實現,可能要費一些腦細胞.
- 暫時想到這麼多了,打算複習並整理一遍數據結構和算法.用haskell實現,體驗一下.如果感覺還不錯,後面我就是haskell粉了.