Haskell学习笔记

《learn you a Haskell》这书的结构与常见的语言入门教材完全不一样。事实上,即使学到第八章,你还是写不出正常的程序…因为到现在为止还没告诉你入口点模块怎么写,IO部分也留在了最后几章才介绍。最重要的是,没有系统的总结数据类型、操作符、语句,这些知识被零散的介绍在1-8章的例子中,换句话来说,这书其实不算是很合格的教材(代码大全那种结构才更适合),不过它重点强调了FP与其他语言的思想差异,对于已经有其他语言基础的人来说,读这本书能够很快领悟到FP的妙处,对于那些并不用它来做项目(估计也很难有公司用这个做项目,太难了)的人来说,这本书很适合作为拓展阅读的小册子。另外一本《Real World Haskell》也相当有名,我还没有看,这本看完后再着手阅读吧。

第八章 自定义类型和类型类

关键字data用来构建类型,格式为

data classname = definition

左端是类型的名字,右端是该类型的取值,又称为构造子。例如:

data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)

这里表示类型Shape可以取值为Circle或Rectangle,这两个类型的构造子分别是3个、4个Float。构造子本质是一种函数,取几个参数返回一种类型。比如这里的Circle :: Float->Float->Float->Shape。关键字deriving表示派生于某类型类,即具有该类型类的性质。这里面有一些类似于面向对象中的封装与继承的概念,比如可以认为Shape是基类,Circle和Rectangle是派生类。如果某个函数对Shape通用,那么在模式匹配中就将两种派生类的实现都写出来,否则只匹配部分类即可。注意Circle和Rectangle并不是自定义类型,并不能直接使用,只有在需要Shape时,用构造子进行匹配来使用。如果构造子只有一个,可以让data类型与构造子同名,这样更加清晰。

如果说第七章的module相当于C++中的namespace,那么本章的data就相当于class,不同的是module封装的是function,而Haskell中class就是function,所以module、data和function就构成了Haskell中的用户层次。

导出格式:

module Shape

(Shape(…)

,fun1

,fun2

)

其中Shape(…)表示导出全部构造子。

Record Syntax

我们知道C++中提倡软件工程中的封装概念,尽量不让内置数据类型暴露给使用者,一般要写一大堆setxxx,const getxxx函数,虽然这些小函数往往只有一句话,但是写多了也很烦。Record Syntax是Haskell中一种用来简化此类函数书写的语法结构。在声明data时,直接标明其参数名称和对应的数据类型:

data Shape=Circle { x::Float,y::Float,radix::Float} | Rectangle { x1::Float,y1::Float,x2::Float,y2::Float} deriving (Show)

这里的语法有点像C中的位域,但是它实际的意思其实仍然是前面学过的类型解释符。Haskell会自动生成已经注明名称的参数的函数。另外在调用构造子时,也要遵从这种结构,不过x::Float换成x=1.0(即Circle {x=1.0,y=2.2,radix=3.1})。

类型参数

前面学习了值构造子,这里介绍类型构造子。值构造子显然需要明确值的类型,而类型构造子则宽松的多,比如向量,我们只需要向量的参数类型一致即可,不必明确具体的类型。换句话说,类型参数是一种"泛型"语法支持(类似C++的模板)。

比如 data Vector a = Vector a a a deriving (Show),这就是一个三维向量。类型参数a对后面的值构造子产生约束。也就是C++中的:

template <class a>

class Vector

{

    Vector(a first,a second, a last);

}

显然我们在写函数的时候需要对类型参数进行实例化,换句话说,需要对函数进行类型约束。记住,不要在data声明中添加类型约束,而是在函数中添加,因为具体执行操作的是函数,而数据类型需要比函数更加抽象。这里按着C++中写模板的思路来就很容易理解什么时候用类型构造子。

派生实例

