求《银河系漫游者必知42事指南》五部txt文档!

豆丁微信公众号
君,已阅读到文档的结尾了呢~~
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
银河系漫游指南(英)
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='http://www.docin.com/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口后将能永久保存播放记录|
《清风明月1990》分享的百度云盘资源
分享资源:183&&粉丝:3381&&关注:14&&&&07-09hello world
百度云盘分享达人推荐
(C) 面包动漫站 [MBZhan.Com] 所有影视大全和影视均收集引用于各大视频网站,本站只提供影视视频引用播放,不参与视频制作上传,不提供视频储存下载。
若本站的引用侵犯了您的利益,请联系我们核查所实后将在第一时间删除。欢迎影视爱好者对本站引用视频内容进行监督,共创绿色健康互联网。
手机扫一扫轻松打开李开复在纽约哥伦比亚大学工程学院的毕业典礼演讲实录_百度文库
您的浏览器Javascript被禁用,需开启后体验完整功能,
享专业文档下载特权
&赠共享文档下载特权
&100W篇文档免费专享
&每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
李开复在纽约哥伦比亚大学工程学院的毕业典礼演讲实录
阅读已结束,下载本文需要
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
加入VIP
还剩11页未读,
定制HR最喜欢的简历
你可能喜欢高效文件处理这是一个简单的文件读取测试,读取一个全是数字的文本文件,输出它们的和。-- file: ch08/SumFile.hsmain = do&&& contents &- getContents&&& print (sumFile contents)& where sumFile = sum . map read . words虽然String类型是读写文件时默认使用的,但是它的效率较低,因此这个简单的程序执行效率会很糟糕。String 用 Char值的列表表示; 列表上每个元素都是独立分配的,并且还有一些簿记的开销。要读写文本和二进制文件的程序,其内存占用和性能会受到这些因素影响。像这样简单的测试,在量比较大时,即使像Python这样的解释型语言的性能也比Haskell的好。bytestring 库提供了对String类型快速廉价的替代。用bytestring 写的代码在性能和内存占用上经常可以媲美甚至超越C,同时保持Haskell的表达能力和简洁。这个库提供了两个模块。每个模块中定义的函数正好替换掉与之相对的String上的函数。&* Data.ByteString 模块定义了一个严格类型 ByteString。 它将二进制或文本的字符串保存在单独一个数组中。&* Data.ByteString.Lazy 模块提供了惰性类型,也叫 ByteString。它将数据字符串表示成一系列块,每个最大64KB的数组。两种ByteString类型分别在特定的场景下性能更好。对于大量的流式数据(几百M到上T),惰性 ByteString 类型通常是最好的。块的大小调整到适合现代CPU的L1 缓存的大小,垃圾回收器也可以快速的把不再需要的流数据清除掉。严格ByteString 类型在应用程序不需要关心内存占用时性能最好,或者当需要进行随机数据访问时应用。二进制 I/O 和qualified import我们来开发些小的函数来演示ByteString 的一些API。我们将确定一个文件是不是ELF目标文件:这是几乎所有现代的Unix类操作系统上使用的可执行文件格式。只需要简单的查看文件开头的四个字节,看看它们是否匹配一个特定的字节序列。标识出文件类型的序列通常称为魔术字。-- file: ch08/ElfMagic.hsimport qualified Data.ByteString.Lazy as LhasElfMagic :: L.ByteString -& BoolhasElfMagic content = L.take 4 content == elfMagic&&& where elfMagic = L.pack [0x7f, 0x45, 0x4c, 0x46]上面代码中的import qualified 使用了 Haskell 的qualified import语法导入 ByteString 模块。这可以让我们用自己选择的名字来引用一个模块。例如,当我们要指向 lazy ByteString 模块的take函数时,必须写成 L.take ,因为我们把这个模块用L的名字导入。如果我们不明确支出要的是那个版本的take的话,编译器将报告错误。对ByteStirng模块我们总是用qualified import语法,因为它们提供的很多函数都与Prelude模块中重名。提示Qualified import 可以方便的切换 ByteString 的类型。只需要修改源文件头上的import声明;剩下的代码很可能不需要修改。这样可以方便的测试两种类型的 ByteString,来看看哪种更适合你的应用的需要。不管用不用 qualified import,都可以用模块的全名来无歧义进行指定。例如, Data.ByteString.Lazy.length 和 L.length都指的是相同的函数,就像 Prelude.sum 和 sum。惰性和严格ByteString模块适合用在二进制I/O中。Haskell中表示字节的数据类型是 Word8;如果需要引用它的名字,需要从 Data.Word 模块中导入它。L.pack 函数取一个 Word8 值的列表,并把他们打包成一个惰性 ByteString。(L.unpack函数执行相反的操作)hasElfMagic函数简单的把文件的前4个字节与魔术字进行比较。我们用经典的Haskell风格写的程序,hasElfMagic 函数不进行I/O操作。这里是将它用在文件上的函数。-- file: ch08/ElfMagic.hsisElfFile :: FilePath -& IO BoolisElfFile path = do& content &- L.readFile path& return (hasElfMagic content)&L.readFile 函数是readFile函数在惰性ByteString中的等价物。它对文件惰性的按需读取。它在同时读取最多64KB的块时效率也很高。惰性 ByteString对我们的任务是个好的选择:因为我们只需要读取文件头最多4个字节,我们可以安全的把这个函数用在任意大小的文件上。文本 I/O为了方便,bytestring 库提供了另外两个带有有限文本I/O能力的模块, Data.ByteString.Char8 和 Data.ByteString.Lazy.Char8。它们将单独字符串元素用Char而非Word8暴露。警告这些模块中的函数只处理字节大小的Char值,所以只适合于ASCII和一些欧洲字符集。超过255的值将被截断。面向字符的bytestring模块提供了文本处理的有用工具。这里的文件包含了一个著名互联网公司2008年中每月股票价格。ghci& putStr =&& readFile "prices.csv"Date,Open,High,Low,Close,Volume,Adj Close,20.09,20.12,19.53,19.80,.80,21.12,21.20,20.60,20.66,.66,27.07,27.10,26.63,26.76,.76,27.17,27.78,26.76,27.41,.41如何从这样一系列条目中找到最高的收盘价?收盘价是逗号分隔的第四列。这个函数从一行数据中取出收盘价。-- file: ch08/HighestClose.hsimport qualified Data.ByteString.Lazy.Char8 as Lclosing = readPrice . (!!4) . L.split ','因为这个函数用& (??)风格书写,因此我们从右到左读。 L.split 函数将惰性ByteString函数用匹配字符分割成列表。(!!)操作符获得列表中某项元素。 readPrice 函数将字符串表示的小数价格转换成实际的数字。-- file: ch08/HighestClose.hsreadPrice :: L.ByteString -& Maybe IntreadPrice str =&&& case L.readInt str of&&&&& Nothing&&&&&&&&&&&& -& Nothing&&&&& Just (dollars,rest) -&&&&&&&& case L.readInt (L.tail rest) of&&&&&&&&& Nothing&&&&&&&&&& -& Nothing&&&&&&&&& Just (cents,more) -&&&&&&&&&&&& Just (dollars * 100 + cents)我们使用L.readInt 函数,它解析一个整数。它返回整数和字符串剩下的部分。我们的定义有些复杂,因为L.readInt 在解析失败时需要返回Nothing。我们寻找最高收盘价的函数很直截了当。-- file: ch08/HighestClose.hshighestClose = maximum . (Nothing:) . map closing . L.lineshighestCloseFrom path = do&&& contents &- L.readFile path&&& print (highestClose contents)我们用了一个技巧来避开maximum函数不能应用在空列表上的限制。ghci& maximum [3,6,2,9]9ghci& maximum []*** Exception: Prelude.maximum: empty list由于我们不希望对空的股票数据抛出异常,因此用 (Nothing:)表达式来保证maximum 用的 Maybe Int 值永远不为空。ghci& maximum [Nothing, Just 1]Just 1ghci& maximum [Nothing]Nothing我们的函数可以工作么?ghci& :load HighestClose[1 of 1] Compiling Main&&&&&&&&&&&& ( HighestClose.hs, interpreted )Ok, modules loaded: Main.ghci& highestCloseFrom "prices.csv"Loading package array-0.1.0.0 ... linking ... done.Loading package bytestring-0.9.0.1 ... linking ... done.Just 2741因为我们已经把I/O从逻辑中分离开了,所以可以不用创建空文件就能测试没有数据的情况。ghci& highestClose L.emptyNothing文件名匹配很需哦面向系统的编程语言都提供库函数,来让我们将文件名与模式进行匹配,或者给出匹配模式的文件列表。其他语言里,这个函数经常称为 fnmatch。虽然Haskell的标准库里提供了很好的系统编程工具,但是并没有提供这类模式匹配函数。我们借此机会开发自己的。我们要处理的模式种类有glob模式,通配符模式,或称shell风格模式。它们有一些简单的规则。你可能已经知道了,这里我们快速的回顾一下。&&& * 从一个字符串的开头到结尾匹配一个模式&&& * 大部分文本字节匹配本身。例如,模式中的文本foo 将只匹配到输入字符串里的foo。&&& * *(星号)字符表示&任意匹配&;它将匹配任何的文本,包括空字符串。例如, foo* 模式将会匹配任何以 foo 开头的字符串,像 foo本身, foobar, 或者 foo.c 等。quux*.c 模式将匹配任何以 quux 开头,以 .c 结尾的字符串,如 quuxbaz.c 。&&& * ? (问号)字符匹配任意一个字符。pic??.jpg 模式将匹配如 picaa.jpg 或 pic01.jpg 这样的名字。&&& * [ (左方括号) 字符开始一个字符类型,以 ] 结束。它的意思是&匹配这种类型中任意一个字符&。字符类型可以在 [ 后跟一个 ! 来取反,它的意思是&匹配任何不在这个类型中的字符&。&&&& 一个字符后跟 - (中划线),再跟另一个字符,表示一个范围:&匹配这个集合内的任意字符&。&&&&& 字符类型有一个巧妙的地方是它们不能为空。在[ 或者 [! 后的第一个字符是属于类型的一部分,因为我们可以写一个包含 ] 字符的类型 []aeiou]。& pic[0-9].[pP][nN][gG] 模式匹配的字符串是由 pic 字符串,跟单独一个数字,再跟任意大小写的 .png 字符串组成的。虽然Haskell的标准库里并没有提供块模式的匹配,但它提供了一个很好的正则表达式匹配库。glob模式只是裁剪过的正则表达式,只有些微语法变化。glob模式很容易转换成正则表达式,不过要先理解如何在Haskell中使用正则表达式。Haskell中的正则表达式在这一节,我们假设你已经熟悉其他一些语言如Python, Perl 或者 Java 中的正则表达式。为了简短,从现在开始把&正则表达式&简写为 regexp(译注:或简称正则)。我们主要关注Haskell中处理正则与其他语言中的不同点。Haskell的正则匹配库比其他的语言更具表达能力,因此有很多要讨论的内容。要开始探索正则库,只需要用 Text.Regex.Posix 模块。和往常摸索这个模块最方便的方式是用 ghci 交互模式。ghci& :module +Text.Regex.Posix要进行一半的正则匹配只需要一个函数,中缀表达式 (=~) (从Perl中借鉴的)。要跨过的第一个障碍是Haskell的正则库大量使用多态。结果是, (=~) 操作符的类型签名很难理解,因此我们不在这里解释它了。=~ 操作符的参数和返回结果的类型都用类型类。第一个参数(在 =~ 左侧)是要匹配的文本;第二个参数(在右边)是要匹配的正则表达式。两者都可以用String 或者 ByteString。结果的多种类型=~ 操作符的返回值是多态的,因此Haskell编译器需要一些方法知道我们想要什么类型的结果。在实际的代码中,它也许可以通过我们后面使用结果的方式来推断它的正确类型。但是在用ghci摸索的时候经常缺少这些提示。如果我们没有指定结果的类型,解释器将会返回错误,因为它没有足够的信息来推断结果的类型。当ghci无法推断目标类型时,我们告诉它我们想要的类型是什么。如果想要一个Bool类型结果,将会得到一个通过还是失败的结果。ghci& "my left foot" =~ "foo" :: BoolLoading package array-0.1.0.0 ... linking ... done.Loading package containers-0.1.0.1 ... linking ... done.Loading package bytestring-0.9.0.1 ... linking ... done.Loading package mtl-1.1.0.0 ... linking ... done.Loading package regex-base-0.93.1 ... linking ... done.Loading package regex-posix-0.93.1 ... linking ... done.Trueghci& "your right hand" =~ "bar" :: BoolFalseghci& "your right hand" =~ "(hand|foot)" :: BoolTrue在正则库内部,有一个名为 RegexContext 的类型类用来描述目标类型的行为;基础库里定义类这个类型的很多实例。Bool类型是这个类型类的实例,因此我们得到了有用的结果。另一个这种实例是 Int,它给出正则匹配成功的次数。ghci& "a star called henry" =~ "planet" :: Int0ghci& "honorificabilitudinitatibus" =~ "[aeiou]" :: Int13如果要 String 类型结果,将会获得第一个匹配上的子串,如果没有匹配上则返回空的字符串。ghci& "I, B. Ionsonii, uurit a lift'd batch" =~ "(uu|ii)" :: String"ii"ghci& "hi ludi, F. Baconis nati, tuiti orbi" =~ "Shakespeare" :: String""另一个可用的类型结果是 [String],它返回所有匹配的字符串的列表。ghci& "I, B. Ionsonii, uurit a lift'd batch" =~ "(uu|ii)" :: [String]&interactive&:1:0:&&& No instance for (RegexContext Regex [Char] [String])&&&&& arising from a use of `=~' at &interactive&:1:0-50&&& Possible fix:&&&&& add an instance declaration for&&&&& (RegexContext Regex [Char] [String])&&& In the expression:&&&&&&&&&&& "I, B. Ionsonii, uurit a lift'd batch" =~ "(uu|ii)" :: [String]&&& In the definition of `it':&&&&&&& it = "I, B. Ionsonii, uurit a lift'd batch" =~ "(uu|ii)" ::&&&&&&&&&&&& [String]ghci& "hi ludi, F. Baconis nati, tuiti orbi" =~ "Shakespeare" :: [String]&interactive&:1:0:&&& No instance for (RegexContext Regex [Char] [String])&&&&& arising from a use of `=~' at &interactive&:1:0-54&&& Possible fix:&&&&& add an instance declaration for&&&&& (RegexContext Regex [Char] [String])&&& In the expression:&&&&&&&&&&& "hi ludi, F. Baconis nati, tuiti orbi" =~ "Shakespeare" :: [String]&&& In the definition of `it':&&&&&&& it = "hi ludi, F. Baconis nati, tuiti orbi" =~ "Shakespeare" ::&&&&&&&&&&&& [String][Note]&& &小心String结果如果想要String类型的结果,要当心。因为 (=~) 返回空字符串来表示 &无匹配&,显然如果空字符串也可是合法的正则匹配的话,这将会造成困难。如果遇到这种情况,应该用一个不同的返回类型代替,如 [String]。这些是&简单&的结果类型,不过我们不会就此结束。在继续前,用一个单独的模式来用在剩下的例子中。可以在ghci里把这个模式定义成变量,节省点输入。ghci& let pat = "(foo[a-z]*bar|quux)"当匹配发生时,我们可以获取很多信息。如果请求 (String, String, String) 类型的元组,将会获得第一次匹配前的文本,匹配的文本,和剩下的文本。ghci& "before foodiebar after" =~ pat :: (String,String,String)("before ","foodiebar"," after")如果匹配失败,整个文本将做为元组中 &匹配之前&的部分,其他两个元素为空。ghci& "no match here" =~ pat :: (String,String,String)("no match here","","")请求一个四元组将会得到第四个元素,它是所有匹配上的文本列表。ghci& "before foodiebar after" =~ pat :: (String,String,String,[String])("before ","foodiebar"," after",["foodiebar"])也可以获得关于匹配的数字信息。一对 Int 给出第一次匹配的子串的偏移量和长度。如果要一个这种数对的列表,将会获得所有匹配的信息。ghci& "before foodiebar after" =~ pat :: (Int,Int)(7,9)ghci& "i foobarbar a quux" =~ pat :: [(Int,Int)]如果匹配失败的话,在请求单独一个元组时返回的元组第一个元素值为 -1 (匹配偏移量),请求元组列表的话则返回空列表。ghci& "eleemosynary" =~ pat :: (Int,Int)(-1,0)ghci& "mondegreen" =~ pat :: [(Int,Int)]这不是RegexContext 类型类全部的内置实例。Text.Regex.Base.Context 模块的文档中有完整的列表。这种函数返回结果类型多态的能力,在静态类性语言中很不寻常。正则表达式进阶混合和匹配字符串类型之前已经注意到, =~ 操作符的参数类型和返回值类型都是类型类。可以用 String 或者严格ByteString 值作为正则表达式和要匹配的文本。ghci& :module +Data.ByteString.Char8ghci& :type pack "foo"pack "foo" :: ByteString尝试一下把 String 和 ByteString 混合在一起使用。ghci& pack "foo" =~ "bar" :: BoolFalseghci& "foo" =~ pack "bar" :: Int0ghci& pack "foo" =~ pack "o" :: [(Int, Int)]不过,要注意如果想要一个字符串结果的话,要匹配的文本的字符串类型要相同。看下在实际中的含义。ghci& pack "good food" =~ ".ood" :: [ByteString]["good", "food"]在上面的例子里,用 pack 把 String转换成 ByteString。因为结果类型中是 ByteString,因此类型检查通过。不过如果用 String 来尝试的话,就不行了。ghci& "good food" =~ ".ood" :: [ByteString]&interactive&:1:0:&&& No instance for (RegexContext Regex [Char] [ByteString])&&&&& arising from a use of `=~' at &interactive&:1:0-20&&& Possible fix:&&&&& add an instance declaration for&&&&& (RegexContext Regex [Char] [ByteString])&&& In the expression: "good food" =~ ".ood" :: [ByteString]&&& In the definition of `it':&&&&&&& it = "good food" =~ ".ood" :: [ByteString]可以简单的修复这个问题,让左边字符串的类型和结果类型重新匹配就可以了。ghci& "good food" =~ ".ood" :: [String]这个限制并不适用于要匹配的正则表达式。它用String 还是 ByteString 都是没限制的。其他需要知道的事情在查看Haskell的库文档时,会看到几个与正则相关的模块。 Text.Regex.Base 下的模块定义了其他正则模块用的公共API。同时可能安装有多个不同的正则API实现。在本书写作时,GHC自带了一个实现 Text.Regex.Posix。如其名称所暗示的,这个包提供了 POSIX正则语义。[Note]&& &Perl 和 POSIX 正则表达式如果你从Perl,Python或者Java这些语言转到Haskell语言,并且用过这些语言中的正则表达式的话,应该注意在 Text.Regex.Posix中处理的POSIX正则与Perl风格的正则有很多重要的区别。这里是一些显著的不同点。Perl 的正则引擎匹配时采用左侧优先,而POSIX引擎则是贪婪匹配。这意味着,对于一个正则表达式 (foo|fo*) 和一个字符串 foooooo,Perl风格的引擎将匹配到 foo (最左侧匹配),而POSIX引擎将匹配整个字符串(贪婪匹配)。POSIX正则的语法相对于Perl风格的更少更统一。同时也缺少一些Perl风格正则提供的功能,如零宽度断言和对贪婪匹配的控制。Hackage上还有其他的正则程序包可供下载。有些比当前的POSIX引擎提供了更好的性能(如 regex-tdfa);还有的提供了大多数程序员们都熟悉的Perl风格匹配(如 regex-pcre)。他们都支持本节讲到的标准API。将 glob 模式转换成正则表达式已经看到许多用正则表达式匹配文本的方式了,现在回过头来看下glob模式。我们要写一个函数把glob模式转换成正则表达式。glob模式和正则都是字符串,因此函数的类型就很清楚了。-- file: ch08/GlobRegex.hsmodule GlobRegex&&& (&&&&& globToRegex&&& , matchesGlob&&& ) whereimport Text.Regex.Posix ((=~))globToRegex :: String -& String生成的正则必须是锚定的,这样它从字符串的开头匹配到结尾。-- file: ch08/GlobRegex.hsglobToRegex cs = '^' : globToRegex' cs ++ "$"String就是 [Char],一个Char的列表。: 操作符把一个值(这里是字符 ^ )放到列表的前端,这个列表是将要看到的 globToRegex' 函数的返回值。[Note] 在定义之前使用一个值在Haskell的源文件中使用一个值或者变量时,并不要求先声明或定义。定义出现在它第一次使用的后面是很正常的。Haskell编译器在源代码一级并不关心顺序。我们可以按照最合理的方式来灵活组织代码,而不是非要按照让编译器作者最舒服的顺序。Haskell模块的作者经常把&最重要&的代码放在源文件的开头,把其他的连接辅助代码放到后面。在这里globToRegex函数和它的辅助函数就是这么做的。 globToRegex' 函数以正则表达式为基础,因此要做一些转换工作。我们用Haskell模式匹配方便的枚举出要处理的每种情况。-- file: ch08/GlobRegex.hsglobToRegex' :: String -& StringglobToRegex' "" = ""globToRegex' ('*':cs) = ".*" ++ globToRegex' csglobToRegex' ('?':cs) = '.' : globToRegex' csglobToRegex' ('[':'!':c:cs) = "[^" ++ c : charClass csglobToRegex' ('[':c:cs)&&&& = '['& :& c : charClass csglobToRegex' ('[':_)&&&&&&& = error "unterminated character class"globToRegex' (c:cs) = escape c ++ globToRegex' cs第一个子句规定如果遇到glob模式的结尾(空字符串),就返回正则中匹配行尾的 $ 符号。后面的一系列子句把glob语法转成正则语法。最后一个子句处理其他字符的转义。escape函数保证正则引擎不把输入的字符当作正则的语法翻译。-- file: ch08/GlobRegex.hsescape :: Char -& Stringescape c | c `elem` regexChars = '\\' : [c]&&&&&&&& | otherwise = [c]&&& where regexChars = "\\+()^$.{}]|"charClass 辅助函数只检查一个字符类是否正确的结束。它把输入直接输出,直到遇到 ] 才把控制权交还给 globToRegex' 。-- file: ch08/GlobRegex.hscharClass :: String -& StringcharClass (']':cs) = ']' : globToRegex' cscharClass (c:cs)&& = c : charClass cscharClass []&&&&&& = error "unterminated character class"现在完成了globToRegex和它的辅助函数的定义,载入到ghci中试一下。ghci& :load GlobRegex.hs[1 of 1] Compiling GlobRegex&&&&&&& ( GlobRegex.hs, interpreted )Ok, modules loaded: GlobRegex.ghci& :module +Text.Regex.Posixghci& globToRegex "f??.c"Loading package array-0.1.0.0 ... linking ... done.Loading package containers-0.1.0.1 ... linking ... done.Loading package bytestring-0.9.0.1 ... linking ... done.Loading package mtl-1.1.0.0 ... linking ... done.Loading package regex-base-0.93.1 ... linking ... done.Loading package regex-posix-0.93.1 ... linking ... done."^f..\\.c$"它们看上去很像合理的正则表达式。可以用它匹配字符串么?ghci& "foo.c" =~ globToRegex "f??.c" :: BoolTrueghci& "test.c" =~ globToRegex "t[ea]s*" :: BoolTrueghci& "taste.txt" =~ globToRegex "t[ea]s*" :: BoolTrue成功!我们再在 ghci 中玩一下。可以创建一个 fnmatch 的临时定义并使用。ghci& let fnmatch pat name& =& name =~ globToRegex pat :: Boolghci& :type fnmatchfnmatch :: (RegexLike Regex source1) =& String -& source1 -& Boolghci& fnmatch "d*" "myname"False然而 fnmatch的名字并不自然。目前最通用的Haskell函数命名风格是具有描述性的驼峰匹配( camel cased)。驼峰匹配把单词连接起来,除了开头的外的单词都大写首字母。比如 &file name matches&的,将变成 fileNameMatches。驼峰匹配这个名字来自大写字母造成的&驼峰&。在我们库里,将用 matchesGlob 来命名。-- file: ch08/GlobRegex.hsmatchesGlob :: FilePath -& String -& Boolname `matchesGlob` pat = name =~ globToRegex pat你可能已经注意到了,我们用过的大多数变量名都比较短。作为一个经验,在更长的函数定义中,富有描述性的变量名更有用一些。而对于一个两行的函数来说,长函数名没太大用处。练习1. 在ghci中试验下,给globToRegex传入格式错误的模式,如 [ ,看看会发生什么。写一个小函数调用 globToRegex,传给它一个错误格式的模式,看看发生什么。2. Unix类文件系统的文件名是大小写敏感的(如"G" 和& "g" 是不同的),而Windows的文件系统不是。给globToRegex 和 matchesGlob函数增加一个参数,控制是否进行大小写敏感匹配。重要的离题:编写惰性函数在命令式语言中,globToRegex'函数通常用循环表示。例如Python的标准 fnmatch 模块包含了一个名为 translate 的函数,它与globToRegex 功能相同。它就是用循环实现的。如果你受到如 Scheme或ML这些语言的函数式编程思想的影响,你可能已经被灌输了这样的概念&通过尾递归模拟循环&。看下globToRegex'函数,它并不是尾递归。再看下它最后一个子句(其他子句的解构类似)就知道原因了。-- file: ch08/GlobRegex.hsglobToRegex' (c:cs) = escape c ++ globToRegex' cs它递归的调用自身,并将递归调用的结果作为 (++)函数的参数。因为递归调用不是这个函数的最后的动作,因此globToRegex'不是尾递归。为什么这个函数的定义不是尾递归呢?原因在于Haskell的非严格求值策略。在开始讨论它之前,我们先快速的看看为什么传统的语言里应该避免这种类型的递归。这是 (++) 的一个简化定义。它是递归的,但不是尾递归。-- file: ch08/append.hs(++) :: [a] -& [a] -& [a](x:xs) ++ ys = x : (xs ++ ys)[]&&&& ++ ys = ys在严格语言中,如果要求值 "foo" ++ "bar" ,将构造整个列表,然后返回。非严格求值将大部分工作推迟到真正需要他们的时候。如果要用 "foo" ++ "bar" 这个表达式的一个元素,函数定义的一个模式被匹配,并返回 x : (xs ++ ys) 表达式。(:) 构造子是非严格的, xs ++ ys 的求值被推迟:需要多少元素就产生多少。当产生更多结果时,将不再使用 x,因此垃圾回收器会回收它。因为我们按需生成需要的元素,并且不保留不需要的部分,因此编译器生成的代码可以使用不变的空间。使用我们的模式匹配器有了一个可以匹配glob模式的函数,还要能实际使用它。在Unix类系统上,glob模式返回所有匹配给定模式的文件和目录的名字。我们在Haskell中创建一个类似的函数。按照Haskell的命名规范,称其为 namesMatching。-- file: ch08/Glob.hsmodule Glob (namesMatching) where我们规定使用Glob 模块的用户只能看到模块中的 namesMatching。这个函数显然要进行很多文件系统路径的操作,运行时要进行分割和组合等。我们要用些之前不熟悉的模块。System.Directory提供了操作目录和目录内容的标准函数。-- file: ch08/Glob.hsimport System.Directory (doesDirectoryExist, doesFileExist,&&&&&&&&&&&&&&&&&&&&&&&& getCurrentDirectory, getDirectoryContents)System.FilePath 模块是操作系统路径名约定的抽象。(&/&)函数将路径的两个部分进行组合。ghci& :m +System.FilePathghci& "foo" &/& "bar"Loading package filepath-1.1.0.0 ... linking ... done."foo/bar"&dropTrailingPathSeparator 函数的名字已经说明了一切了。ghci& dropTrailingPathSeparator "foo/""foo"&splitFileName 函数在路径最后一个斜线处分割。ghci& splitFileName "foo/bar/Quux.hs"("foo/bar/","Quux.hs")ghci& splitFileName "zippity"("","zippity")&System.FilePath 和 System.Directory 模块一起用,可以写一个Unix类和Windows系统上都可用的 namesMatching 函数。-- file: ch08/Glob.hsimport System.FilePath (dropTrailingPathSeparator, splitFileName, (&/&))在这个模块里将模拟"for"循环;初次尝试Haskell的异常处理;当然还有我们刚写的 matchesGlob 函数。-- file: ch08/Glob.hsimport Control.Exception (handle)import Control.Monad (forM)import GlobRegex (matchesGlob)因为目录和文件都是&现实世界&,操作它们有&副作用&,所以处理它们的函数结果具有 IO 类型。如果传入的字符串不包含模式字符,我们简单的检查文件系统中是否有给定的名字。(注意这里用Haskell的守卫语法,以写出简洁的定义。用"if"也可以实现,但是审美上欠缺些)-- file: ch08/Glob.hsisPattern :: String -& BoolisPattern = any (`elem` "[*?")namesMatching pat& | not (isPattern pat) = do&&& exists &- doesNameExist pat&&& return (if exists then [pat] else [])doesNameExist 指向的函数后面很快会定义。如果字符串是一个glob模式呢?函数的定义继续。-- file: ch08/Glob.hs& | otherwise = do&&& case splitFileName pat of&&&&& ("", baseName) -& do&&&&&&&&& curDir &- getCurrentDirectory&&&&&&&&& listMatches curDir baseName&&&&& (dirName, baseName) -& do&&&&&&&&& dirs &- if isPattern dirName&&&&&&&&&&&&&&&&& then namesMatching (dropTrailingPathSeparator dirName)&&&&&&&&&&&&&&&&& else return [dirName]&&&&&&&&& let listDir = if isPattern baseName&&&&&&&&&&&&&&&&&&&&&&& then listMatches&&&&&&&&&&&&&&&&&&&&&&& else listPlain&&&&&&&&& pathNames &- forM dirs $ \dir -& do&&&&&&&&&&&&&&&&&&&&&&&&&& baseNames &- listDir dir baseName&&&&&&&&&&&&&&&&&&&&&&&&&& return (map (dir &/&) baseNames)&&&&&&&&& return (concat pathNames)用splitFileName函数来把字符串分为两部分,最后的文件名和前面其他部分。如果第一个元素是空的,将在当前目录中寻找模式。否则,必须检查目录名看是否包含模式。如果没有,就创建只有这个目录名的列表。如果包含模式,将列出所有匹配的目录。[Note]&& &需要小心的事System.FilePath 模块有些棘手。上面就是个恰当的例子;splitFileName 函数在返回的目录名末尾留了一个斜线。ghci& :module +System.FilePathghci& splitFileName "foo/bar"Loading package filepath-1.1.0.0 ... linking ... done.("foo/","bar")如果忘记(或不知道)去掉那个斜线的话,因为下面namesMatching的行为,namesMatching将会无限递归。ghci& splitFileName "foo/"("foo/","")你可以猜到发生了什么事让我们加上这个注释。最后,把每个目录中所有匹配的收集起来,得到一个列表的列表,把它们连接成一个单独的名字的列表。上面不熟悉的forM函数有些像 "for" 循环:它把第二个参数(一个动作)映射到第一个参数(一个列表)上,并返回结果的列表。还有些散乱的收尾工作要做。首先是上面用到的doesNameExist函数的定义。System.Directory模块并不能让我们检查一个名字是否存在。它强迫我们要么检查文件要么检查目录。这个API有些笨拙,因此我们把这两个检查放在一个函数中。为了性能,先检查文件,因为文件要远比目录普遍。-- file: ch08/Glob.hsdoesNameExist :: FilePath -& IO BooldoesNameExist name = do&&& fileExists &- doesFileExist name&&& if fileExists&&&&& then return True&&&&& else doesDirectoryExist name还有两个函数要定义,每个都返回目录中名字的一个列表。listMatches 函数返回目录中所有匹配给定glob模式的名字的列表。-- file: ch08/Glob.hslistMatches :: FilePath -& String -& IO [String]listMatches dirName pat = do&&& dirName' &- if null dirName&&&&&&&&&&&&&&& then getCurrentDirectory&&&&&&&&&&&&&&& else return dirName&&& handle (const (return [])) $ do&&&&&&& names &- getDirectoryContents dirName'&&&&&&& let names' = if isHidden pat&&&&&&&&&&&&&&&&&&&& then filter isHidden names&&&&&&&&&&&&&&&&&&&& else filter (not . isHidden) names&&&&&&& return (filter (`matchesGlob` pat) names')isHidden ('.':_) = TrueisHidden _&&&&&& = FalselistPlain 函数要么返回空列表,要么返回一个元素的列表,取决于传入的名字是否存在。-- file: ch08/Glob.hslistPlain :: FilePath -& String -& IO [String]listPlain dirName baseName = do&&& exists &- if null baseName&&&&&&&&&&&&& then doesDirectoryExist dirName&&&&&&&&&&&&& else doesNameExist (dirName &/& baseName)&&& return (if exists then [baseName] else [])仔细观察上面的listMatches定义,会发现我们调用了一个名为 handle 的函数。之前,我们从Control.Exception 模块中导入了它的定义;如其名字所暗示的,这是初次尝试Haskell中的异常处理。我们把它扔到ghci中看看有什么发现。ghci& :module +Control.Exceptionghci& :type handlehandle :: (Exception -& IO a) -& IO a -& IO a这说明 handle 取两个参数。第一个是一个函数,这个函数传入一个异常值,并可以具有副作用(其返回值类型带有 IO);抛出异常时执行这个函数。第二个参数是可能会抛出异常的代码。作为异常处理器,handle 的类型限制了它的返回结果类型,必须与可能抛出异常的程序体的值的类型相同。因此在我们代码中,它要么抛出异常,要么返回一个String的列表。const 函数取两个参数;总是返回它的第一个参数,而不管第二个参数。ghci& :type constconst :: a -& b -& aghci& :type return []return [] :: (Monad m) =& m [a]ghci& :type handle (const (return []))handle (const (return [])) :: IO [a] -& IO [a]用const写一个异常处理器,它忽略掉传入的异常。在捕获到异常时,它可以让我们的代码返回空列表。这里对异常处理没有什么可说的了。还有许多需要讨论的内容,我们放在第19章《错误处理》中。练习1. 虽然我们已经使用大小写敏感的globToRegex函数写了可移植的namesMatching函数。找出一种方法,修改namesMatching函数的定义而不改动其类型,使它在Unix类系统上大小写敏感,而在Windows上大小写不敏感。提示:考虑阅读System.FilePath的文档,找出表示当前运行在Unix还是Windows系统上的变量。2. 如果你在Unix类系统上,查看System.Posix.Files模块的文档,看看是否可以找到doesNameExist函数的替代。3. * 通配符只在一个目录中匹配名字。很多shell有一个扩展的通配符语法 **,它可以在所有目录中递归的匹配名字。例如 **.c 意思是&在本目录及其任意深的子目录中匹配 .c 结尾的名字&。实现 ** 通配符。API设计时的错误处理当给globToRegex 传入格式错误的模式时,并不一定是灾难。也许是用户拼写错误,我们希望在这种情况下可以报告有意义的错误信息。这类问题发生时就调用error 函数是很极端的反应(??)。error 抛出一个异常。纯Haskell代码无法处理异常,因此程序从纯代码中跳出,跳到最近的安装了合适的异常处理的IO调用中。如果没有安装这样的处理器,Haskell运行时默认会中止我们的程序(在ghci中打印出一堆讨厌的错误信息)。因此调用error 有点类似于拉下战斗机弹射座椅的把手。我们从一个无法得体处理的灾难中逃离,在坠地后留下一堆燃烧的碎片。我们已经确立了 error 是为了灾难发生时使用的,但是在globToRegex中我们依然使用它。非法的输入应该被拒绝,但是不该把事情搞大。更好的处理方法是什么呢?Haskell的类型系统和库可以拯救我们!我们可以在globToRegex的类型签名中使用预定义的Either类型,来表示失败的可能性。-- file: ch08/GlobRegexEither.hstype GlobError = StringglobToRegex :: String -& Either GlobError StringglobToRegex 返回的值要么是 Left "错误信息",要么是 Right "有效的正则"。这个返回类型强迫调用者去处理可能的错误。(你会发现Haskell代码中Either类型的使用经常出现)练习1. 写一个用上面类型签名的globToRegex版本。2. 修改namesMatching的类型签名,使它可以对模式的错误进行编码,让它使用你重写的globToRegex函数。[Tip]&& &Tip你会发现涉及的工作惊人的大。别担心:后面的章节中会介绍如何用更简洁更高级的方式处理错误。实际应用代码namesMatching 函数本身没什么激动人心的,但确实很有用的程序块。和其他一些函数组合后,就可以做些有趣的事情了。这里有一个例子。定义一个 renameWith 函数,它不是简单的把文件重命名,而是把文件名应用到一个函数上,再将文件改名成此函数的返回结果。-- file: ch08/Useful.hsimport System.FilePath (replaceExtension)import System.Directory (doesFileExist, renameDirectory, renameFile)import Glob (namesMatching)renameWith :: (FilePath -& FilePath)&&&&&&&&&& -& FilePath&&&&&&&&&& -& IO FilePathrenameWith f path = do&&& let path' = f path&&& rename path path'&&& return path'我们再次使用了一个辅助函数,来绕开System.Directory 对文件/目录的区分。-- file: ch08/Useful.hsrename :: FilePath -& FilePath -& IO ()rename old new = do&&& isFile &- doesFileExist old&&& let f = if isFile then renameFile else renameDirectory&&& f old newSystem.FilePath 模块里提供了很多操作文件名的有用函数。这些函数可以很好的与我们的 renameWith 及 namesMatching函数结合,这就就可以快速的用它们创建复杂的行为了。下面就是个实例,这个简洁的函数用来处理C++源文件的后缀名。-- file: ch08/Useful.hscc2cpp =& mapM (renameWith (flip replaceExtension ".cpp")) =&& namesMatching "*.cc"cc2cpp 函数用了些将会反复看到的函数。flip 函数把另一个函数的参数进行交换(在ghci里看下replaceExtension的类型就知道为什么了)。 =&& 函数将其右侧动作的结果传给其左侧的动作。练习1. glob模式解释起来足够简单,可以不用通过正则而直接用Haskell写。试一下。如果读者不知晓正则表达式的话,我们推荐Jeffrey Friedl 的《精通正则表达式》一书。(O'Reilly)
阅读(...) 评论()

我要回帖

更多关于 银河系漫游指南超级电脑 的文章

 

随机推荐