Haskell趣学指南7-8

 

模块

装载模块

haskell中的模块是含有一组相关的函数,类型和类型类的组合。而haskell程序的本质便是从主模块中引用其它模块并调用其中的函数来执行操作。这样可以把代码分成多块,只要一个模块足够的独立,它里面的函数便可以被不同的程序反复重用。这就让不同的代码各司其职,提高了代码的健壮性。

haskell的标准库就是一组模块,每个模块都含有一组功能相近或相关的函数和类型。有处理List的模块,有处理并发的模块,也有处理复数的模块,等等。目前为止我们谈及的所有函数,类型以及类型类都是Prelude模块的一部分,它默认自动装载。在本章,我们看一下几个常用的模块,在开始浏览其中的函数之前,我们先得知道如何装载模块.

在haskell中,装载模块的语法为import,这必须得在函数的定义之前,所以一般都是将它置于代码的顶部。无疑,一段代码中可以装载很多模块,只要将import语句分行写开即可。装载Data.List试下,它里面有很多实用的List处理函数.

执行import Data.List,这样一来Data.List中包含的所有函数就都进入了全局命名空间。也就是说,你可以在代码的任意位置调用这些函数.Data.List模块中有个nub函数,它可以筛掉一个List中的所有重复元素。用点号将lengthnub组合:length 。nub,即可得到一个与(/xs -> length (nub xs))等价的函数。

import Data.List   
   
numUniques :: (Eq a) => [a] -> Int   
numUniques = length 。nub

你也可以在GHCi中装载模块,若要调用Data.List中的函数,就这样:

ghci> :m Data.List

若要在GHci中装载多个模块,不必多次:m命令,一下就可以全部搞定:

ghci> :m Data.List Data.Map Data.Set

而你的程序中若已经有包含的代码,就不必再用:m了.

如果你只用得到某模块的两个函数,大可仅包含它俩。若仅装载Data.List模块nubsort,就这样:

import Data.List (nub,sort)

也可以只包含除去某函数之外的其它函数,这在避免多个模块中函数的命名冲突很有用。假设我们的代码中已经有了一个叫做nub的函数,而装入Data.List模块时就要把它里面的nub除掉.

import Data.List hiding (nub)

避免命名冲突还有个方法,便是qualified importData.Map模块提供一了一个按键索值的数据结构,它里面有几个和Prelude模块重名的函数。如filternull,装入Data.Map模块之后再调用filter,haskell就不知道它究竟是哪个函数。如下便是解决的方法:

import qualified Data.Map

这样一来,再调用Data.Map中的filter函数,就必须得Data.Map.filter,而filter依然是为我们熟悉喜爱的样子。但是要在每个函数前面都加个Data.Map实在是太烦人了! 那就给它起个别名,让它短些:

import qualified Data.Map as M

好,再调用Data.Map模块的filter函数的话仅需M.filter就行了

要浏览所有的标准库模块,参考这个手册。翻阅标准库中的模块和函数是提升个人haskell水平的重要途径。你也可以各个模块的源代码,这对haskell的深入学习及掌握都是大有好处的.

检索函数或搜寻函数位置就用Hoogle,相当了不起的Haskell搜索引擎! 你可以用函数名,模块名甚至类型声明来作为检索的条件.

Data.List

显而易见,Data.List是关于List操作的模块,它提供了一组非常有用的List处理函数。在前面我们已经见过了其中的几个函数(如map和filter),这是Prelude模块出于方便起见,导出了几个Data.List里的函数。因为这几个函数是直接引用自Data.List,所以就无需使用qulified import。在下面,我们来看看几个以前没见过的函数:

intersperse取一个元素与List作参数,并将该元素置于List中每对元素的中间。如下是个例子:

ghci> intersperse '.' "MONKEY"   
"M.O.N.K.E.Y"   
ghci> intersperse 0 [1,2,3,4,5,6]   
[1,0,2,0,3,0,4,0,5,0,6]

intercalate取两个List作参数。它会将第一个List交叉插入第二个List中间,并返回一个List.

ghci> intercalate " " ["hey","there","guys"]   
"hey there guys"   
ghci> intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]]   
[1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]

transpose函数可以反转一组List的List。你若把一组List的List看作是个2D的矩阵,那transpose的操作就是将其列为行。

ghci> transpose [[1,2,3],[4,5,6],[7,8,9]]   
[[1,4,7],[2,5,8],[3,6,9]]   
ghci> transpose ["hey","there","guys"]   
["htg","ehu","yey","rs","e"]