本节介绍了从类型类从派生类型的方法。前面已经介绍过类型类本质上是一种接口要求,它描述了类型的行为一致性。在我们创建类型时,可以在值构造子后面加上deriving (Ord,Show,Read,…),来给创建的类添加接口。一旦加上了指定的类型类接口,就给予所创建的类对应的行为特性。如果加了Ord,就可以直接比较两个类(根据值构造子和参数),如果加了Show,那么就可以显示该类的参数(如果使用了syntax record,就显示出"名称=值"的格式。)

派生实例是很有用的特性,在我们设计类的时候需要明确该类支持的行为特性。这看起来有些像C++中的重载操作符,但是不需要我们自己去实现。Haskell会自动推断应该怎样实现声明的行为。

注意Haskell中True/False与C中意义完全不同【C语言中没有bool,只是单纯认为0为false,非0为true】,可以认为 data Bool = False | True deriving (Ord),所以True > False是成立的。同理Nothing总是小于Just a。

类型别名

类似typedef的语法,在Haskell中格式为

type Newtype=OldType,与typedef相似的是一般用来简化书写或者明晰概念。与typedef不同的是,type也支持Haskell的不全调用。

这里举了Either a b作为例子,Either a b是用来代替Maybe a的,当函数的返回类型可能有多种,或者函数需要根据情况返回不同的信息时,经常使用Either a b作为返回类型。

data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)

Either a b有两个值构造子,如果用了Left构造子,其返回类型就是a,否则是b,换句话说,Haskell可以返回多种类型的结果,而不像C++那样只能用结构来封装。

递归数据结构

【我本以为第8章到这就完了呢…结果后面又发现是翻译的大哥翻到一半就终止了,第八章后面还有不少,这部分是后来添补的】

考虑list,如果我们需要自己定义list类型,应该如何声明?应该这样:

data List a = Empty | Cons a (List a) deriving (Show,Read,Eq,Ord)

这里Cons相当于运算符 " : ",显然这里的List定义是递归的——等号的左右两端都存在List。这样,我们就可以像使用:一样使用Cons来构造List,如 3 `Cons` 4 `Cons` Empty。

也可以自定义操作符,使用infixr来确定操作符的优先级和左、右结合性,注意这里可以定义任意操作符,这一点和C++中的重载操作符有本质的不同。书中以操作符 :-: 为例介绍了使用方法。

下面介绍了一个二叉搜索树的生成作为例子:

data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)

一个二叉树是一个空树,或者一个由根节点和其左子树与右子树构成的树。下面是一些常用操作函数的实现,这里从略。

TypeClass102

本节介绍如何自定义类型类,类型类与常见的过程式或者面向对象模型的编程语言并无相似之处,不过可以和C++中的重载操作符对比参考。

我们使用关键字class来定义类型类,使用关键字instance来定义类型类的实例。

class Eq a where

(==) :: a->a->Bool

(/=) :: a->a->Bool

x == y = not x /= y

x /= y = not x==y

instance Eq TrafficLight where

instance下面就是详细解释该接口的实现。在不想使用默认的从类型类派生得到的行为时,必须使用instance来自定义数据对于该接口的行为方式。对于Maybe类型的实例,格式是

instance (Eq m) => Eq (Maybe m) where

注意这里需要对m添加类型约束。

可以在GHCI中使用:info 得到类型类、类型和类型构造子的详细信息。

A yes-no typeclass

本节讲了一个类型类的实例,它用来完成各种类型向Bool的缺省转换(如C的非零为True,0为False)。

class YesNo a where

    yesno :: a -> Bool

实现:

instance YesNo Int where

    yesno 0 = False

    yesno _ = True

instance YesNo [a] where

    yesno [] = False

    yesno _ = True

instance YesNo Bool where

    yesno = id

…其他略

注意id是标准库函数。

函数子类型类

函数子类型类(Functor class)指的是可以被被映射的函数满足的接口。

class Functor f where

    fmap :: (a->b) -> f a -> f b

