Haskell学习笔记(一)

这一系列的笔记主要参考中文版的 Real World Haskell,这篇博文作为本系列的第一篇,先介绍一下Haskell的语法和一些基本知识。


进入/退出交互模式ghci

安装好Haskell,命令行敲ghci进入交互模式

Prelude> :set prompt “ghci>”
ghci>:q                       -- 退出

基本算术

ghci> 1 + 1
2

ghci> (+) 1 1
2

ghci> 2 + (-1)     -- 负数是通过唯一的一元操作符'-'得到,-1的括号不能省略
1

ghci> True && False
False

ghci> True || False
True

ghci> :info (+)      -- 操作符的信息可以通过':info'方式查看
class Num a where
  (+) :: a -> a -> a
  ...
    -- Defined in ‘GHC.Num’
infixl 6 +           -- infixl代表加法是左结合的,优先级为6

ghci> :info (^)
(^) :: (Num a, Integral b) => a -> b -> a   -- Defined in ‘GHC.Real’
infixr 8 ^        -- 乘方是右结合的,指数b的类型是Integer,优先级为8

ghci> not True    -- not是一个内置函数,不可以通过:info查看其信息
False

ghci> 1 == 1
True

ghci> 1 /= 1
False

ghci> let e = exp 1     -- 添加绑定
ghci> e ^ 1
2.718281828459045
ghci> e ** pi      -- 指数不是整数时,需要更换操作符
23.140692632779263

列表和元组

列表表示一系列相同类型的元素集合,元组则可以包括不同类型的元素,void在Haskell中被实现为一个空的元组

ghci> [1,2]    -- 列表
[1,2]

ghci> [1..5]
[1,2,3,4,5]

ghci> [1,3..9]
[1,3,5,7,9]

ghci> [1] ++ [2,3]  -- 列表的连接
[1,2,3]

ghci> 1 : [2,3]  -- 元素和列表的连接,类似cons
[1,2,3]

ghci> head [1,2,3]   -- 返回列表的第一个元素
1

ghci> tail [1,2,3]   -- 返回除去第一个元素后的剩余列表
[2,3]

ghci> last [1,2,3]   -- 返回列表的最后一个元素
[3]

ghci> init [1,2,3]   -- 返回除去最后一个元素的剩余列表
[1,2]

ghci> reverse [1,2,3]
[3,2,1]

ghci> take 2 [1,2,3]  -- 返回列表的前n个元素组成的新列表
[1,2]

ghci> drop 1 [1,2,3]  -- 返回丢弃前n个元素后得到的新列表
[2,3]

ghci> null []
True

ghci> (1, "Hello")    -- 元组
(1, "Hello")

ghci> fst (1,"Hello")  -- 取元组的第一个元素
1

ghci> snd (1,"Hello")  -- 取元组的第二个元素
"Hello" 

字符串

Haskell的字符串和C的非常相似,用单引号表示单个字符,双引号代表字符串,字符串实际就是单个字符的列表,但不需要像C中用'\0'结尾

ghci> 'a'
'a'

ghci> "Hello World"
"Hello World"

ghci> ['H','e','l','l','o']
"Hello"

ghci> "" == []
True

ghci> 'a' : "bc"
"abc"

ghci> "a" ++ "bc"
"abc"

查看表达式类型

在交互式界面下,我们有两种方法查看表达式的类型:

ghci> :set +t
ghci > 'a'
'a'
it :: Char    -- it是交互模式下存储运算结果的变量
ghci> :unset +t
ghci> 'a'
'a'

ghci> :type 'a'
'a' :: Char
ghci> :type 1 + 1
1 + 1 :: Num a => a    -- :type执行静态的类型推导,并不会显示运算结果2的类型

分数

ghci> :m +Data.Ratio      -- ':m'代表载入模块
ghci> :set +t
ghci> 1 % 2               -- 分数1/2
1 % 2
it :: Integral a => Ratio a

整数范围

Haskell中的整数是不限大小的,你可以写出下面的式子:

ghci> 313 ^ 15
27112218957718876716220410905036741257

自定义函数

在ghci中定义函数的语法和标准Haskell有所不同,我们在后者环境下定一个函数add,并加载到ghci环境中:

-- this is in myDrop.hs
myDrop n xs = if n <= 0 || null xs
              then xs
              else myDrop (n - 1) (tail xs)
