【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
...