注意定义中的f并非类型,而是类型构造子。换言之,函数子类型类的实例参数必须是类型类构造子而不能是具体的类型,那么可见这个类型类是容器的接口(如Maybe或[]等拥有1个以上值构造子的类型)。如果f有两个以上的参数,那么只能用函数的不完全调用格式,仅保留一个参数进行处理(如Either a)

种类和一些类型相关的东西

本节对类型(type)的知识做了扩展,这里将类型本身分为具体类型和不完全类型。

这种类型的类型被称为种类(kinds),可以在GHCI中使用:k来对类型进行种类的分析。

GHCI使用 " * "表示具体的类型,如果不是具体类型,就是可以通过一个或多个参数得到具体类型的不完全类型。

如果理解了类型构造子本身也是函数这一点,本节的内容还是比较容易理解的。

第九章 输入与输出

第九章开始就没有中文的翻译了,只能看英文资料,英文9-14章戳我

到了第九章,我们终于可以写Hello World了!一本教材讲到大半才讲输入输出的,也算是比较罕见了,呵呵。

在Haskell中,我们并不能像在命令式编程中一样随意改变非const变量的值。Haskell保持着这样一个特性:对于一个函数,只要它的调用参数不变,那么它返回的值总是不变。Haskell不会试图改变已经确定的变量,而是试图返回新的变量。那么这里出现一个问题:如果Haskell并不改变任何变量,那么它就无法输出——因为输出会改变屏幕。为此,Haskell设计了一种机制将非纯函数式编程(即与输入输出打交道的部分)与函数式编程(即前面八章介绍的内容)隔离开来,这里将这种机制称为side-effects,直译为边界效应。

Hello World!

Hello World总是每种语言必须提到的东西。对于Haskell,输出Helloword只需要一行代码,嗯,不愧是优雅与简洁的典范。

main = putStrLn "Hello world!"

main表示主函数,所有涉及IO的函数都在main中执行。所以main函数经常被写作main:: IO (),当然()也可以换成其他的返回类型。

putStrLn函数的解释是

putStrLn :: String->IO ()

也就是输入一个字符串,执行一个IO action,返回一个空的tuple。

因为IO行为是非纯函数行为,所以Haskell设计了do块来将所有的非纯函数进行封装,最后通过<-操作符将IO取得的值绑定到一个变量上。do块中,除了最后一个IO action 其他的均可绑定到一个变量上,不过如putStrLn这种函数的返回值肯定是(),所以绑定没什么意义。最后一个IO action会将返回值绑定给main本身。do块这种行为方式类似于verilog中的begin…end语句块。

如果不涉及IO,仍然使用前面学过的let … in…来直接绑定变量,不过这里in可以省略,缺省成为整个do块中有效。

return语句:在haskell中return语句只能在IO块中使用,它表示一个IO行为,输出一个可以通过变量绑定的值。return语句并不能从该段程序中返回。我们只有在需要执行一次什么都不做的IO或者不希望返回最后一个IO action取得的值时使用return语句。

main:main本身即是一个IO函数,所以可以通过在main函数结尾调用它来递归该函数。

其他IO函数:

函数原型

解释

putStr

类似putStrLn,但尾部不输出换行符

putChar/getChar

输出/入字符

print:: Show a => a -> IO ()

输出一切属于show类型类的数据

sequence:: Monad m => [m a] -> m [a]

执行参数1中的I/O动作,返回动作的结果

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

map的I/O版,相当于sequence . map

mapM_ :: Monad m => (a -> m b) -> [a] -> m ()

同上,只是不再返回I/O动作的执行结果

注意map一个I/O动作到一个list中,并不会真正执行这个list中的动作。想要真正执行,必须使用sequence函数,当然,方便起见,可以使用mapM或mapM_。

这一块介绍了Control.Monad内的几个函数,when函数取一个布尔值和一个I/O动作作为参数,如果bool值为真,执行该动作,否则返回一个什么都不执行的I/O动作;forever永久执行参数中的I/O动作;forM类似于mapM,只是参数的顺序颠倒。

