標籤:

如何評價最新推出的 Glasgow Haskell Compiler (GHC) 8.0.1 版本?

從 Blog: GHC 8.0.1 is available! a€「 GHC 上面看到,新版本提供了如下激動人心的功能:

- 更好的 implicit call stack 支持,終於可以有能看的 stack trace 了

- DuplicateRecordFields 語言擴展,終於不怕 records 撞名了

- 大幅提升了錯誤信息的可讀性以及內容。庫作者可以自定義錯誤信息了,脆弱的 rewrite rule 也會有更多的提示信息,等等

- 重寫 GADT 的 pattern match checker 以提供更加準確的檢查

- 以及更為重要的,TypeInType 語言擴展

只是希望不要我在吭哧吭哧的 precompile 好了 stackage 上一大坨 library 的最新版本之後,第二天說 GHC 又有 bug fix 就好...


謝邀, 關注Haskell cutting edge發展的人在知乎好像不多了, 只好我來做個搬運.

從各種跳票到RC歷經半年多的時間, 社區期待已久的GHC 8.0 終於發布.

8.0是個大版本號的變化, 同時也開始了Haskell後面各種大的變動.

填了部分的坑(也就是沒填完), 為很多常見的寫法提供了便利, 提高語言實現的可觀察性, 後面還有很長的路要走(Haskell 2020 GHC 8.8), 具體可以看Haskell Communities and Activities Report

(坑) ----------------------下面是想寫又覺得好累沒寫完的-----------------

分幾個部分來看這次變動:

解決語言使用痛點

Haskell Record的使用一直讓人詬病的一點是不能有field的名字不能重複[0], 因為Record會導出一個跟field同名的selector, 會在在同一個namespace下面產生衝突 [0], 8.0提供了DuplicateRecordFields來解決這個問題, 然而這只是OverloadedRecordFields [1]的part1

ApplicavtiveDo

由Proposal提出到實現花了5年, 期間Simon Marlow在Fackbook搞了Haxl, 用Template Haskell實現了個半吊子的. 看到的說法是當年最早發現的是Monad, 還沒意識到Applicative也是這麼有用的, GHC 7.10的AMP到8.0的ApplicativeDo, Applicative算是完全得到實現支持.

語義

