`
pf_miles
  • 浏览: 134240 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

《Learn You a Haskell for Great Good!》读书笔记(三)

阅读更多
这是7,8,9章的一个小结;
实际上我看完这3章花了相当长的时间,一来最近工作的事情比较忙,二来这3章实际上包含了众多的how to,一些用法上的新玩意儿都迸发了出来...并且如果是第一次接触这语言的话,最好敲一敲那些代码,才能期望有个“感觉”;重点主要集中在type和typeclass的深入概念上,看这段内容的时候一定要不惜花时间思考,实际上我花了相当长的时间思考typeclass和type之间的关系能够用在哪些情景之中;还有就是IO,haskell不惜使用了编程语言世界中最强力的一种约束——静态类型约束来把IO操作狠狠地跟'pure code'隔离开,这带来相当大的好处...这些好处是值得花时间好好去想清楚的——想清楚了再继续往下读,否则继续给我想!这也是这几章需要花多一些的时间来阅读的原因——because differences inside——跟其它语言比起来!
好了,流水帐开始:

包的引入与文档查询:
=======================================
repl模式引入module:
ghci> :m + Data.List Data.Map Data.Set
仅仅import指定函数:
import Data.List (nub, sort)
import时排除某函数
import Data.List hiding (nub, sort)
当引入的module中的函数名和当前已引入的函数名有冲突时,可用qualified表示引入包内的函数必须通过指定全限定的包名来调用
import qualified Data.Map
import qualified Data.Map as M
库参考文档以及搜索引擎:
http://www.haskell.org/ghc/docs/latest/html/libraries/

http://haskell.org/hoogle
hoogle这玩意儿太厉害了,肯定是haskell编程过程中最常使用的工具,它其实也可以搭建成离线版——在自己的机器上,网站上有介绍~有空一定要弄起来

实际上,打开repl,没事儿敲敲':t',':info',':k',也是查文档的有效方式



自定义XXX how to:
=======================================
自定义包:
module Geometry  
( sphereVolume  
, sphereArea  
, cubeVolume  
, cubeArea  
, cuboidArea  
, cuboidVolume  
) where  

function definitions...


自定义type:
data Person = Person String String Int Float String String deriving (Show)

record syntax: 
data Person = Person { firstName :: String  
                      , lastName :: String  
                      , age :: Int  
                      , height :: Float  
                      , phoneNumber :: String  
                      , flavor :: String  
                      } deriving (Show)
data Person = Man {firstName::String, lastName::String} | Woman String String deriving (Show)
data Car a b c = Car { company::a,
    model::b,
    year::c
} deriving (Show)

“等号左边的是type constructor, 右边的是value constructor”

类型别名:
type PhoneNumber = String  
type Name = String  
type PhoneBook = [(Name,PhoneNumber)]

类型别名通常用来使得类型名称更接近“业务”上的理解

这么定义也是可以的(耐人寻味的Either类型):
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)

引用
infixr num 符号定义
infixl num 符号定义
上述2个关键字用来定义中缀函数,num指定其优先级,越大越高;r或l说明定义的操作符是右结合的或左结合的


自定义typeclass:
class Eq a where  
    (==) :: a -> a -> Bool  
    (/=) :: a -> a -> Bool  
    x == y = not (x /= y)  
    x /= y = not (x == y)

其中后两条等式是为了"minimal complete definition"而做,并非必须

声明某type属于某typeclass:
instance Eq TrafficLight where  
    Red == Red = True  
    Green == Green = True  
    Yellow == Yellow = True  
    _ == _ = False

由于"minimal complete definition",这里只需要列出'=='的情况,而不必列出'/='的情况

创建一个typeclass为另一个typeclass的"子typeclass":
class (Eq a, Show a) => Num a where  
  ...

实际上这里有个“多重继承的概念”,Num是Eq和Show的“子typeclass”,由于typeclass是相当于“接口”的东西,所以它并不会像cpp的多重继承一样陷入“可怕的菱形”的窘境;

Types that can act like a box can be functors.
一个type要成为Functor typeclass的一员, 需要实现的函数是fmap——将一个函数作用于“容器”的“内容物”的函数
:info Functor
class Functor f where fmap :: (a -> b) -> f a -> f b

其中'f'是一个“像容器一样”的type,比如[]或者Maybe

P.S:其实typeclass很像python的metaclass,可以对type做出一些限制与约束,但其本身并不给出任何具体实现,它只是一个“接口”



IO相关
=======================================
main = do  
    putStrLn "Hello, what's your name?"  
    name <- getLine  
    putStrLn $ "Read this carefully, because this is your future: " ++ tellFortune name


let用来绑定pure expression到名字,而'<-'用来绑定IO Action的结果到名字
return做的事情和'<-'相反

交互IO相关函数:
getLine, putStrLn, putStr, putChar, print, getChar, when, sequence, mapM, mapM_, forever, forM, getContents, interact
file操作:上述“交互IO相关函数”加上'h'前缀就是操作handler的函数
openFile, hGetContents, hClose, withFile, readFile, writeFile, appendFile, hSetBuffering, hFlush, removeFile, renameFile, doesFileExist

命令行参数相关:getArgs, getProgName

P.S:一号管道和main参数是两个概念......(做到这部分实验代码的时候才纠正掉之前对管道的错误理解)
pattern matching 在haskell程序中非常重要(matching bind)

函数定义的point-free写法跟value-binding还是不一样的(有无let关键字)

引用
if IO actions fall into the main function (or are the result in a GHCI line), they are performed

上面这段话的意思是,IO action只有当与main函数“接触”的时候,才会被执行;因为main函数是程序的入口,其实也代表了现实世界,而IO action正是用来执行“现实世界的任务”的,所以,也就只有当它真正接触到“现实世界”的时候才是它被执行的时候...(这个需要对IO做一个狠狠地思考才能真正理解)



随机数相关:
=======================================
StdGen, random, mkStdGen, randoms, randomR, randomRs, newStdGen




二进制方式操作文件:
=======================================
快速对文件操作:Data.ByteString.Lazy, Data.ByteString
pack, unpack, fromChunks, toChunks, 还有许多与Data.List相似的函数
cons和cons'的区别在于前无论如何会建立一个新chunk,后者在当前chunk未满的情况下不会建立新chunk



异常相关:
=======================================
pure code也能抛出异常——比如除以0;
但异常只能在IO code中被捕获——因为haskell是lazy的,你无法确定pure code什么时候会运行,但IO code可以
catch函数用来“捕获”异常
ioeGetFileName根据IO异常找到引起该异常的file path
一些判断异常种类的断言:
isAlreadyExistsError
isDoesNotExistError
isAlreadyInUseError
isFullError
isEOFError
isIllegalOperation
isPermissionError
isUserError

作者在文中指出:我们应该花尽可能少的精力来写I/O代码,我们的程序的主要逻辑应该被包含在'pure function'中,因为他们(pure function)的结果仅仅依赖于传入的参数是什么;当你跟'pure function'打交道时,你仅仅需要考虑它的返回值是什么就可以了(注:不需要考虑环境带来的“副作用”);“这使得你生活得更轻松”...

在pure code中抛出异常是能够做到的,但书中并没有讲,是因为作者不推荐那样做,作者更倾向于使用Either或Maybe这样的返回值来表现错误的结果,认为这是“更优雅”的方式(对不起我在函数式语言中用了“返回值”这个词,考虑到大多数读这些文字的人具有命令式编程背景,比如我,那么“返回值”应该很容易理解...)

上面这段话可以看作对函数式代码的好处的简单总结





总结:
这几章可以说是重头戏,开头我也说了,type、typeclass和pure code与IO的概念可以大致勾勒出haskell处理问题的“世界观”,是值得思考的,其实我观察它例子中的IO部分的代码,发现跟命令式编程很相似,而pure code的感觉就又完全不同了——变成函数式了——这种视角看来,我们所熟悉的命令式语言其实一直在写IO代码(它对副作用不做特殊控制,全然交给程序员解决),而haskell狠狠地使用了静态类型约束将IO代码与pure code分离开,确实能够强制性地写出“干净”的逻辑代码
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics