一.引用
引用模块的语法格式为:
-- 把模块中所有函数加入全局命名空间
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:模式匹配自定义数据类型