Define and Use Hackage

一個 haskell package 的版本號是有明確意義而不是隨便亂取的數字開心就好的。 到底這些數字個別有什麼意思?不同的數字間又代表了什麼差異?接下來我們會簡單介紹一下, 希望對於有心開發 hackage 的人會有幫助。

此外,我們說一個 instance C T 是一個孤兒 (orphan instance) 是指這個 instance 既不是和 data T 一起被定義在同一個 module 中,也不是和 class C 一起被定義在同一個 module 中。這樣的 instance 會產生一些 importing 上面的問題,也因此這個議題對於開發 hackage 的人來說是值得了解的。

版本號

一個 haskell package 的版本號基本上都具有 A.B.C 的形式。其中 A.B 被稱為是主要版本號 (major version number),而 C 則是次要版本號 (minor version number)。除了 A.B.C 以外,目前還允許更多的數字當做是輔助的版本號,例如說 1.2.1.1。 原則上版本號會根據下列 policy 來做變更:

[如果出現下列變更,增加主要版本號 A.B]

  • 刪除1 entities2
  • 刪除 instances
  • 更改 entities 的型別 (type)
  • 更改 datatype 或 classes 的定義更改
  • 新增 orphan instances (更多細節)

[如果出現下列變更,A.B 不變,但是增加 C]

  • 新增 bindings (即 funcions 或 variables) 或 types 或 classes
  • 新增 non-orphan instances
  • 新增 modules (更多細節)

關於新增 module

原則上,新增 module,根據上述的 policy,會造成次要版本號的變更。但是有時候新增 module 也是會造成主要版本號的變更。 例如說,新增的 module 和其他 modules 撞名或是新增了過於一般性的 module (例如說 Data.Set)。原則上後者這種情況應該是需要避免的,而且其實蠻好避免的,因為我們只要設定並且使用自己的 name space 就可以非常有效地避免這個問題了。

相容性 (compatibility)

假設套件的開發者有滿足上述 policy 中的第二點。那麼,使用者在設定 dependency 時允許一定範圍的 C 例如 abc >= 2.1.0 && < 2.2,則可以確保向下相容 (backwards compatibility)。因為更大的 C 意指只有新增東西,而不是有變更或減少。

但是,如果套件中有出現了 orphan instance,那麼在設定 dependency 時必須根據 orphan instance 牽涉到的 datatype and typeclass 來精確地指定 C。例如說,假設某套件在 2.1.1 時新增了 data T (或 class C) 且根據 T (或 C) 定義了若干組 orphan instances ,則在使用該套件時必須很精確地去描述那一個特定的次要版本號以維持相容性,例如說 abc >= 2.1.1 && < 2.1.2。(不過在這個情況下,也許應該說是在強制套件使用者要去注意相容性的問題。)

關於 import

不同的 importing 方式會造成 dependency 設定上可以允許的版本號範圍會不太一樣。例如說,給定下面兩種 importing 方式:

第一種方法可以有效地避免兩個 modules 之間的內容物撞名問題。所以兩個 modules 不管怎麼新增東西都還是不會有撞名的問題, 因此我們可以設定比較寬鬆的 dependency 如

第二種 importing 方法就沒那麼好了。第一個問題是,假設兩個 modules 同時都新增了一個叫做 xoo 的 function, 那麼很容易我們可以發現這會有撞名問題。為了在更新這兩個 packages 時避免這種撞名問題,我們被迫只能設定比較嚴格的 dependency 如

此外,第二種 importing 還有一個問題:因為宣告 DEPRECATED 和實際刪除程式碼都視為是刪除(所以需要改主要版本號),所以我們沒法從主要版本號上面去區別這兩種情況。 用上面的例子來說明:假設 yoo 被宣告成 DEPRECATED,那麼第二種方法會在 yoo 真的被刪除時出現 importing 錯誤。

孤兒 (orphan instance)

誠如一開始所介紹的,一個 instance C T 是一個孤兒 (orphan instance) 是指這個 instance 既不是和 data T 一起被定義在同一個 module 中,也不是和 class C 一起被定義在同一個 module 中。

這會造成什麼問題呢?主要的問題是來自於:「instance 本身是沒有辦法在定義 module 時被暴露 (expose) 的」,實際上這會造成:「我們沒有辦法選擇要或是不要 import 某個或某些 instances」。例如說給定下面兩個 modules

我們可以在 import TMCM 時自由地把 data Tclass C 給隱藏 (hide) 起來,這種時候那些相關的 instance 就會被連帶隱藏起來。可是對於 orphan instances 例如說

就沒這種事情了。一旦任何人 import 了 Another,那兩個 instances 就會被強制性地 import 到那個 module 裡面,而且我們沒有辦法選擇不要也沒有辦法隱藏其中任何一個。

一個解決孤兒問題的方法是使用 newtype。例如說上面那個 Another 可以被改寫成

這樣就可以強制性地把這兩個孤兒給領養(?)起來。


  1. 宣告 DEPRECATED 也是視為一種刪除

  2. 一個 entity 是指在某個 module 中可以“看到”的 value 或 datatype 或 typeclass。所謂“看到”可能意味著該 entity 被定義在這個 module 或是被 import 進來。

[Tags: haskell, hackage]