【haskell】趣学指南笔记(ch03-ch04)
《haskell趣学指南》概要
ch3 类型系统
haskell
中所有东西都有类型。编译器可以做静态类型推导。比如
-- Char
'a'
-- [Char]
"abc"
-- Bool
True
-- Num p => p
1
这里举例类型对应关系为
-
Char
对应字符
-
[Char]
对应字符串
,字符串是List
类型装Char
的语法糖 -
Bool
对应布尔值
-
(Num p) => p
对应数字
,这是个类型类可以理解为范型类,Num
为型别(type
),值为p
在ghci
中可以使用:t DATA
来检查DATA
的类型(type
)
其他类型
-
Int
整数 -
Integer
整数,无界限,效率(内存效率)比Int
低 -
Float
单精度浮点数 -
Double
双精度浮点数 -
Bool
布尔类型 -
Char
字符 -
Tuple
元组类型,空元组()
也是一个类型
type类型
所有的类型都是以大写字母开头,使用类变量的函数被称为多态函数
。
head :: [a] -> a
这里的a
像是范型参数一样,head
函数我们前面讲过,取一个List
类型的第一个元素。
Typeclass类型类
Typeclass
类似其他语言的interface
,用于定义行为(函数/方法
)。比如
-- :t (==)
(==) :: (Eq a) => a -> a -> Bool
==
的类型如上。
::
左边部分定义函数名,右边部分定义函数体,和我们学习lambda演算
的时候一样。(λx.x
)。
=>
符号表示类型约束
,左边表示需要约定的类型Eq a
,表示a
是Eq
类型,右边表示应用的范围,表示后面用到的a
都是Eq
类型。
->
符号表示参数分割,a -> a -> Bool
表示接受两个Eq a
作为参数,返回一个Bool
。
对于以上定义,主要理解Eq
。按interface
来理解,就是a
需要实现Eq
类型,然后才能作为==
的操作数据。还有其他的类型类。
-
Ord
可排序 -
Show
可展示 -
Read
可读入,与Show
相反。read :: (Read a) => String -> a
,read
结果需要带类型生命read "5" :: Int
-
Enum
可枚举,successer
前置和predecesor
后置 -
Bounded
上限下限(Bounded a) => a
-
Num
数字(Num a) => a
-
Integral
仅包含整数Int
和Integer
-
Floating
包含Float
和Double
因此可以简单理解haskell
的类型对应我们其他语言的数据类型(比如数字、字符串),类型类对应我们其他语言的接口(比如相等、转字符串)。
ch4 函数
可以用这样的语法定义函数
NAME :: Typeclass
NAME PARAMS = EXPRESSION
比如
add :: (Int a) => a -> a -> a
add a b = a + b
模式匹配
模式匹配是一种强大的语法结构。他映射了我们显示生活中的抽象。
lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"
上面是一个函数定义,第一行为函数类型声明,你可以使用:t lucky
检查函数的类型。该函数声明了一个a
变量,且a
是Integral
类型,输出一个String
类型。
第二行和第三行是函数体,函数体需要带上函数名字lucky
,并带上一个应用参数。比如
-- 当参数为7时,返回"LUCKY NUMBER SEVEN!"
lucky 7 = "LUCKY NUMBER SEVEN!"
有意思的是第三行的x
,这是模式匹配的强大之处,整个函数体按先后顺序先匹配第二行,如果参数为7
,则执行=
后面的表达式,如果不为7,则继续下一个匹配,下一个匹配使用x
匹配所有的7以外的数字,匹配上则执行=
后面的内容。
你可以认为模式匹配
是switch
和if..else
的加强版,因为在这个匹配里面我们还定义了内部参数x
可以用这种函数定义方式来创建factorial
函数,计算斐波那契数列。
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
而如果模式匹配失败,则函数无法处理,程序崩溃。
模式匹配不仅可以在函数定义时使用,还可以匹配元组的元素,这类似于JavaScript
的解构赋值。
-- _表示任意数据,程序不关心
first :: (a, b, c) -> a
first (x, _, _) = x
second :: (a, b, c) -> b
second (_, y, _) = y
third :: (a, b, c) -> c
third (_, _, z) = z
List
也可以用。而且List
有一个匹配方法x:xs
。其中x
表示第一个元素,xs
表示剩下的所有元素组成的List
ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
ghci> [a+b | (a,b) <- xs]
[4,7,6,8,11,4]
计算List
的长度
length' :: (Num b) => [a] -> b
length' [] = 0
length' (_:xs) = 1 + length' xs
非常强大!
类型守卫(Guards)
如果按模式匹配,每个条件都去写一行函数体,可能我们的函数定义会变得冗长。有类型守卫的方式来定义函数
myCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
| a > b = GT
| a == b = EQ
| otherwise = LT
这个语法定义一个中缀函数myCompare
,接受两个Ord
类型的参数,返回Ordering
。|
分割不同的条件,|条件 = 结果
的模式,关键词otherwise
表示任意没有满足的条件。你可以理解类型守卫为一个语法糖。
关键词where
可以在函数体最后增加where
来创建临时变量,简化我们的重复名字,定义的变量对整个函数可见。
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
这个函数where bmi weight height = weight / height ^ 2
定义了bmi
类型,该类型接受weight height
两个参数,可以理解bmi :: a -> a -> a
。然后在函数体中就可以用calcBmis xs = [bmi w h | (w, h) <- xs]
,把List
类型创建的(w, h)
传递给bmi
。
where
关键词可以使用模式匹配!
initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastname
where (f:_) = firstname (l:_) = lastname
定义两个匿名函数(lambda
)。f:_
是模式匹配处理List
类型,即匹配到firstname
(一个String
,也是[Char]
类型)的首字母,l
同理匹配lastname
的首字母。initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
中的[f]
数组类型会被替换为firstname
的第一个字符。
关键字let
let
关键字可以在一个作用域里面绑定变量,不同的Guard
之间变量不共享。let
可以在任意位置创建变量。
-- 计算圆柱体表面积
cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea + 2 * topArea
let..x.in..y.
把变量绑定到y
,也就是说你可以在in
后的表达式中替换对应变量。(规约/变量替换)
List
中也可以用
calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
这里没有用let...in...
,只有let
。这个List
有三个约束条件
-
从 xs
元组中提取w
和h
-
用 w
和h
来定义bmi
变量 -
bmi
需要>= 25.0
最终输出满足所有条件的bmi
。
case表达式
case Expression
有点像我们JavaScript
中的switch...case...
。直接看示例
head' :: [a] -> a
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x
和类型守卫的方式类似,每一行就是一个条件。->
表示左边模式匹配上的话,case
表达式返回右边的表达式。
基本模式
case expression of pattern -> result
pattern -> result
pattern -> result
...