假如有两个多项式3x2+ 5x + 9,10x3 + 98x3 + 5x2 + x - 1,将其相加,我们可以列三个List:[0,3,5,9][10,0,0,9][8,5,1,-1]来表示。再用如下的方法取得结果.

ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]   
[18,8,6,17]

使用transpose处理这三个List之后,三次幂就倒了第一行,二次幂到了第二行,以此类推。在用sum函数将其映射,即可得到正确的结果。

foldl'foldl1'是它们各自惰性实现的严格版本。在用fold处理较大的List时,经常会遇到堆栈溢出的问题。而这罪魁祸首就是fold的惰性: 在执行fold时,累加器的值并不会被立即更新,而是做一个"在必要时会取得所需的结果"的承诺。每过一遍累加器,这一行为就重复一次。而所有的这堆"承诺"最终就会塞满你的堆栈。严格的fold就不会有这一问题,它们不会作"承诺",而是直接计算中间值的结果并继续执行下去。如果用惰性fold时经常遇到溢出错误,就应换用它们的严格版。

concat把一组List连接为一个List。

ghci> concat ["foo","bar","car"]   
"foobarcar"   
ghci> concat [[3,4,5],[2,3,4],[2,1,1]]   
[3,4,5,2,3,4,2,1,1]

它相当于移除一级嵌套。若要彻底地连接其中的元素,你得concat它两次才行.

concatMap函数与map一个List之后再concat它等价.

ghci> concatMap (replicate 4) [1..3]   
[1,1,1,1,2,2,2,2,3,3,3,3]

and取一组布尔值List作参数。只有其中的值全为True的情况下才会返回True。

ghci> and $ map (>4) [5,6,7,8]   
True   
ghci> and $ map (==4) [4,4,4,3,4]   
False

orand相似,一组布尔值List中若存在一个True它就返回True.

ghci> or $ map (==4) [2,3,4,5,6,1]   
True   
ghci> or $ map (>4) [1,2,3]   
False

anyall取一个限制条件和一组布尔值List作参数,检查是否该List的某个元素或每个元素都符合该条件。通常较map一个List到and或or而言,使用any或all会更多些。

ghci> any (==4) [2,3,5,6,1,4]   
True   
ghci> all (>4) [6,9,10]   
True   
ghci> all (`elem` ['A'..'Z']) "HEYGUYSwhatsup"   
False   
ghci> any (`elem` ['A'..'Z']) "HEYGUYSwhatsup"   
True

iterate取一个函数和一个值作参数。它会用该值去调用该函数并用所得的结果再次调用该函数,产生一个无限的List.

