vlambda博客
学习文章列表

【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,表示aEq类型,右边表示应用的范围,表示后面用到的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仅包含整数 IntInteger
  • Floating包含 FloatDouble

因此可以简单理解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变量,且aIntegral类型,输出一个String类型。

第二行和第三行是函数体,函数体需要带上函数名字lucky,并带上一个应用参数。比如

-- 当参数为7时,返回"LUCKY NUMBER SEVEN!"  
lucky 7 = "LUCKY NUMBER SEVEN!"  

有意思的是第三行的x,这是模式匹配的强大之处,整个函数体按先后顺序先匹配第二行,如果参数为7,则执行=后面的表达式,如果不为7,则继续下一个匹配,下一个匹配使用x匹配所有的7以外的数字,匹配上则执行=后面的内容。

你可以认为模式匹配switchif..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有三个约束条件

  1. xs元组中提取 wh
  2. wh来定义 bmi变量
  3. 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  
                                 ...