文件与流

其实I/O这一块Haskell与命令式语言并无不同,要注意的只是do块的位置和I/O函数结果的绑定。对于文件I/O,haskell与C基本一致,常用函数如下:

函数原型

解释

getContents :: IO String

从标准流读入数据直到EOF(Ctrl+D)

interact :: (String -> String) -> IO ()

对输入执行参数1的函数,输出结果

openFile :: FilePath -> IOMode -> IO Handle

System.IO,打开文件,选择方式,返回句柄

hGetContents :: Handle -> IO String

根据句柄返回文件内容

hClose :: Handle -> IO ()

根据句柄关闭文件

withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r

整合的一个函数,打开文件并使用参数3处理文件,完成后关闭文件

hGetLine/hPutStr/hputStrLn/hGetChar

大体同标准流,只是参数中多了文件句柄

readFile :: FilePath -> IO String

读取文件

writeFile :: FilePath -> String -> IO ()

写入文件,模式为截断

appendFile :: FilePath -> String -> IO ()

写入文件,模式为追加

getTemporaryDirectory :: IO FilePath

System.Directory,读取临时文件夹路径

openTempFile :: FilePath -> String -> IO (FilePath, Handle)

System.IO,打开临时文件,返回一个tuple

removeFile/renameFile

System.Directory,删除、重命名文件,参数是路径不是句柄

注意这里的读取文件相关函数都是惰性的。以上青色字体来自System.IO库,没有注明的来自Precluded库。使用句柄做参数的函数均以h开头。

缓冲控制:如果需要修改编译器默认的缓冲机制,可以使用函数hSetBuffering来修改,使用hFlush来强制刷新缓冲区

这里介绍了Unix下管道操作符 | 的使用。简单来说,可以通过管道操作符将上一个动作的输出作为下一个动作的输入。

命令行参数

同C语言程序一样,Haskell也是可以接受命令行参数的。C语言将命令行参数作为main函数的参数传递,而Haskell主要使用两个函数来取得用户输入的参数,import System.Environment

getArgs :: IO [String]

取出所有参数

getProgName :: IO String

取得程序名称

另外后面给了错误退出的函数(类似<stdlib>中的exit函数):errorExit。

随机数发生器

随机数发生器在任何语言中都是标准库自带的函数/类。虽然Haskell要求纯函数的输入一定时,输出固定,但是实际上几乎所有的语言中随机数发生器生成的都是伪随机数,所以Haskell这个特性并不意味着其实现比一般的语言困难。相关函数如下(import System.Random):

random :: (RandomGen g, Random a) => g -> (a, g)

参数给出一个随机数种子,返回一个随机数和一个新的种子

mkStdGen :: Int -> StdGen

以一个整数为参数,生成一个标准的种子

randoms :: (RandomGen g, Random a) => g -> [a]

根据种子生成一个无限长的随机序列

randomR :: (RandomGen g, Random a) => (a, a) -> g -> (a, g)

参数1的pair限制了最后取得随机数的范围

randomRs :: (RandomGen g, Random a) => (a, a) -> g -> [a]

根据参数1生成规定范围的无限长list

getStdGen :: IO StdGen

取得一个全局的随机数发生器种子

newStdGen :: IO StdGen

刷新全局随机数发生器

  

注意random函数的返回类型可以是任何类型,所以在使用的时候必须在后面加上类型约束::(type1,type2)作为随机数和种子的类型,如果使用StdGen的种子,则一般返回类型为(Int,StdGen)。

如果不执行newStdGen,那么getStdGen总是返回同样的种子。

另外这里还介绍了read加入了错误处理的版本reads,后者再不能读取参数时将返回空list。

二进制字符串

