一。引用
引用模塊的語法格式為:
-- 把模块中所有函数加入全局命名空间
import <module>
-- 部分引用
import <module> (fn1, fn2)
-- 引入数据类型及其值构造器
import <module> (Datatype(constructor, constructor))
-- 引入所有值构造器
import <module> (Datatype(..))
-- hiding 排除
import <module> hiding (fn)
-- 保留命名空间
import qualified <module>
-- 保留命名空间,并起别名
import qualified <module> as <alias>
例如:
import Data.List
import Data.List (nub, sort)
import Data.Tree (Tree(Node, Branch))
import Data.Tree (Tree(..))
import Data.List hiding (nub)
import qualified Data.Map
import qualified Data.Map as M
hiding 語法能夠緩解命名衝突問題,但不很方便,對於存在大量命名衝突的模塊,可以通過 qualified 保留命名空間來避免衝突
GHCi 環境
通過 :m 命令引用模塊:
> :m Data.List
> :m Data.List Data.Map Data.Set
GHC 7.0 之後,支持在 GHCi 環境直接使用 import 語法:
> import qualified Data.Map as M
> M.fromList [('a', 1)]
fromList [('a',1)]
所以,不用關注環境區別,具體見 import qualified in GHCI
二。聲明
模塊用來組織代碼,比如把功能相近的函數放到同一個模塊中
例如二叉樹的模塊定義:
module BTree
-- 声明要暴露出去的函数及数据类型
( Tree
, singleton
, add
, fromList
, find
) where
-- 引入依赖模块
-- 定义数据类型及函数
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
singleton x = Node x EmptyTree EmptyTree
注意:
-
強制要求模塊名與文件名相同,所以對應的文件名應為
BTree.hs -
模塊聲明必須位於首行(之前不能有
import之類的東西,import可以放在where之後)
模塊中數據結構的導出與 import 語法類似:
module MyModule (Tree(Branch, Leaf)) where
data Tree a = Branch {left, right :: Tree a} | Leaf a
只暴露出數據結構 Tree 及其構造器 Branch 和 Leaf,也可以通過 .. 暴露出所有值構造器:
module MyModule (Tree(..))
或者不暴露值構造器,僅允許通過工廠方法等方式獲取該類型值(常見的比如 Map.fromList):
module MyModule (Tree, factory)
缺點是,這樣做就無法使用值構造器進行模式匹配了
子模塊
模塊具有樹形層級結構,模塊可以有子模塊,子模塊還可以有子模塊……
對目錄結構及命名有要求,例如:
.
├── main.hs
└── Math
├── Number.hs
└── Vector.hs
包名要求首字母大寫(Math),子模塊文件名要與子模塊名保持一致,大小寫敏感性與環境有關(比如 OSX 不敏感)
三。標準庫模塊
標準庫內置了很多強大的函數,可以通過 Hoogle 查看用法示例、類型聲明、甚至源碼,非常方便
Data.List
提供了大量的 List 操作函數,常用的比如 map, filter,還有:
謂詞:
-- every,全部為 True 才 True
and :: Foldable t => t Bool -> Bool
-- some,有一個為 True 就 True
or :: Foldable t => t Bool -> Bool
-- 常用的 some,List 中任意元素滿足條件就 True
any :: Foldable t => (a -> Bool) -> t a -> Bool
-- 常用的 every,List 中所有元素滿足條件才 True
all :: Foldable t => (a -> Bool) -> t a -> Bool
構造新 List:
-- 在數組中插入分隔元素
intersperse :: a -> [a] -> [a]
-- 與 intersperse 類似,在二維數組中插入一維數組作為分隔元素,再打平到一維
intercalate :: [a] -> [[a]] -> [a]
-- 二維數組行列轉置
transpose :: [[a]] -> [[a]]
-- 降維(把一組 List 連接成一個 List)
concat :: Foldable t => t [a] -> [a]
-- 先做映射再降維,相當於 concat . map
concatMap :: Foldable t => (a -> [b]) -> t a -> [b]
-- 無限遞歸調用,把返回值再傳入
iterate :: (a -> a) -> a -> [a]
-- 按位置斷開,返回斷開的兩部分
splitAt :: Int -> [a] -> ([a], [a])
-- 取元素,直到不滿足條件為���
takeWhile :: (a -> Bool) -> [a] -> [a]
-- 刪元素,直到不滿足條件為止
dropWhile :: (a -> Bool) -> [a] -> [a]
-- 按條件斷開(首次不滿足條件的位置),類似於 takeWhile
span :: (a -> Bool) -> [a] -> ([a], [a])
-- 按條件斷開(首次滿足條件的位置)
break :: (a -> Bool) -> [a] -> ([a], [a])
-- 遞歸 init,直到 List 為空
inits :: [a] -> [[a]]
-- 遞歸 tail,直到 List 為空
tails :: [a] -> [[a]]
排序:
-- 歸並排序
sort :: Ord a => [a] -> [a]
-- 插入到 List 中第一個大於等於該元素的元素之前
insert :: Ord a => a -> [a] -> [a]
分組:
-- 分組,依據是相鄰且值相等
group :: Eq a => [a] -> [[a]]
-- 按條件分組,滿足條件的一組,不滿足的一組
partition :: (a -> Bool) -> [a] -> ([a], [a])
匹配:
-- 子串匹配(子 List 匹配),是否包含指定子串
isInfixOf :: Eq a => [a] -> [a] -> Bool
-- 子串匹配,是否以指定子串開頭
isPrefixOf :: Eq a => [a] -> [a] -> Bool
-- 子串匹配,是否以為指定子串結尾
isSuffixOf :: Eq a => [a] -> [a] -> Bool
-- 元素包含性檢測,是否包含指定元素
elem :: (Foldable t, Eq a) => a -> t a -> Bool
-- 元素包含性檢測,是否不包含指定元素
notElem :: (Foldable t, Eq a) => a -> t a -> Bool
查找:
-- 按條件查找,返回第一個滿足條件的元素
find :: Foldable t => (a -> Bool) -> t a -> Maybe a
-- 查找,返回第一個匹配元素索引或 Nothing
elemIndex :: Eq a => a -> [a] -> Maybe Int
-- 查找所有
elemIndices :: Eq a => a -> [a] -> [Int]
-- 與 find 類似,但返回第一個滿足條件的元素索引
findIndex :: (a -> Bool) -> [a] -> Maybe Int
-- 與 find 類似,但返回所有滿足條件的項的索引
findIndices :: (a -> Bool) -> [a] -> [Int]
組合:
-- 組合 List,還有 zip3 ~ zip7
zip :: [a] -> [b] -> [(a, b)]
-- 組合 List,並 map 一遍,還有 zipWith3 ~ zipWith7
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
文本處理:
-- 字符串按行拆分(\n)
lines :: String -> [String]
-- join 換行(\n)
unlines :: [String] -> String
-- 按空白字符拆分
words :: String -> [String]
-- join 空格
unwords :: [String] -> String
刪除元素:
-- 去重
nub :: Eq a => [a] -> [a]
-- 刪掉第一個匹配元素
delete :: Eq a => a -> [a] -> [a]
集合運算:
-- 求差集,有重複元素的話,只刪第一個
(\\) :: Eq a => [a] -> [a] -> [a]
-- 求並集
union :: Eq a => [a] -> [a] -> [a]
-- 求交集
intersect :: Eq a => [a] -> [a] -> [a]
更通用的版本
length, take, drop, splitAt, !!, replicate 等函數參數或返回值都有要求 Int 類型,不夠通用,因此提供了類型更通用的對應版本:
genericLength :: Num i => [a] -> i
genericTake :: Integral i => i -> [a] -> [a]
genericDrop :: Integral i => i -> [a] -> [a]
genericSplitAt :: Integral i => i -> [a] -> ([a], [a])
genericIndex :: Integral i => [a] -> i -> a
genericReplicate :: Integral i => i -> a -> [a]
nub, delete, union, intsect, group, sort, insert, maximum, minimum 都通過 == 來判斷是否相等,也提供了更通用的允許自己判斷相等性的版本:
nubBy :: (a -> a -> Bool) -> [a] -> [a]
deleteBy :: (a -> a -> Bool) -> a -> [a] -> [a]
unionBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
intersectBy :: (a -> a -> Bool) -> [a] -> [a] -> [a]
groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
insertBy :: (a -> a -> Ordering) -> a -> [a] -> [a]
maximumBy :: Foldable t => (a -> a -> Ordering) -> t a -> a
minimumBy :: Foldable t => (a -> a -> Ordering) -> t a -> a
帶 By 的函數通常與 Data.Function.on 一起用:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
f `on` g = \x y -> f (g x) (g y)
比如要表達按正負給相鄰元素分組:
groupBy ((==) `Data.Function.on` (> 0)) values
語義很清楚:按照元素是否大於零,給它分類
另外,sort 與 sortBy compare 等價(默認的比較方式就是 compare),要按 List 長度排序的話,這樣做:
sortBy (compare `on` length) xs
語義同樣非常清楚。所以
(==) `on`
compare `on`
都是非常棒的慣用套路
P.S.可以通過 :browse <module> 命令查看模塊中的所有函數及數據類型定義的類型聲明
Data.Char
String 實際上是 [Char]:
type String = [Char] -- Defined in 'GHC.Base'
所以在處理字符串時,經常會用到 Data.Char 模塊,提供了很多字符相關函數
判定字符範圍:
-- 控制字符
isControl :: Char -> Bool
-- 空白符
isSpace :: Char -> Bool
-- 小寫 Unicode 字符
isLower :: Char -> Bool
-- 大寫 Unicode 字符
isUpper :: Char -> Bool
-- 字母
isAlpha :: Char -> Bool
-- 字母或數字
isAlphaNum :: Char -> Bool
-- 可打印字符
isPrint :: Char -> Bool
-- ASCII 數字,0-9
isDigit :: Char -> Bool
-- 八進制數
isOctDigit :: Char -> Bool
-- 十六進制數
isHexDigit :: Char -> Bool
-- 字母,功能等價於 isAlpha,實現方式不同
isLetter :: Char -> Bool
-- Unicode 注音字符,比如法文
isMark :: Char -> Bool
-- Unicode 數字,包括羅馬數字等
isNumber :: Char -> Bool
-- 標點符號
isPunctuation :: Char -> Bool
-- 貨幣符號
isSymbol :: Char -> Bool
-- Unicode 空格或分隔符
isSeparator :: Char -> Bool
-- ASCII 字符(Unicode 字母表前 128 位)
isAscii :: Char -> Bool
-- Unicode 字母表前 256 位
isLatin1 :: Char -> Bool
-- 大寫 ASCII 字符
isAsciiUpper :: Char -> Bool
-- 小寫 ASCII 字符
isAsciiLower :: Char -> Bool
判斷所屬類型:
generalCategory :: Char -> GeneralCategory
返回的 GeneralCategory 是個枚舉,共 30 個類別,例如:
> generalCategory 'a'
LowercaseLetter
> generalCategory ' '
Space
字符轉換:
-- 轉大寫
toUpper :: Char -> Char
-- 轉小寫
toLower :: Char -> Char
-- 轉 title 形式,與 toUpper 類似,部分連體字母有區別
toTitle :: Char -> Char
-- 字符轉數字,要求 [0-9,a-f,A-F]
digitToInt :: Char -> Int
-- 數字轉字符
intToDigit :: Int -> Char
-- 字符轉 Unicode 碼
ord :: Char -> Int
-- Unicode 碼轉字符
chr :: Int -> Char
所以,要實現簡單的加解密可以這樣做:
encode shift = map $ chr . (+ shift) . ord
decode shift = map $ chr . (subtract shift) . ord
-- 或者技巧性更足的
decode shift = encode $ negate shift
Data.Map
字典是鍵值對的無序列表,以 平衡二叉樹 的形式存儲,Data.Map 提供了一些字典處理函數
P.S.Data.Map 中的一些函數與 Prelude 和 Data.List 模塊存在命名衝突,所以使用 qualified import as 保留命名空間並起個別名:
import qualified Data.Map as Map
構造新 Map:
-- List 轉 Map,有重複 key 的話,取最後一個 value
Map.fromList :: Ord k => [(k, a)] -> Map.Map k a
-- Map 轉 List
Map.toList :: Map.Map k a -> [(k, a)]
-- 與 fromList 類似,不直接丟棄重複 key,允許手動處理
Map.fromListWith :: Ord k => (a -> a -> a) -> [(k, a)] -> Map.Map k a
-- 空 Map
Map.empty :: Map.Map k a
-- 插入 (k, a),返回新 Map
Map.insert :: Ord k => k -> a -> Map.Map k a -> Map.Map k a
-- 與 insert 類似,允許處理重複 key
Map.insertWith :: Ord k => (a -> a -> a) -> k -> a -> Map.Map k a -> Map.Map k a
-- 單元素 Map
Map.singleton :: k -> a -> Map.Map k a
-- 映射到新 Map
Map.map :: (a -> b) -> Map.Map k a -> Map.Map k b
-- 濾出新 Map
Map.filter :: (a -> Bool) -> Map.Map k a -> Map.Map k a
取 Map 信息:
-- 判空
Map.null :: Map.Map k a -> Bool
-- 長度
Map.size :: Map.Map k a -> Int
-- 取所有 key
Map.keys :: Map.Map k a -> [k]
-- 取所有 value
Map.elems :: Map.Map k a -> [a]
查找:
-- 按 key 查找
Map.lookup :: Ord k => k -> Map.Map k a -> Maybe a
-- 包含性判斷
Map.member :: Ord k => k -> Map.Map k a -> Bool
Data.Set
提供了集合相關的工具函數,結構上去 Map 類似,都以樹結構存儲
P.S.同樣,也存在大量命名衝突,需要 qualified import:
import qualified Data.Set as Set
構造集合:
-- List 轉 Set
Set.fromList :: Ord a => [a] -> Set.Set a
集合操作:
-- 求交集
Set.intersection :: Ord a => Set.Set a -> Set.Set a -> Set.Set a
-- 求差集
Set.difference :: Ord a => Set.Set a -> Set.Set a -> Set.Set a
-- 求並集
Set.union :: Ord a => Set.Set a -> Set.Set a -> Set.Set a
-- 判斷子集
Set.isSubsetOf :: Ord a => Set.Set a -> Set.Set a -> Bool
-- 判斷真子集
Set.isProperSubsetOf :: Ord a => Set.Set a -> Set.Set a -> Bool
注意,函數名很調皮啊,數組的 List.intersect 到集合這變成 Set.intersection 了
Map 中的很多函數在 Set 里也有對應版本,例如 null, size, member, empty, singleton, insert, delete, map, filter 等
同樣,集合可以用來實現一行代碼去重:
unique :: Ord a => [a] -> [a]
unique = Set.toList . Set.fromList
集合去重效率高於 List.nub,但缺點是構造集合會對元素進行排序,所以得到的去重結果不保留原順序(List.nub 會保留)
參考資料
-
Haskell data type pattern matching:模式匹配自定義數據類型
暫無評論,快來發表你的看法吧