模块_Haskell笔记2

一.引用

引用模块的语法格式为:

-- 把模块中所有函数加入全局命名空间
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)) wheredata 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会保留)

参考资料

  • Haskell/Modules
  • Haskell data type pattern matching:模式匹配自定义数据类型

原文发布于微信公众号 - ayqy(gh_690b43d4ba22)

原文发表时间:2018-04-26

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券