StrictData和unbox-strict-fields這兩個extension, 代碼里不需要滿眼的!(Bang Pattern)跟{-# UNPACK #-}了, 控制代碼里strict跟lazy算是Haskell優化的部分之一, 這兩個extension也算是提供了使用的便利而已

類型系統(這方面我不懂)

Haskell本來不是一個Dependently Typed language, 近年的發展傾向要把Haskell變成Dependent type的. Type Level Programing的需求也是與日俱增(看向Servant),

Observability

這個詞我是從這個大哥http://www.cl.cam.ac.uk/~srk31/ 那裡學來的, 用來描述語言實現的出錯信息跟debug方便性很實在, 也是近年來編程語言的改進方向, CustomTypeError跟DWARF支持算是GHC往這方面的努力, 畢竟新手看到不會丈二和尚, 雖然老手已經形成條件反射 ( , BTW, DWARF支持還有些坑會在8.2填

[0] https://ghc.haskell.org/trac/ghc/wiki/Records

[1] https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields


我只能說GHC 8.0的更新感人!

1、關於Haskell惰性求值的問題可以休矣!對於函數fact

fact :: Int -&> Int -&> Int
fact 0 n = n
fact n acc = fact (n-1) (n * acc)

如果不用Strict擴展

Rec {
-- RHS size: {terms: 20, types: 7, coercions: 0}
fact [Occ=LoopBreaker] :: Int -&> Int -&> Int
[GblId, Arity=2, Str=DmdType]
fact =
(ds_d2pw :: Int) (n_a27A :: Int) -&>
case ds_d2pw of wild_Xb { GHC.Types.I# ds1_d2py -&>
case ds1_d2py of _ [Occ=Dead] {
__DEFAULT -&>
fact
(- @ Int GHC.Num.$fNumInt wild_Xb (GHC.Types.I# 1#))
(* @ Int GHC.Num.$fNumInt wild_Xb n_a27A); -- 直接引用
0# -&> n_a27A -- 直接返回
}
}
end Rec }

可以看到n_a27A沒有被eval,而是直接被引用,這樣只有在返回時才會被計算。

Rec {
-- RHS size: {terms: 29, types: 13, coercions: 0}
fact [Occ=LoopBreaker] :: Int -&> Int -&> Int
[GblId, Arity=2, Str=DmdType]
fact =
(ds_d2qu :: Int) (n_a2pY :: Int) -&>
case ds_d2qu of ds1_X2qz { GHC.Types.I# ipv_s2qE -&>
case ds1_X2qz of wild_Xf { GHC.Types.I# ds2_d2qw -&>
case ds2_d2qw of _ [Occ=Dead] {
__DEFAULT -&>
case n_a2pY of n1_X2q5 { GHC.Types.I# ipv1_s2qJ -&> -- 被case求值了。
fact
(- @ Int GHC.Num.$fNumInt wild_Xf (GHC.Types.I# 1#))
(* @ Int GHC.Num.$fNumInt wild_Xf n1_X2q5)
};
0# -&> case n_a2pY of n1_X2q7 { GHC.Types.I# ipv1_s2qM -&> n1_X2q7 }
}
}
}
end Rec }

而對於同一個參數n_a2pY在__DEFAULT 被case了,所以就被求值了。雖然我還是比較喜歡自己控制求值的策略,但是以後再有一知半解的半吊子噴Haskell因為惰性求值效率低可以直接打他們的臉了。

2、ApplicativeDo

經過了好久,do的語法終於用把Functor、Applicative、Monad分開了。

&> :set -XApplicativeDo
&> :t m -&> do { x &<- m "a"; y &<- m "b"; return (x || y) } m -&> do { x &<- m "a"; y &<- m "b"; return (x || y) } :: Applicative f =&> (Char -&> f Bool) -&> f Bool

不需要再使用QuasiQuotes applicative-quoters了。所以TH與QQ是GHC改進的一個指引,很多都在這裡預實現,然後經過驗證再加入到GHC中。在GHC 7.10之前會直接推斷為Monad類型類。

3、TypeApplications可以加一個@跟類型來傳入類型信息。

&> :t id @Int
id @Int :: Int -&> Int

&> read @Int "2016"
2016

之前的做法一定要手工指定,或者使用::

&> :set -XScopedTypeVariables
&> :t (a :: Int) -&> id a
(a :: Int) -&> id a :: Int -&> Int

DuplicateRecordFields

{-# LANGUAGE DuplicateRecordFields #-}
{-# OPTIONS_GHC -ddump-ds #-}
data Person = P {name :: String, age :: Int}
data Company = C {name :: String, ceo :: Person}

再也不怕名字不夠了,之前我一般會寫成personName等等,但是現在不用了,所以生成ToJSON與FromJSON應該不再需要使用TH來指定去掉前綴了。

OverloadedLabels可以方便地訪問field name,這是OverloadedRecordFieldName擴展的一個折衷,未來會有更完整的實現

{-# LANGUAGE DataKinds, KindSignatures, MultiParamTypeClasses,
FunctionalDependencies, FlexibleInstances,
OverloadedLabels, ScopedTypeVariables #-}

import GHC.OverloadedLabels (IsLabel(..))
import GHC.TypeLits (Symbol)

data Label (l :: Symbol) = Get

class Has a l b | a l -&> b where
from :: a -&> Label l -&> b

data Point = Point Int Int deriving Show

data Point3D = Point3D Int Int Int deriving Show

instance Has Point "x" Int where from (Point x _) _ = x
instance Has Point "y" Int where from (Point _ y) _ = y

instance Has Point3D "y" Int where from (Point3D _ y _) _ = y
instance Has Point3D "x" Int where from (Point3D x _ _) _ = x
instance Has Point3D "z" Int where from (Point3D x _ z) _ = z

instance Has a l b =&> IsLabel l (a -&> b) where
fromLabel _ x = from x (Get :: Label l)

example2 = #x (Point3D 1 2 3)
example = #x (Point 1 2)

用Has以及類型層面的運算就可以解決 @邵成 在第二屆函數式編程分享會上提出的擴展對象名欄位的問題,之前我也與 @parker liu 討論過,以後應該會有更漂亮的解決。

UndecidableSuperClasses

關於這個擴展,可以看https://www.youtube.com/watch?v=ZL9ehIJhk98,(要翻牆,你懂的)已經比較難以解釋了,簡單地說就是幾個Class的上下文互相之間有依賴,這些依賴是類型函數的計算,如果計算能停止就會有一個不動點,所以這樣搞是可行的。估計category好多東西要重寫了。

TypeFamilyDependencies,這個跟FunctionalDependencies相似,但是是在type family上的。

類型上運算的內射性(injectivity)是非常重要的更新,如果沒有這個,好多函數的類型沒有辦法通過類型檢查,使得在做一些工作的時候十分掣肘。具體可以看http://ics.p.lodz.pl/~stolarek/_media/pl:research:stolarek_peyton-jones_eisenberg_injectivity_extended.pdf以及視頻https://www.youtube.com/watch?v=s0wkCKZU3WI (需要翻牆)

TypeInType統一了kind與type

Prelude&> :m +Data.Kind
Prelude Data.Kind&> :set -XTypeInType
Prelude Data.Kind&> type A i = i -&> i -&> *
Prelude Data.Kind&> :i A
type A i = i -&> i -&> * -- Defined at &:3:1
Prelude Data.Kind&> :k *
* :: *
Prelude Data.Kind&> type B = (* :: (* :: (* :: *)))
Prelude Data.Kind&> type C = (*,*) -- Yes! @parker liu
Prelude Data.Kind&> type D = (*,*) -&> * -- 如果你懂什麼是CxC的話,就能理解表達這玩意的意思了。在等EK大神玩出新花樣。

寫這個kind type別名,引入了higher-rank kinds。TypeInType可以在更一般地表達範疇論中的概念才是重要的,因為這些概念又能大量地化簡代碼,正如 @parker liu的答案中說的那樣。雖然一直這樣抽象並不一定見得是好事,但是懂你就用,不懂就不用,也沒誰逼你,只是你寫代碼費點人力而已。


謝邀,前面 @閱千人而惜知己和 @祖與占已經寫的比較詳細了,我就說說自己的感受吧。

從去年9、10月間就知道要出GHC 8.0了,一直象追星一樣(我是從不追星的)追到現在,終於看到GHC 8.0出來了,心情非常愉快。

GHC 8.0是Haskell走向實用的一個重要里程碑,增加了很多在實際工程中需要的特性,特別是在debug和compile的參數及錯誤報告方面。另外Strict的加入使得程序員可以很方便的控制lazy的使用,arm、ppc、m68k等平台的更完善的支持,windows系統上更加穩定,支持AIX系統(這是眾多銀行等大型企業使用較多的系統)。這些都使得Haskell在實際工程環境的應用上邁出了堅實的一大步,GHC 8.0及其後續版本的改進,將會在這方面越做越好。

不過我更期待的是能儘快推出iOS版本的64位ghc,現在能用的是GHC 7.8.3,還是用llvm3.0作後端的32位版本。

下面大致說一下新增的特性吧:

1. CallStack和Debug信息

這絕對是調試和找bug的利器。有了CallStack,可以很快定位到問題點,而且GHC的CallStack列印信息是比較完整的,和Java的一樣,比C/C++好。C/C++在經過O2以上的優化後的CallStack信息的最後出錯的地方要找到對應的源代碼是比較麻煩的,我一般是通過反彙編的文件來查找的。GHC 8.0的Debug信息是DWARF格式的,這就可以使用其它成熟的調試器來debug了,使得工具鏈更加完善。

2. 多平台和系統的支持

arm、ppc、m68k是嵌入式系統使用較多的平台,Haskell要在嵌入式系統、移動終端設備(手機等)上發展,就必須支持好這幾個平台。在windows上更加穩定,使得可以更好的在windows上開發商業應用,增加了可以應用的主流操作系統。AIX系統的支持,使得Haskell在大型企業服務系統上可以有立足之地。這些都大大擴展了Haskell的生態鏈和應用環境。

3. Compile方面

GHC 8.0支持使用llvm3.7作為後端的代碼生成,可以得到更高質量和效率的目標代碼,arm的代碼生成則是完全使用llvm3.7來完成的。編譯器前端方面增加了類似gcc的-W選項來控制warning的打開和關閉,增加了-dth-dec-file用於dump編譯Template Haskell生成的中間文件,增加了-fprint-expanded-types用於列印類型別名的信息。編譯器的使用更加符合gcc等其它傳統編譯器的習慣,更多友好的編譯期錯誤輸出。

4. Applicative和Monad

繼GHC 7.10明確Applicative和Monad的繼承關係後,在使用上更好的支持了這一繼承關係。增加了Applicative Do用於Applicative的表達式,統一了Applicative和Monad的表現形式,不過這種Applicative Do的形式沒有使用&<$&>加&<*&>的方式直觀。filterM、zipWithM和replicateM等函數都泛化到了Applicative。

5. Dependent Type的支持

GHC8.0中支持了TypeInType,這是支持Dependent Type的第一步。TypeInType消除了kind和type之間的區別,確保了Haskell程序的type層次上的一致性。支持了higher-rank kinds、kind families和type-level GADT等特性。Dependent Type的支持使得Haskell有了更好的類型系統支持,使得我們可以寫出更嚴謹和正確的代碼,可以應對更複雜的問題。但這也讓使用Haskell有了更高的門檻。但更重要的是我們可以更好的將範疇論和代數方面的東西應用到編程上,使得編程離數學更近了。

6. 其它的語言特性支持

injective type families、DuplicateRecordFields和OverloadedLabels等特性的支持,pattern synonyms可以和類型構造子綁定,各種GHC擴展實現的改進。這些改進修正了原先實現的一些問題,可以更方便的寫出更好的模塊化的代碼。

Haskeller們,準備好迎接這些和後續越來越多的變化了嗎?

舊日時光漸漸遠去,變革已然臨近。

黎明即將到來,曙光就在前方。

參考鏈接:

Blog: GHC 8.0.1 is available! a€「 GHC

3. Release notes for version 8.0.1


最大的變動是依賴類型方面的兩個擴展

01 類型可以直接做參數,不再需要使用原來的proxy擴展

02 把type和kind統一了起來

這是依賴類型的第一步,將來會逐漸完善其他部分

以後haskell程序真的可以玩出花了,所以學haskell要趁早 ( ????? )

……

其他大部分都是一些語法,出錯和調試信息,移植性方面的改進。

總得來說ghc 8新語義方面驚喜很大但點不是很多,主要改進在實用性上


  • ApplicativeDo

就 AppDo 來說,我還是比較偏好 pure 然後 ap 的用法,說實話因為 Applicative 自身的限制,最後一行一定要 return ( expr ) 寫括弧不能用 dollar sign 讓我非常不爽,非要用的話,還是更加偏好 TH 版本的寫法,雖然和 Monad 無法兼容。

  • Strict

這個真是淚流滿面,寫並發相關的東西的時候不用一堆 bang 了

目前看來就這麼多, stack 把我養懶了都忘了怎麼裝 cabal 了。。。。。。嗯,我一定不是一個人


推薦閱讀:

haskell中的immutable array是如何實現隨機訪問的?
如何評價即將正式 release 的 GHC 8.2.1 ?
OCaml在寫編譯器上比Haskell好在哪?為何Rust第一個版本採用了OCaml?
如何評價eta-lang?
對大量使用 immutable data structure 的語言,其 VM 和 GC 會有何特點?

TAG:Haskell |