Haskell/模組
模組是組織 Haskell 程式碼的主要手段。我們在使用 import 語句將庫函式引入作用域時,就遇到了它們。除了幫助我們更好地使用庫之外,瞭解模組還可以幫助我們構建自己的程式,並建立獨立的程式,這些程式可以獨立於 GHCi 執行(順便說一下,這是下一章的主題,獨立程式)。
Haskell 模組[1] 是一種將一組相關功能分組到單個包中並管理可能具有相同名稱的不同函式的有用方法。模組定義是 Haskell 檔案中的第一件事。
一個基本的模組定義如下
module YourModule where
請注意
- 模組的名稱以大寫字母開頭;
- 每個檔案只包含一個模組。
檔案的名字是模組的名字加上.hs副檔名。模組名中的任何點 '.' 將被更改為目錄。[2] 因此模組YourModule將在檔案YourModule.hs中,而模組Foo.Bar將在檔案Foo/Bar.hs或Foo\Bar.hs。由於模組名必須以大寫字母開頭,因此檔名也必須以大寫字母開頭。
模組本身可以從其他模組匯入函式。也就是說,在模組宣告和程式碼的其餘部分之間,您可以包含一些匯入宣告,例如
import Data.Char (toLower, toUpper) -- import only the functions toLower and toUpper from Data.Char
import Data.List -- import everything exported from Data.List
import MyModule -- import everything exported from MyModule
匯入的資料型別由其名稱指定,後面是括號中的匯入建構函式列表。例如
import Data.Tree (Tree(Node)) -- import only the Tree data type and its Node constructor from Data.Tree
如果您匯入了一些具有重疊定義的模組怎麼辦?或者,如果您匯入了一個模組,但想自己覆蓋一個函式呢?有三種方法可以處理這些情況:限定匯入、隱藏定義和重新命名匯入。
假設 MyModule 和 MyOtherModule 都為remove_e定義了,它從字串中刪除所有 e 例項。但是,MyModule 只刪除小寫 e,而 MyOtherModule 則刪除大小寫。在這種情況下,以下程式碼是模稜兩可的
import MyModule
import MyOtherModule
-- someFunction puts a c in front of the text, and removes all e's from the rest
someFunction :: String -> String
someFunction text = 'c' : remove_e text
不清楚remove_e指的是什麼!為了避免這種情況,請使用 qualified 關鍵字
import qualified MyModule
import qualified MyOtherModule
someFunction text = 'c' : MyModule.remove_e text -- Will work, removes lower case e's
someOtherFunction text = 'c' : MyOtherModule.remove_e text -- Will work, removes all e's
someIllegalFunction text = 'c' : remove_e text -- Won't work as there is no remove_e defined
在後面的程式碼片段中,沒有任何名為remove_e的函式可用。當我們進行限定匯入時,所有匯入的值都包含模組名作為字首。順便說一下,即使您進行了常規匯入,您也可以使用相同的字首(在我們的示例中,MyModule.remove_e即使沒有包含“qualified”關鍵字,也仍然有效)。
注意
限定名稱(如 MyModule.remove_e)和函式組合運算子 (.) 之間存在歧義。編寫 reverse.MyModule.remove_e 一定會讓你的 Haskell 編譯器感到困惑。一種解決方案是風格化:始終使用空格進行函式組合,例如 reverse . remove_e 或 Just . remove_e 甚至 Just . MyModule.remove_e
現在假設我們要匯入MyModule和MyOtherModule,但我們確信要刪除所有 e,而不僅僅是刪除小寫 e。每次呼叫時新增MyOtherModule將變得非常繁瑣remove_e。我們不能只是排除remove_e嗎MyModule?
import MyModule hiding (remove_e)
import MyOtherModule
someFunction text = 'c' : remove_e text
這之所以有效是因為匯入行上的 hiding 一詞。匯入行中“hiding”關鍵字後面的任何內容都不會被匯入。透過用括號和逗號分隔列出它們來隱藏多個項
import MyModule hiding (remove_e, remove_f)
請注意,代數資料型別和型別同義詞不能隱藏。它們始終會被匯入。如果您在多個匯入模組中定義了資料型別,則必須使用限定名稱。
這並不是真正允許覆蓋的技術,但它通常與 qualified 標誌一起使用。想象一下
import qualified MyModuleWithAVeryLongModuleName
someFunction text = 'c' : MyModuleWithAVeryLongModuleName.remove_e text
尤其是在使用 qualified 時,這會很煩人。我們可以使用 as 關鍵字改進它
import qualified MyModuleWithAVeryLongModuleName as Shorty
someFunction text = 'c' : Shorty.remove_e text
這使我們能夠使用Shorty代替MyModuleWithAVeryLongModuleName作為匯入函式的字首。這種重新命名對限定匯入和常規匯入都有效。
只要沒有衝突的專案,我們就可以匯入多個模組並將它們重新命名為相同
import MyModule as My
import MyCompletelyDifferentModule as My
在這種情況下,MyModule中的函式和MyCompletelyDifferentModule中的函式都可以以 My 為字首。
有時,對同一個模組使用兩次 import 指令很方便。一個典型的場景如下
import qualified Data.Set as Set
import Data.Set (Set, empty, insert)
這使您可以透過別名“Set”訪問 Data.Set 模組的所有內容,還可以讓您訪問幾個選定的函式(empty、insert 和建構函式)而無需使用“Set”字首。
在本文開頭的示例中,使用了“import 所有匯出的內容 from MyModule”一詞。[3] 這引出一個問題。我們如何決定哪些函式是匯出的,哪些函式是“內部的”?以下是如何
module MyModule (remove_e, add_two) where
add_one blah = blah + 1
remove_e text = filter (/= 'e') text
add_two blah = add_one . add_one $ blah
在這種情況下,只有remove_e和add_two被匯出。雖然add_two被允許使用add_one,但匯入MyModule的模組中的函式不能直接使用add_one,因為它沒有被匯出。
資料型別匯出規範與匯入類似。您命名型別,然後在括號中列出建構函式列表
module MyModule2 (Tree(Branch, Leaf)) where
data Tree a = Branch {left, right :: Tree a}
| Leaf a
在這種情況下,模組宣告可以改寫為“MyModule2 (Tree(..))”,宣告所有建構函式都被匯出。
維護匯出列表是一個好習慣,不僅因為它減少了名稱空間汙染,而且因為它能夠進行某些編譯時最佳化,否則這些最佳化將不可用。