Haskell 有什麼缺陷?

Haskell (GHC)在設計和實現上有什麼缺陷?會如何影響我們的使用?

雖然還有社區,學習曲線,配套設施等等方面的問題,但是希望答案能集中在語言(GHC)本身的設計和實現上


Monomorphism Restriction不能算是缺陷,現在GHC好像也默認了NoMonomorphismRestriction。這種問題本來就是怕引起特別多的重複計算,而把類型限定到單一類型上,但是這種重複計算出現的可能並不是特別多。

Prelude&> let foo x = let plus = (+1) in (plus 2, plus 3)
Prelude&> :t foo
foo :: (Num t2, Num t1) =&> t -&> (t1, t2)

GHC又不知道你想讓後面的是一種類型這是兩種。Haskll98默認是沒有這個選項了。引起這個問題的唯一原因就是Haskell已經把類型研究到極致了,你在其他語言(非函數式)里絕對見不到這樣的細節,因為其他語言的類型要明確標註,或者也是做了單態限定,要不4.4 + 3不把4轉為double怎麼計算?GHC單一同態限定恰恰把其他編譯器認為「理所當然」的過程暴露給你,讓你提供選擇。如果你把類型上下文全寫清楚來就不會有這樣的問題。

Prelude&> :set -XMonomorphismRestriction
Prelude&> let foo x = let plus = (+1) in (plus 2, plus 3)
Prelude&> :t foo
foo :: Num t1 =&> t -&> (t1, t1)

這個問題是很subtle,是你不想給足夠的類型上下文導致的,相反我覺得這是Haskell類型系統設計的極為細緻一個體現。

1、Haskell本身採用ADT使得類型擴展比較困難。用Scala的話說就是Haskell里所有的data都是sealed,這對擴展性非常不利。不過已經有論文出現要解決這個問題了,因為GHC的設計者們發現解決這個問題跟解決類型上的open type family是何其相似的。

2、多參數的類型類重合問題

class C a b
instance C Int a
instance C a Int

問題來了,如果是C Int Int怎麼辦,Haskell有擴展任意選一個,我們無法參與,目前的GHC也不會報錯,好像連Warning都沒有,但是已經改進了,未來不知道會不會讓程序員指定匹配的優先順序。

3、Haskell並沒有被設計成依賴類型的語言(dependent type),但是卻想做跟那些語言相同的事,結果就是有時會搞得異常複雜。

{-# LANGUAGE KindSignatures,GADTs,TypeOperators,DataKinds #-}
import GHC.TypeLits

data Vec a (n :: Nat) where
Nil :: Vec a 0
Cons :: a -&> Vec a n -&> Vec a (n + 1)

vhead :: (1 &<= n) =&> Vec a n -&> a
vhead (Cons a v) = a

vtail :: Vec a (n + 1) -&> Vec a n
vtail (Cons x xs) = xs

你會得到下邊的錯誤,並且為了搞好這個問題你需要再同一大堆東西!這一大堆東西就是類型層面的證明,讓GHC相信,你寫的程序是可以deduce的,這實在是太讓人無力了。

Could not deduce (n1 ~ n)
from the context ((n + 1) ~ (n1 + 1))

意思是說,從n1 = n 無法推出n + 1 = n1 + 1。學過定理證明的都懂,這是很好證明的,GHC目前用這裡的自然數無法自動證明這個,但目前GHC也在努力。而對於Agda

data Vec (A : Set) : Nat -&> Set where
vnil : Vec A zero
vcons : (n : Nat) &> A -&> Vec A n -&> Vec A (suc n)

tail : ? {A n} → Vec A (suc n) → Vec A n
tail (_ ∷ xs) = xs

一切都是這麼稀鬆,不得不說Agda的設計可能更加趨於合理。未來的GHC可能會有解方程的機制,用SMT可以實現這個,不過目前還不行。

4、GHC的類型系統設計value與type沒有很好地整合在一起,雖然有DataKinds我想這多半是為的使用者考慮。如果設計得好將不會出現下邊的代碼:

type family EqBool a b where
EqBool True True = True
EqBool False False = True
EqBool a b = False

type family EqOrdering a b where
EqOrdering LT LT = True
EqOrdering EQ EQ = True
EqOrdering GT GT = True
EqOrdering a b = False

type family EqList a b where
EqList [] [] = True
EqList (h1 : t1) (h2 : t2) = (h1 == h2) (t1 == t2)
EqList a b = False

type family EqMaybe a b where
EqMaybe Nothing Nothing = True
EqMaybe (Just x) (Just y) = x == y
EqMaybe a b = False

這些代碼很傻地重複了值上的運算,並且在類型層面的無法使用類型類限定。我覺得偉大的GHC應該能避免寫這些東西,好的,有一個庫叫singleton在搞這個,我並不熟悉了,是一個賓夕法尼亞大學的博士在做。但是我並沒有系統地了解過DP,並不知道會有什麼樣的用處。

目前在我看來,Haskell語言日臻完美,GHC的bug主要集中在後端,而不是語法上,package主要就是一些支持的不完善的細節問題,就語言來說,可能Agda要更好,但是作為工程語言總是要有折衷的(Haskell的確是工程語言)。期待InjectiveTypeFamily(竊以為用這個可以解決ADT擴展的問題)還有OverloadedRecordField等一堆擴展。

如有問題歡迎討論


謝邀

我能理解的有Monomorphism restriction, 其他的我理解不了

默認non-strict是不是缺陷就見仁見智了, 這點的爭論也不少


cabal hell 太能折騰人了


GHC的運行時實現部分太噁心了,特別是:ghc/Linker.c at master · ghc/ghc · GitHub,手工把所有需要的C函數列了一坨,硬編碼鏈接進去。然後每次GHC、gcc、mingw版本變動,都要給這個破文件打一大坨補丁。也是為啥win32版ghc要自帶mingw,win32版ghc的bug之源。。。。


缺陷就是學起來太難。


最大的麻煩還是laziness導致的space leak吧,尤其是full laziness transform也可能會產生space leak,比如 https://www.reddit.com/r/haskell/comments/55xk4z/erratum_to_sharing_memory_leaks_and_conduit_and/。


缺陷其實挺多的。

Surprises of the Haskell module system (part 1)


初學者 length返回的是int 不是num 好麻煩、

fromIntegral (length [1,2,3,4])


同意學習太難的觀點,而且它的學習難是有自己的特點。

首先是概念太多,有時一份最簡單範例代碼,開頭各種擴展先引入7、8個。要想寫出生產型的代碼,Haskell可能是你需要掌握的概念最多的一門語言。

其次就是和流行的語言差異太大,其它語言裡面哪有聽說「類型」的類型也這麼多?


推薦閱讀:

EDSL相關雜記(2)
SKI組合子演算入門
除回溯外,有哪些比較好用且效率高的解數獨演算法?
Kotlin 版圖解 Functor、Applicative 與 Monad
為什麼這段 Haskell 代碼比 C 慢那麼多?

TAG:函數式編程 | Haskell |