核心概念
本文旨在幫助熟悉 Haskell 的程序員快速了解 Clean 語言的元素以及與 Haskell 的差異,以便於閱讀和理解 Clean 代碼。
摘要
給 Haskell 程序員的 Clean 語言概覽
本文旨在為熟悉函數式程式語言 Haskell 的讀者提供 Clean 語言元素的簡明概述,並比較其與 Haskell 的異同。本文主要基於 Achten [2007] 的工作,但該工作基於 Clean 2.1 和 Haskell98。 顯然,本概述並不詳盡,有關 Clean 語言的完整規範,請參閱最新的語言報告 [Plasmeijer et al., 2021]。 主要目標是幫助讀者閱讀 Clean 代碼。 表1列出了左側經常出現的 Clean 語言元素及其右側的 Haskell 等效項。
模組
Clean 模組具有獨立的定義(標頭)和實現文件。 定義模組包含類別定義、實例、函數類型和類型定義(可能是抽象的)。 實現模組包含函數實現。 這意味著在 Clean 中只導出定義模組中定義的內容。 這與 Haskell 有很大不同,因為那裡只有一個模組文件。 在 Haskell 中選擇導出什麼是使用模組 Mod(···) 語法完成的。
嚴格性
在 Clean 中,默認情況下,所有表達式都延遲求值。 可以使用嚴格性屬性 (!) 對類型進行註釋,從而導致在輸入函數之前將值評估為頭部範式。 在 Haskell 中,在模式中,可以使用 !3 強制執行嚴格性。 在函數中,可以使用嚴格 let (#!) 來強制評估表達式,在 Haskell 中,seq 或 $! 用於此目的。
唯一性類型
Clean 中的類型可能是唯一的,這意味著它們可能不會被共享 [Barendsen and Smetsers, 1996]。 唯一性類型系統允許編譯器生成高效的代碼,因為唯一的數據結構可以被破壞性地更新。 此外,唯一性類型也可用作副作用的模型。 Clean 使用世界即值範式,其中 World 代表外部環境並且始終是唯一的。 具有副作用的程序的特徵是 Start :: *World *World start 函數。 在 Haskell 中,與世界的交互是使用 IO 單子完成的。 IO 單子可以很好地使用 World 作為狀態的狀態單子在 Clean 中實現,實際上也是如此。 除了將類型標記為唯一之外,還可以使用唯一性屬性變量 u: 標記它們並對其定義約束。 例如,確保函數的一個參數至少與另一個參數一樣唯一。 最後,使用 .(點),可以說明多個變量具有相同的唯一性。 唯一性在函數類型中自動傳播,但在數據類型中必須手動標記。 示例見清單 1。
f :: (Int, *World) *World // 唯一性會針對函數類型自動傳播(即 *(Int, *World)))
f :: *a *a
// f 僅適用於唯一值
f :: .a .a
// f 適用於唯一值和非唯一值
f :: v:a u:b u:b, [v<=u]
// 當 a 的唯一性低於 b 時,f 才有效
Listing (Clean) 1:Clean 中唯一性註釋的示例。
泛型
泛型函數 [Jeuring and Jansson, 1996](也稱為多態或類型索引函數)內置於 Clean Plasmeijer et al. [2021, Chp. 7.1][Alimarine, 2005] 中,而在 Haskell 中,它們作為庫實現 [Team, 2021, Chp. 6.19.1]。 Clean 中泛型的實現與 Generic H∀skell [Hinze and Jeuring, 2003] 的實現非常相似。可以使用 of 語法獲取有關類型的元數據,從而使函數可以訪問元數據記錄。 豐富的元數據允許非常複雜的泛型函數接近模板元編程的表達式級別。 清單 4 顯示了泛型相等的示例,清單 5 顯示了利用元數據的泛型打印函數的示例。
GADT
廣義代數數據類型 (GADT) 是豐富的數據類型,允許顯式定義構造函數的類型實例化 [Cheney and Hinze, 2003, Hinze, 2003]。 雖然 Clean 本身不支持 GADT,但可以使用嵌入投影對或等價類型來模擬它們 [Cheney and Hinze, 2002, Sec. 2.2]。 為了說明這一點,清單 2 和 3 分別顯示了在 Clean 和 Haskell4 中實現的示例 GADT。
Clean
Haskell
備註
// 單行
-- 單行
/* 多行 /* 嵌套 */ */
{- 多行 {- 嵌套 -} -}
import Mod0
import Mod0
import Mod1 ⇒qualified f, :: t
import Mod0 (f, t)
import qualified Mod1 (f, t)
import Mod1 hiding (f, t)
42 :: Int
42 :: Int
True :: Bool
True :: Bool
toInteger 42 :: Integer
42 :: Integer
38.0 :: Real
38.0 :: Float -- 或 Double
"Hello" +++ "World" :: String5
"Hello" ++ "World" :: String6
[' Hello '] :: [Char]
"Hello" :: String
?t
Maybe t
(?None, ?Just e)
(Nothing, Just e)
:: T a0 · · · :== t
type T a0 · · · = t
:: T a0 · · · = C0 f0 f1 · · · | C1 f0 f1 · · · | · · ·
data T a0 · · · = C0 f0 f1 · · · | C1 f0 f1 · · · | · · ·
:: T a0 · · · = { f0 :: t0, f1 :: t1, · · · }
data T a0 · · · = T { f0 :: t0, f1 :: t1, · · · }
:: T a0 · · · =: t
newtype T a0 · · · = t
:: T = E.t: Box t & C t
data T = forall t.C t ⇒Box t7
f0 :: a0 a1 · · · t | C0 v0 & C1, C2 v1
f0 :: (C0 v0, C1 v1, C2 v1) ⇒a0 a1 · · · t
(+) infixl 6 :: Int Int Int
infixl 6 +
(+) :: Int Int Int
qid :: (A.a: a a) (Bool, Int)
qid8 :: (forall a: a a) (Bool, Int)
qid id = (id True, id 42)
qid id = (id True, id 42)
class f a :: t
class F a where f :: t
class C a | C0, · · · , Cn a
class (C0 a, · · ·, Cn, a) ⇒C a
class C s ~m where · · ·
class C s m | m s where · · ·9
instance C t | C0 t & C1 t · · · where · · ·
instance (C0 a, C1 a, · · ·) ⇒C t where · · ·
x=:p10
x@p
[1,2,3]
[1,2,3]
[x:xs]
x:xs
[e \ e<-xs | p e]
[e | exs, p e]
[l \ l<-xs, r<-ys]
[l | lxs, rys]
[(l, r) \ l<-xs & r<-ys]
[(l, r) | (l, r)zip xs ys] or [(l, r) | lxs | rys]11
\a0 a1 · · ·e or \· · ·.e or \· · ·=e
\a0 a1 · · ·e
if p e0 e1
if p then e0 else e1
case e ofp0 e0 // or p0 = e0· · ·
case e ofp0 e0· · ·
f p0 p1 · · ·| c= t| otherwise = t // or = t
f p0 p1 · · ·| c= t| otherwise = t
:: R = { f :: t }
data R = R { f :: t }
r = { f = e }
r = R {e}
r.f
f r
r!f12
(\v(f v, v)) r
{r & f = e }
r { f = e }
:: R0 = { f0 :: R1 }
data R0 = R0 { f0 :: R1 }
:: R1 = { f1 :: t }
data R1 = R1 { f1 :: t }
g { f0 } = e f0
g (R0 {f0=x}) = e x or g (R0 {f0}) = e f013
g { f0 = {f1} } = e f1
g (R0 {f0=R1 {f1=x}}) = e x
:: A :== {t}
type A = Array Int t
a = {v0, v1, · · ·}
array (0, n+1) [(0, v0), (1, v1), · · ·, (n, · · ·)]
a = {e \ p <-: a}
array (0, length a-1) [e | (i, p) zip [0..] a]
a.[i]
a!i
a![i]14
(\v(v!i, v)) a
{ a & [i] = e}
a//[(i, e)]
f :: a Dynamic | TC a
f :: Typeable a ⇒a Dynamic
f e = dynamic e
f e = toDyn e
g :: Dynamic t
g :: Dynamic t
g (e :: t) = e0
g d = case fromDynamic d ofJust e e0Nothing e1
g e = e1
f p0# q0 = e0= e
f p0= e[x := x′]where q0[x := x′] = e0 -- for each x ∈var(q0) ∩var(e0)
:: BM a b = { ab :: a b, ba
:: b a }
bm :: BM a a
bm = {ab=id, ba=id}
:: Expr a
= E.e: Lit (BM a e) e & toString e
| E.e: Add (BM a e)
(Expr e) (Expr e) & + e
| E.e: Eq
(BM a Bool) (Expr e) (Expr e) & == e
lit e = Lit bm e
add l r = Add bm l r
eq l r = Eq bm l r
eval :: (Expr a) a
eval (Lit bm e)
= bm.ba e
eval (Add bm l r) = bm.ba (eval l + eval r)
eval (Eq
bm l r) = bm.ba (eval l == eval r)
print :: (Expr a) String
print (Lit _ e)
= toString e
print (Add _ l r) = print l +++ "+" +++ print r
print (Eq
_ l r) = print l +++ "==" +++ print r
Listing 2:Clean 中的表達式 GADT。
data Expr a where
Lit :: Show a ⇒a Expr a
Add :: Num a
⇒Expr a Expr a Expr a
Eq
:: Eq e
⇒Expr e Expr e Expr Bool
eval :: Expr a a
eval (Lit e)
= e
eval (Add l r) = eval l + eval r
eval (Eq
l r) = eval l == eval r
print :: Expr a String
print (Lit e)
= show e
print (Add l r) = print l ++ "+"
++ print r
print (Eq
l r) = print l ++ "==" ++ print r
Listing 3:Haskell 中的表達式 GADT。
generic gEq a :: a a Bool
gEq{|Int|}
x
y
= x == y
gEq{|Bool|}
x
y
= x == y
gEq{|Real|}
x
y
= x == y
gEq{|Char|}
x
y
= x == y
gEq{|UNIT|}
x
y
= True
gEq{|OBJECT|} f
(OBJECT x)
(OBJECT y)
= f x y
gEq{|CONS|}
f
(CONS x)
(CONS y)
= f x y
gEq{|RECORD|} f
(RECORD x)
(RECORD y)
= f x y
gEq{|FIELD|}
f
(FIELD x)
(FIELD y)
= f x y
gEq{|PAIR|}
fl fr (PAIR lx rx) (PAIR ly ry) = fl lx ly && fr rx ry
gEq{|EITHER|} fl _
(LEFT x)
(LEFT y)
= fl x y
gEq{|EITHER|} _
fr (RIGHT x)
(RIGHT y)
= fr x y
gEq{|EITHER|} _
_
_
_
= False
:: T = C1 Int ([Char], ?Bool) | C2
derive gEq [], T, (,), ?
Start = (gEq{|*|} C2 (C1 42 ([], ?Just True)), gEq{||} (<) [1,2,3] [2,3,4])
// (False, True)
Listing 4:Clean 中的泛型相等函數。
generic gPrint a :: a [String] [String]
gPrint{|Int|}
x
acc = [toString x:acc]
gPrint{|Bool|}
x
acc = [toString x:acc]
gPrint{|Real|}
x
acc = [toString x:acc]
gPrint{|Char|}
x
acc = [toString x:acc]
gPrint{|UNIT|}
x
acc = acc
gPrint{|PAIR|}
fl fr (PAIR l r) acc = fl l [" ":fr r acc]
gPrint{|EITHER|} fl _
(LEFT x)
acc = fl x acc
gPrint{|EITHER|} _
fr (RIGHT x)
acc = fr x acc
gPrint{|OBJECT|}
f
(OBJECT x) acc = f x acc
gPrint{|CONS of gcd|}
f
(CONS x)
acc = ["(", gcd.gcd_name, " ":f x [")":acc]]
gPrint{|RECORD of grd|} f
(RECORD x) acc = ["{", grd.grd_name, " | ":f x ["}":acc]]
gPrint{|FIELD of gfd|}
f
(FIELD x)
acc = [pre, gfd.gfd_name, "=":f x acc]
where
pre = if (gfd.gfd_index == 0) "" ", "
:: T = {f1 :: Int, f2 :: (Real, [?Int])}
derive gPrint (,), [], ?, T
Start = gPrint{|*|} {f1=42, f2=(3.14, [?None])} []
// {T | f1=42 , f2=(_Tuple2 3.14 (Cons (!None ) (_Nil )))}
Listing 5:Clean 中的泛型打印函數。