跳到主要內容
黯羽輕揚每天積累一點點

模塊_Haskell 筆記 2

免費2018-04-26#Functional_Programming#Haskell模块#Haskell module syntax#Haskell模块定义

模塊的聲明、引用方式,以及常用標準庫函數概覽

一。引用

引用模塊的語法格式為:

-- 把模块中所有函数加入全局命名空间
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 及其構造器 BranchLeaf,也可以通過 .. 暴露出所有值構造器:

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

語義很清楚:按照元素是否大於零,給它分類

另外,sortsortBy 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 中的一些函數與 PreludeData.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 會保留)

參考資料

評論

暫無評論,快來發表你的看法吧

提交評論