不清楚这个翻译是否合适,总之Bytestrings主要介绍二进制读写文件的方法。不同于C语言,这里有两个版本的二进制读取,一个是严格的非惰性版本,另外一个是惰性版本,分别来自Data.ByteString和Data.ByteString.Lazy. 对于lazy版本,这里和前面介绍的文件IO函数的实现也有所不同——它每次最少读取64K字节的东西。大体来讲ByteString的相关函数与Data.List中的函数接口一致,不过在类型约束中用ByteString代替了[a],用Word8代替了a。函数pack将Word8打包成ByteString,unpack用于解包;fromChunks将严格版(非惰性)转换为惰性版,toChunks则相反;cons和cons'用来取代list的 :操作符,注意后者适用于非惰性版;还有其他一些函数,对应于list中的某些函数,这里就不列举了。

转载于:https://www.cnblogs.com/livewithnorest/archive/2012/08/02/2620718.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377284.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

组合问题 已知组合数_组合和问题

组合问题 已知组合数Description: 描述&#xff1a; This is a standard interview problem to make some combination of the numbers whose sum equals to a given number using backtracking. 这是一个标准的面试问题&#xff0c;它使用回溯功能将总和等于给定数字的数字进…

可变参数模板、右值引用带来的移动语义完美转发、lambda表达式的理解

可变参数模板 可变参数模板对参数进行了高度泛化&#xff0c;可以表示任意数目、任意类型的参数&#xff1a; 语法为&#xff1a;在class或者typename后面带上省略号。 Template<class ... T> void func(T ... args) {// }T:模板参数包&#xff0c;args叫做函数参数包 …

BI-SqlServer

一.概述 SqlServer数据仓库ETL组件 IntegrationServiceOLAP组件 AnalysisService报表 ReportingServiceMDX(查多维数据集用的)和DMX(查挖掘模型用的)。二.商业智能-Analysis Services 项目 构建挖掘模型1构建挖掘模型2构建挖掘模型3三.商业智能-SqlServerAnalysis-Asp.net WebS…

python 子图大小_Python | 图的大小

python 子图大小In some cases, the automatic figure size generated by the matplotlib.pyplot is not visually good or there could be some non-acceptable ratio in the figure. So, rather than allowing a pyplot to decide the figure size, we can manually define t…

《设计模式整理》

目录常见设计模式如何保证单例模式只有一个实例单例模式中的懒汉与饿汉模式OOP设计模式的五项原则单例模式中的懒汉加载&#xff0c;如果并发访问该怎么做常见设计模式 单例模式&#xff1a; 单例模式主要解决了一个全局使用的类频繁的创建和销毁的问题。 单例模式下确保某一个…

JSON学习资料整理

1.什么是JSON JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于JavaScript的一个子集。 JSON采用完全独立于语言的文本格式&#xff0c;但是也使用了类似于C语言家族的习惯&#xff08;包括C, C, C#, Java, JavaScript, Perl, Python等&#xff09;。这些…

OSI七层模型及其数据的封装和解封过程

OSI(Open System Interconnection)参考模型把网络分为七层: 1.物理层(Physical Layer) 物理层主要传输原始的比特流,集线器(Hub)是本层的典型设备; 2.数据链路层(Data Link Layer) 数据链路层负责在两个相邻节点间无差错的传送以帧为单位的数据,本层的典型设备是交换机(Switch)…

rss聚合模式案例_RSS的完整形式是什么?

rss聚合模式案例RSS&#xff1a;真正简单的联合 (RSS: Really Simple Syndication) RSS is an abbreviation of Really Simple Syndication. It is also called Rich Site Summary. It is quality attainment for the syndication of collection of web content and used to di…

《MySQL——38道查询练习(无连接查询)》

目录一、准备数据1、创建数据库2、创建学生表3、创建教师表4、创建课程表5、创建成绩表6、添加数据二、查询练习1、查询 student 表的所有行2、查询 student 表中的 name、sex 和 class 字段的所有行3、查询 teacher 表中不重复的 department 列4、查询 score 表中成绩在60-80之…