-- back to ghci mode
ghci> :load myDrop.hs
ghci> myDrop 2 "cat"
"t"

副作用

避免函数的副作用有很多好处,在Haskell中,定义的函数默认都是无副作用的(纯函数),有副作用的函数在Haskell中类型会带有IO标签:

ghci> :type readFile
readFile :: FilePath -> IO String

纯度减轻了理解一个函数所需的工作量。一个纯函数的行为并不取决于全局变量、数据库的内容或者网络连接状态。纯代码(pure code)从一开始就是模块化的:每个函数都是自包容的,并且都带有定义良好的接口。
将纯函数作为默认的另一个不太明显的好处是,它使得与不纯代码之间的交互变得简单。一种常见的 Haskell 风格就是,将带有副作用的代码和不带副作用的代码分开处理。在这种情况下,不纯函数需要尽可能地简单,而复杂的任务则交给纯函数去做。
软件的大部分风险,都来自于与外部世界进行交互:它需要程序去应付错误的、不完整的数据,并且处理恶意的攻击,诸如此类。Haskell 的类型系统明确地告诉我们,哪一部分的代码带有副作用,让我们可以对这部分代码添加适当的保护措施。
通过这种将不纯函数隔离、并尽可能简单化的编程风格,程序的漏洞将变得非常少。

变量

在Haskell中,一个变量和一个值建立绑定之后,这个变量的值就不可以再被修改了,举个例子,下面的代码是无法通过编译的:

-- file: ch02/Assign.hs
x = 10
x = 11

命令式语言中的变量是和一个内存地址建立了绑定,但地址里写的内容是可以反复修改的,所以在命令式语言中不同时刻读取同一变量的值可能会得到不同的结果。在Haskell中,变量是和值建立的绑定,一个变量被绑定后,我们总能把变量替换成绑定的值。

惰性求值

Haskell中用于追踪未求值表达式的记录被称为块。编译器通过创建块来延迟表达式的求值,直到这个表达式的值真正被需要为止。如果某个表达式的值不被需要,那么从始至终,这个表达式都不会被求值。在Haskell中可以定义无限长的数组,并可以对其进行一些操作:

-- mycons.hs
mycons n = n : mycons (n + 1)
l = mycons 0       -- l是自然数集合

-- ghci
ghci> :load mycons.hs
ghci> take 3 l
[0,1,2]
ghci> (!!) l 0    -- !!是根据下标取列表元素的操作,从0开始
0
ghci> takeWhile(\x->x<3) l
[0,1,2]

Haskell还自带了一个repeat::a->[a]函数,他接受一个参数x,返回一个无限个x组成的列表。

类型系统

Haskell的类型系统基于简洁而强大的 System F,这里我们不深究其实现,只列出Haskell类型系统的三个特性:

  • 强类型:强类型语言不会做隐式的类型转换。举个例子,如果将一个整数值作为参数传给了一个接受浮点数的函数,C 编译器会自动且静默(silently)地将参数从整数类型转换为浮点类型,而 Haskell 编译器则会引发一个编译错误。
  • 静态类型:静态类型的语言会在编译阶段求出所有变量和表达式的类型,并拒绝执行任何类型错误的程序。同时,Haskell还提供了 typeclass 机制让程序员实现一些动态类型语言的功能。
  • 自动推导:如果没有自动推导,那么一门静态语言就要求程序员事无巨细地显示声明所有变量的类型以及类型之间的关系,就像Java采用的norminal type system。和很多函数式语言一样,Haskell可以几乎自动推导出所有表达式的类型(有时需要程序员提供一些必要的提示)。这使得程序员在编码时几乎不需要写任何的类型信息(写出来当然也是可以的),但同时编译时期也有强大的类型检查保证Haskell代码的类型安全。

多态

Haskell中列表中的值可以是任意类型,所以我们可以称列表为类型多态(polymorphic)的。任何列表相关的操作函数应当也是多态的,看一个例子:

ghci> :type last
ghci> last :: [a] -> a

这里last的类型中包含了一个类型变量a,在不同情况下它会具化成不同的类型,当last函数应用于一个字符串的时候,其类型就是[Char]->Char,而应用于一个整数数组时,last的类型就是[Integer]->Integer

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容