实战 最长状态
从去年开始,陆续学习了一点Haskell的东西,当时在codewars上面着实写了一些练习题,还有H99之类的.
最近又开始回头看SICP,然后看到racket,接触到CSE341的课程,这个过程中自己也有和Haskell进行比较.
比较的结果,就是Haskell还是有着自己的独特之处的,值得再继续学习下去.
学习要结合着练习,毕竟学的都是死的,真的能够在日常生活中使用起来,才能够保持对相关知识的活力.
所以就有了今天这个实战的小code.
题目
要完成的功能其实非常简单.
日志处理
日志按行分布,部分行中带有BufferS的信息.
这些行的Log中,有的带有status信息.具体的的status,则是从status到该行的最后.
找到停留在该status最长的状态,并且输出对应的停留的行数.
注意:带有BufferS的行并不一定是连续的.
这个要求其实比较简单,就是求出该日志中停留在什么状态最长.
其中可能涉及到的方法有:
参数解析
文件读取,和标准输出
文本处理
其他东西,则涉及到Haskell具体的一些东西了,后面再说.
实战
本文涉及到的Haskell的特点
副作用
很多资料都介绍说Haskell是一个没有副作用的语言.这其实是不可能的,如果完全没有副作用,那么其实就等于不能够运行一样.
盗图一张啊 :)
但是Haskell的副作用限制比较大,至少要符合其对应的类型系统中去.这样的结果就是副作用比较集中,从而不会散布在代码的各个地方.
例如:
|
|
所用的带副作用的代码,都在main函数的控制中.而这里代用副作用的函数其实就三个getArgs获得参数,readFile,以及print打印.比如我想把打印直接放在process的函数里面,这个时候就会有问题,因为process的类型就必须是IO类型的.这和一般的代码结构是矛盾的.
函数式
在上面的代码片段中,以及可以获取参数,读取文件,标准输出都包含了.下面就集中在文本处理process的过程上.
首先需要将整个文件读取内容按行进行划分,使用lines函数.lines函数将内容划分成字符串列表[String].
评论
从此可以看到Haskell的一个特点,自带电池,库的强大性.这点基本上在所有的编程语言的发展历史上都有所体现.从C到C++,到Java,包括Haskell,以及一堆在jvm上面的语言.
第二个特点,数据结构的支持,原始的编程语言中很少带有强大的数据结构,但是后续的编程语言都带有强大的数据结构,包括列表,map(或者dict字典,关联数组).
如果说各种各样的库,使得编程语言的能力加性的提高,那么数据结构在使得编程语言的能力有着乘性的提高.
下面需要过滤字符串列表,带有BufferS并且带有status的继续处理,其他的可以丢失掉.结合函数式编程语言标配的filter
,只需要提供一个谓词函数就可以了.
|
|
函数式编程语言的函数式的特点,可以提供更高的抽象性.一般的函数操作其输入输出数据,高阶的函数操作函数,从而提高更高的抽象性.
|
|
这里的.&&.
,其实是一个函数,类似于C++的操作符重载.这里相当于建立一个新的操作符.这个操作将两个谓词函数都应用在参数上并且与
起来.也就是组成一个新的谓词函数,要求同时满足两个谓词函数.听起来觉得好像和一般的与
似乎差不多啊.
&& -> 返回一个布尔值,该布尔值是两个值的`与`
.&&. -> 返回一个谓词函数,该函数要求同时满足(`与`)两个谓词函数
这样对比一下,好像清晰一点点了.
紧接着,从满足条件的字符串中提取除相应的状态信息.
然后就到了最核心的逻辑的地方了.
|
|
- 从右向左边看,
group
将列表划分成列表的列表,这个新的列表中的每个列表都是相同的. - 同时应用
head
和length
函数到列表的元素(第二级的列表). maximumBy
函数,则比较元素的第二个参数.
例如:
["a", "a", "b", "c", "c", "c", "a"]
->
[["a", "a"], ["b"], ["c", "c", "c"], ["a"]]
->
[("a", 2), ("b", 1), ("c", 4), ("a", 1)]
->
("c", 4)
从这一段可以看到Haskell强大的表现力.对Haskell熟悉以后,这一段就像直接翻译处核心逻辑一样.除了关键点的逻辑之外,没有一点点噪声,没有奇奇怪怪的中间变量和下标,没有特意构造出来的临时的数据结构.甚至可以说肯定没有bug,因为这些已经是最上层的逻辑的翻译了,如果有问题,那也是程序员对需求的逻辑没有理解清楚,而不是代码本身的bug.
性能问题
从上面来看,Haskell的这段代码岂不是很好,没有一点问题了?
也不尽然,这段代码的性能比较慢,对于100M+的日志文件,需要处理40s+.这个时间在实际的使用过程中是不能接受的.日常工作中节奏的连贯性是非常重要的,过长的等待时间会使得程序员的注意力无法保持集中,从而打断其工作的状态,因此需要优化.
Haskell的优化可以通过profiling来进行.如果没有profiling则不要做任何优化,因为这个时候根本不知道要优化什么地方,仅仅依赖于幻想来优化,往往只是浪费时间而已.
profile的结果显示,第一步的lines就需要很长的时间.从网上搜索发现,可以通过将String优化为ByteString数据结构,从而大大加快其性能.
兼容性
而Haskell上,ByteString
和String
是两种数据结构,但是有很多的成员函数?都是API兼容
的,从而比较容易切换过去.
注意的是,这里的兼容和一般的API兼容是不同的,这里其实是其域上的函子是一致的.
比如,两个结构上都有isInfixOf
函数.
-- Stirng, [Char] 使用的
isInfixOf :: [a] -> [a] -> Bool
-- ByteString
isInfixOf :: ByteString -> ByteString -> Bool
-- 只是保持这个形式不变
T -> T -> Bool
完整的代码在这里
回头看,很多地方就是在胡言乱语,姑且先记在这里吧.