工作经常使用的SQL整理,实战篇(一)

工作经常使用的SQL整理&#xff0c;实战篇&#xff08;一&#xff09; 原文:工作经常使用的SQL整理&#xff0c;实战篇&#xff08;一&#xff09;工作经常使用的SQL整理&#xff0c;实战篇&#xff0c;地址一览&#xff1a; 工作经常使用的SQL整理&#xff0c;实战篇&#xff…

XPth和XSLT的一些简单用法

&#xff08;目的在于让大家知道有这个东西的存在&#xff09; XPath:即XML Path语言(Xpath)表达式使用路径表示法(像在URL中使用一样)来为XML文档的各部分寻址&#xff01; 关于XPath如何使用了&#xff0c;我们来看看&#xff01;当然这里面的代码只是入门&#xff0c;更深层…

isc dhcp_ISC的完整形式是什么?

isc dhcpISC&#xff1a;印度学校证书 (ISC: Indian School Certificate) ISC is an abbreviation of the Indian School Certificate. It alludes to the 12th class examination or higher secondary examination conducted by the Council for the Indian School Certificat…

《MySQL——连接查询》

内连接&#xff1a; inner join 或者 join 外连接 1、左连接 left join 或 left outer join 2、右连接 right join 或 right outer join 3、完全外连接 full join 或 full outer join 图示理解 全连接 创建person表和card表 CREATE DATABASE testJoin;CREATE TABLE person (…

win7下 apache2.2 +php5.4 环境搭建

这篇文章很好 没法复制 把链接粘贴来http://www.360doc.com/content/13/0506/13/11495619_283349585.shtml# 现在能复制了&#xff1a; 把任何一篇你要复制、却不让复制的文章收藏入收藏夹(直接CtrlD,确定) 2在收藏夹中&#xff0c;右击刚才收藏的那个网址&#xff0c;点属性 3…

HDU_1533 Going Home(最优匹配) 解题报告

转载请注明出自cxb:http://blog.csdn.net/cxb569262726 题目链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1533 说实话&#xff0c;这个题目刚开始还真看不出是完备匹配下的最大权匹配&#xff08;当然&#xff0c;这个也可以用网络流做。&#xff08;应该是添加…

c#中 uint_C#中的uint关键字

c#中 uintC&#xff03;uint关键字 (C# uint keyword) In C#, uint is a keyword which is used to declare a variable that can store an integral type of value (unsigned integer) the range of 0 to 4,294,967,295. uint keyword is an alias of System.UInt32. 在C&…

《MySQL——事务》

目录事务的必要性MySQL中如何控制事务手动开启事务事务的四大特征事务的四大特征事务开启方式事务手动提交与手动回滚事务的隔离性脏读现象不可重复读现象幻读现象串行化一些补充使用长事务的弊病commit work and chain的语法是做什么用的?怎么查询各个表中的长事务&#xff1…

运行在TQ2440开发板上以及X86平台上的linux内核编译

一、运行在TQ2440开发板上的linux内核编译 1、获取源码并解压 直接使用天嵌移植好的“linux-2.6.30.4_20100531.tar.bz2”源码包。 解压&#xff08;天嵌默认解压到/opt/EmbedSky/linux-2.6.30.4/中&#xff09; tar xvjf linux-2.6.30.4_20100531.tar.bz2 -C / 2、获取默认配置…

ArcCatalog ArcMap打不开

原来是因为&#xff1a; 连接了电信的无线网卡 关掉即可 启动ArcCatalog之后再开启无线网卡 没问题&#xff01;转载于:https://www.cnblogs.com/ccjcjc/archive/2012/08/21/2649867.html

Python熊猫– GroupBy

Python熊猫– GroupBy (Python Pandas – GroupBy) GroupBy method can be used to work on group rows of data together and call aggregate functions. It allows to group together rows based off of a column and perform an aggregate function on them. GroupBy方法可用…