ghci> take 10 $ iterate (*21   
[1,2,4,8,16,32,64,128,256,512]   
ghci> take 3 $ iterate (++ "haha""haha"   
["haha","hahahaha","hahahahahaha"]

splitAt取一个List和数值作参数,将该List在特定的位置断开。返回一个包含两个List的二元组.

ghci> splitAt 3 "heyman"   
("hey","man")   
ghci> splitAt 100 "heyman"   
("heyman","")   
ghci> splitAt (-3"heyman"   
("","heyman")   
ghci> let (a,b) = splitAt 3 "foobar" in b ++ a   
"barfoo"

takeWhile这一函数十分的实用。它从一个List中取元素,一旦遇到不符合条件的某元素就停止.

ghci> takeWhile (>3) [6,5,4,3,2,1,2,3,4,5,4,3,2,1]   
[6,5,4]   
ghci> takeWhile (/=' '"This is a sentence"   
"This"

如果要求所有三次方小于1000的数的和,用filter来过滤map (^3) [1..]所得结果中所有小于1000的数是不行的。因为对无限List执行的filter永远都不会停止。你已经知道了这个List是单增的,但haskell不知道。所以应该这样:

ghci> sum $ takeWhile (<10000) $ map (^3) [1..]   
53361

(^3)处理一个无限List,而一旦出现了大于10000的元素这个List就被切断了,sum到一起也就轻而易举.

dropWhile与此相似,不过它是扔掉符合条件的元素。一旦限制条件返回False,它就返回List的余下部分。方便实用!

ghci> dropWhile (/=' '"This is a sentence"   
" is a sentence"   
ghci> dropWhile (<3) [1,2,2,2,3,4,5,4,3,2,1]   
[3,4,5,4,3,2,1]

给一Tuple组成的List,这Tuple的首相表示股票价格,第二三四项分别表示年,月,日。我们想知道它是在哪天首次突破$1000的!

ghci> let stock = [(994.4,2008,9,1),(995.2,2008,9,2),(999.2,2008,9,3),(1001.4,2008,9,4),(998.3,2008,9,5)]   
ghci> head (dropWhile (/(val,y,m,d) -> val < 1000) stock)   
(1001.4,2008,9,4)

spantakeWhile有点像,只是它返回两个List。第一个List与同参数调用takeWhile所得的结果相同,第二个List就是原List中余下的部分。

ghci> let (fw,rest) = span (/=' '"This is a sentence" in "First word:" ++ fw ++ ",the rest:" ++ rest   
"First word: This,the rest: is a sentence"

span是在条件首次为False时断开list,而break则是在条件首次为True时断开List。break pspan (not 。p)是等价的.

ghci> break (==4) [1,2,3,4,5,6,7]   
([1,2,3],[4,5,6,7])   
ghci> span (/=4) [1,2,3,4,5,6,7]   
([1,2,3],[4,5,6,7])

break返回的第二个List就会以第一个符合条件的元素开头。

sort可以排序一个List,因为只有能够作比较的元素才可以被排序,所以这一List的元素必须是Ord类型类的实例类型。

ghci> sort [8,5,3,2,1,6,4,2]   
[1,2,2,3,4,5,6,8]   
ghci> sort "This will be sorted soon"   
" Tbdeehiillnooorssstw"

group取一个List作参数,并将其中相邻并相等的元素各自归类,组成一个个子List.

ghci> group [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]   
[[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]

若在group一个List之前给它排序就可以得到每个元素在该List中的出现次数。

ghci> map (/l@(x:xs) -> (x,length l)) 。group 。sort $ [1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7]   
[(1,4),(2,7),(3,2),(5,1),(6,1),(7,1)]

initstailsinittail相似,只是它们会递归地调用自身直到什么都不剩,看:

ghci> inits "w00t"   
["","w","w0","w00","w00t"]   
ghci> tails "w00t"   
["w00t","00t","0t","t",""]   
ghci> let w = "w00t" in zip (inits w) (tails w)   
[("","w00t"),("w","00t"),("w0","0t"),("w00","t"),("w00t","")]

我们用fold实现一个搜索子List的函数:

search :: (Eq a) => [a] -> [a] -> Bool   
search needle haystack =   
  let nlen = length needle   
  in foldl (/acc x -> if take nlen x == needle then True else acc) False (tails haystack)

首先,对搜索的List调用tails,然后遍历每个List来检查它是不是我们想要的.

ghci> "cat" `isInfixOf` "im a cat burglar"   
True   
ghci> "Cat" `isInfixOf` "im a cat burglar"   
False   
ghci> "cats" `isInfixOf` "im a cat burglar"   
False

由此我们便实现了一个类似isIndexOf的函数,isInfixOf从一个List中搜索一个子List,若该List包含子List,则返回True.

isPrefixOfisSuffixOf分别检查一个List是否以某子List开头或者结尾.

ghci> "hey" `isPrefixOf` "hey there!"   
True   
ghci> "hey" `isPrefixOf` "oh hey there!"   
False   
ghci> "there!" `isSuffixOf` "oh hey there!"   
True   
ghci> "there!" `isSuffixOf` "oh hey there"   
False

elemnotElem检查一个List是否包含某元素.

partition取一个限制条件和List作参数,返回两个List,第一个List中包含所有符合条件的元素,而第二个List中包含余下的.

ghci> partition (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"   
("BOBMORGAN","sidneyeddy")   
ghci> partition (>3) [1,3,5,6,3,2,1,0,3,7]   
([5,6,7],[1,3,3,2,1,0,3])

了解spanbreak的差异是很重要的.

ghci> span (`elem` ['A'..'Z']) "BOBsidneyMORGANeddy"   
("BOB","sidneyMORGANeddy")

span和break会在遇到第一个符合或不符合条件的元素处断开,而partition则会遍历整个List。

find取一个List和限制条件作参数,并返回首个符合该条件的元素,而这个元素是个Maybe值。在下章,我们将深入地探讨相关的算法和数据结构,但在这里你只需了解Maybe值是Just something或Nothing就够了。与一个List可以为空也可以包含多个元素相似,一个Maybe可以为空,也可以是单一元素。同样与List类似,一个Int型的List可以写作Int,Maybe有个Int型可以写作Maybe Int。先试一下find函数再说.

ghci> find (>4) [1,2,3,4,5,6]   
Just 5   
ghci> find (>9) [1,2,3,4,5,6]   
Nothing   
ghci> :t find   
find :: (a -> Bool-> [a] -> Maybe a

注意一下find的类型,它的返回结果为Maybe a··,这与a的写法有点像,只是Maybe型的值只能为空或者单一元素࿰

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值