560 likes | 859 Views
如何编辑 Haskell 程序? 如何运行解释器? 如何在解释器下运行一个 Haskell 程序?. 类型和函数. 一个程序(函数)形如 f :: A -> B 或者 f :: A -> B -> C, A, B, C 表示函数的输入数据集合和输出数据集合 例如 , Int 表示整数集合 double :: Int -> Int mymax :: Int -> Int -> Int 问题: 如何表示输入数据和输出数据? Haskell 提供了哪些表示数据的类型? 如何定义函数,或者定义函数的语法如何?. 第二章 基本类型与定义. 本章介绍
E N D
如何编辑Haskell程序? • 如何运行解释器? • 如何在解释器下运行一个Haskell程序?
类型和函数 • 一个程序(函数)形如 f :: A -> B 或者 f :: A -> B -> C, A, B, C 表示函数的输入数据集合和输出数据集合 • 例如, Int 表示整数集合 double :: Int -> Int mymax :: Int -> Int -> Int • 问题: 如何表示输入数据和输出数据?Haskell提供了哪些表示数据的类型? • 如何定义函数,或者定义函数的语法如何?
第二章 基本类型与定义 • 本章介绍 • 基本类型 • 基本函数定义
类型 一个类型是一些值的集合,这些值均支持相同的运算。 例如,布尔类型Bool • 包含两个逻辑值: True 和 False; • 支持&& (与),||(或),not(非)等运算。
类型 在Haskell中, 每个合理的表达式都有它所属的类型,写作 e :: t 类型t可以在编译时由系统的类型推导程序( type inference )计算出。 表达式e具有类型t
例如, double :: Int -> Int, 所以 double 4 :: Int 类型推导规则 一个基本的类型推导规则是: f :: A -> B e :: A f e :: B
类型安全(Type Safety) 但是 是错误的, 称之为类型错误( type error). double True 所有的类型错误均可以在编译时发现,这使得函数程序是类型安全的: well-typed programs never go wrong (no errors at run time)!. 类型避免了一类错误的发生。
基本类型 Haskell提供了一些基本类型,用户也可定义自己的类型。 布尔类型: Bool Bool具有两个值: True和False. 它们表示一个测试或者条件是否成立的两个可能的结果。 布尔运算(operators)包括: && (and), | | (or), not, == (equal) 例如, True | | False = True not True = False 可以查看 Prelude.hs中这些函数的定义。
类型: Bool 运算&& 和| | 称为中缀运算,其类型为 : (&&) :: Bool -> Bool -> Bool (| |) :: Bool -> Bool -> Bool 运算| | 是“可兼或”。我们可以定义“不可兼或”: exOr :: Bool -> Bool -> Bool exOr x y = (x | | y) && not (x && y) 另一种定义exOr的方式: exOr True x = not x exOr False x = x 注意(&&)中的括号不可少
类型: Integers 1, 2, 3, … :: Int 此类型包括介于–2^31 和2^31 –1间的整数。 其中的运算包括: 3 + 4 = 7 3*7 = 21 2^3 = 8 关系运算: >, <=, ==, <, <=, /= 试着在hugs键入:t (+)和 :t div查看这些运算的类型。 整数除法 div 7 2 = 3 mod 7 2 = 1
运算符 运算只是“中缀”函数. (+) :: Int -> Int -> Int 我们可以用两种方式使用+: (+) 3 2 = 5 3 + 3 = 5 一个二元函数也可以使用`(backquote)转变为中缀运算: 7 `div` 2 3 7 `mod` 2 1 Infix version of div and mod
重载(Overloading) 我们使用同一个符号(==)比较整数的相等,布尔值的相等,这也表明(==)具有下列类型: Int -> Int -> Bool Bool -> Bool -> Bool 这种现象称为重载(overloading): 使用同一个符号或者名表示不同类型上的(相似)运算. 更多的例子: (+), (-), (*) 等.
使用条件的定义 如何定义求两个整数中较大的数, max? max :: Int -> Int -> Int max x y | x >= y = x | otherwise = y 如果条件为True, 则max x y 等于 x 如果条件为 False, 那么 max x y 等于 y
使用条件(guards) 一般地,使用条件定义的函数具有下列格式: name x1 x2 … xn | g1 = e1 | g2 = e2 … | otherwise = e 函数名 形式参数 结果 条件
条件 (2) • 条件必须是布尔类型的表达式,即它们的值为True 或者False. • 给定一组参数,Haskell 自上而下计算条件的值,然后将第一个条件之值为True对应的结果作为函数在相应输入下的结果。所以,条件的次序很重要。 • 定义中的条件往往覆盖所有的情况。 习题:定义函数 maxThree ,它返回三个整数中的 最大值.
An example using guards maxThree :: Int -> Int -> Int -> Int maxThree x y z | x >= y && x >= z = x | y >= z = y | otherwise = z Or using a predefined function: maxThree’ x y z = max (max x y) z Exercise: compute maxThree 6 (4+3) 5.
条件表达式 Haskell 提供下列条件表达式: if condition then m else n 其语义是, 如果条件condition (of type Bool) 的值为True, 则表达式的值为m, 否则为n, 其中m和n必须具有相同的类型. 例如, max x y = if x >= y then x else y Exercise. Define min :: Int -> Int -> Int
使用定义做计算 • 函数调用的计算过程 • 用函数定义的右式代替函数; • 使用实在参数代替形式参数 • double :: Int -> Int • double x = 2*x • double 3 2 *3 • 6
计算顺序 一个表达式的计算顺序可能有多种,但是,其计算结果不变. double 5 10 double (2+3) 2*(2+3) The evaluation order does not matter.
递归(Recursion) 能否用fac (n-1) 计算fac n? 问题: 定义函数fac :: Int -> Int fac n = 1 * 2 * … * n 假如我们已知fac (n-1),如何计算fac n? fac n = 1 * 2 * … * (n-1) * n = fac (n-1) * n
阶乘表 已知 fac 0 = 1. n fac n 0 1 1 1 2 2 3 6 4 24 ... 所以 fac 1 = 1 * 1. 所以 fac 2 = 1 * 2. 所以 fac 3 = 2 * 3.
Factorial的递归定义 递归基 fac :: Int -> Int fac 0 = 1 fac n | n > 0 = fac (n-1) * n 递归情况
计算 Factorials fac :: Int -> Int fac 0 = 1 fac n | n > 0 = fac (n-1) * n fac 4 ?? 4 == 0 False ?? 4 > 0 True fac (4-1) * 4 fac 3 * 4 fac 2 * 3 * 4 fac 1 * 2 * 3 * 4 fac 0 * 1 * 2 * 3 * 4 1 * 1 * 2 * 3 * 4 24
原始递归 • 原始递归定义规则: • 利用f (n-1)定义f n(n > 0). • 直接定义f 0. • 如何确定一个函数是否存在递归定义: • 可否将 f n 的计算转换为类似的 f (n-1)的计算,但是参数更简单; • 能否直接定义最简单的情况 f 0;
Quiz Define a function power so that power x n == x * x * … * x n times (Of course, power x n == x^n, but you should define power without using ^).
Quiz Define a function power so that power x n == x * x * … * x n times Don’t forget the base case! power x 0 = 1 power x n | n > 0 = power x (n-1) * x Since this equals (x * x * … * x) * x n-1 times
一般递归(General Recursion) 如何利用f x之值计算f n 之值, 其中 x小于n? Example x^(2*n) == (x*x)^n x^(2*n+1) == (x*x)^n * x
一般递归的幂函数 注意不要漏掉递归基 power :: Int -> Int -> Int power x 0 = 1 power x n | n `mod` 2 == 0 = power (x*x) (n `div` 2) | n `mod` 2 == 1 = power (x*x) (n `div` 2) * x 两种递归的情况 这个定义有何特点呢?
Comparing the Versions First Version power 3 5 power 3 4 * 3 power 3 3 * 3 * 3 power 3 2 * 3 * 3 * 3 power 3 1 * 3 * 3 * 3 * 3 power 3 0 * 3 * 3 * 3 * 3 * 3 1 * 3 * 3 * 3 * 3 * 3 243 Second Version power 3 5 power 9 2 * 3 power 81 1 * 3 power 81 0 * 81 * 3 1 * 81 * 3 243 4 function calls, 4 multiplications. 6 function calls, 5 multiplications.
使用递归应注意的问题 • 使用递归可以将一个问题转换为类似的之问题,递归是程序设计语言中一种有力的解决问题方法; • 一般的问题比简单的问题更容易解决,因为递归方法可以解决更复杂的问题; • 使用递归时应该注意保证“程序终止”( termination),即定义一个问题规模,在递归基的情况问题规模为0,其他情况下大于0,而且在定义中,每次递归调用的规模均严格减小。
字符类型: Char Haskell提高了字符类型Char. 字符类型的值是单引号扩起来的单个字符,如 ‘a’, ‘3’ 等. 一些特殊字符: ‘\t’ (tab), ‘\n’ (newline), ‘\\’ (backslash), ‘\’’(single quote), ‘\”’(double quote)
串类型: Strings 一个串是用双引号括起来的一些字符,如 “Hello World!” :: String 串上的运算: “Hello “ ++ “world!” “Hello World!” show (max 5 (8+4)) “12” 一段文本的类型
实数类型 1.4, 14.732, 3.14159, -33.5 :: Float 算术运算: +, -, *, /, ** (exponential) 关系运算: >, >=, <, <=, ==, /=
列表类型 包括在一对方括号之间的一些同类型值或表达式 [1, 2, 3], [5] :: [Int] 运算: [1, 3] ++ [2, 4, 3] [1, 3, 2, 4, 3] head [1,3,5] 1 last [1, 3, 5] 5 注意: String = [Char] 整数列表类型
Lists 一般地,对于任意类型t, [t] 是类型为t的值构成的列表类型。 例如, [False, True, False], [] :: [Bool] [‘H’, ‘e’, ‘l’, ‘l’,’o’,’!’] :: [Char] (or String) [“Hello”, “world”] :: [[Char]] (or [String]) 空列表 列表的列表
多元组 • (True, False) :: (Bool, Bool) • (“Bob’, 23, ‘M’) :: (String, Int, Char) • 一般地, 对于任意类型 t1, …, tn, (t1, …, tn) 是一个类型,其元素包括所有形如 (a1, …, an) 的值,其中 ai 具有类型ti.
运算符 运算符名使用下列字符构成的串: ! # $ % & * + . < = > ? \ ^ | : - ~ 不过,冒号’:’不可作为首字符。 例如: (/\) :: Bool -> Bool -> Bool x /\ y = if x then y else x
Associativity and Binding Powers • To change associativity or binding power: infixr 7 (/\) (right associative) or infix 7 (/\) (non-associative)
命令类型 writeFile :: FilePath -> String -> IO( ) writeFile “myfile” “Hello” :: IO( ) readFile “myfile” :: IO String 命令类型,没有返回结果 将串 “Hello!” 写入文件 myfile. 由文件 myfile 中读入一个串,其返回结果是一个串 返回一个串的命令类型
函数类型 f :: A -> B e :: A F e :: B double是类型为Int -> Int的函数。 一般地, t1 -> t2 是将类型t1之值映射到类型t2的值的函数的类型。 double :: Int -> Int double x = x + x
Function types • 运算 “->” 是右结合的,即类型 t1 -> t2 -> t3 等于 t1 -> (t2 -> t3) • 但是, (t1 -> t2) -> t3 不可写成 t1 -> t2 -> t3
Curried functions add’ :: (Int, Int) -> Int add’ (x, y) = x + y add :: Int -> Int -> Int add x y = x + y Note: add 2 :: Int -> Int Uncurried version Curried version add 2 是一个函数
函数的复合 quadruple :: Int -> Int quadruple = double . double quadruple 3 double (double 3) double 6 12 函数的复合是函数上的运算
Is max correct? 程序设计是一个非常容易犯错误的过程 ;第一次设计的程序也很少是正确的。 软件开发很大一部分费用是找错和改错,即软件测试:给软件不同的输入,然后检查输出是否正确。 软件测试是确保软件符合设计要求的主要方法。
选择测试数据 测试数据尽可能包括各种可能的情形,特别是那些可能出现错误的情形,特殊情形和边界情形等。 函数 max 的测试数据至少应该包含 x<y, x==y, x>y, 以及正负参数组合的情形。 测试数据应该覆盖每一种情况,使每种情况都能运行至少一次。
”Testing can never demonstrate the absence of errors in software, only their presence” Edsger W. Dijkstra 测试永远不能证明软件中不存在错误,只能证明软件中存在错误。 测试也是找错的最有效方法
软件规格说明(Specifications) 如何说明 `max 是正确的´? 一个规格说明( specification)陈述max应该满足的性质。 Property: x <= max x y Property: y <= max x y
为什么陈述 specifications? • 帮助我们理清max 应该具有的功能。 • 有助于测试的进行。 • 可以证明程序的正确性。
规格说明与测试 我们可以定义检查一个性质是否成立的函数 prop_Max :: Int -> Int -> Bool prop_Max x y = x <= max x y && y <= max x y 如果性质prop_Max 总是返回 True, 那么规格说明是满足的。 我们可以测试许多输入x, y以检查max是否具有“正确性”性质,而无需检查max的结果是什么。
Testing with QuickCheck QuickCheck一个随机测试工具 Main> quickCheck prop_Max OK, passed 100 tests QuickCheck 随机生成100组输入并检查测试的性质是否成立。如果不成立,则返回一个反例。