深入浅出函数式编程Haskell的方法学第1讲

Download Report

Transcript 深入浅出函数式编程Haskell的方法学第1讲

深入浅出函数式编程
Haskell的方法学
第1讲
邓际锋
[email protected]
常用类型
• Int:定长有符号整数
• Char:Unicode字符
• Double:双精度整数,通常64位
• Integer:无限精度整数
• Bool:逻辑类型,值是True或False
• String:字符串,其实就是[Char]
类型系统
• 静态
– 编译器在编译时就知道所有表达式的类型
– 一旦类型不匹配就报错
• 自动推导
– 编译器会推断每个表达式的类型
– 只要推断成功,类型声明就可以省略
Algebraic Data Type
• data——定义新的数据类型
• type——类型别名
结构体
type Id = Int
// C
type Name = String
typedef Id int;
data Person =
typedef Name char*;
Person Id Name
struct person {
Id id;
• Person是类型名
• Person是该类型的data
constructor
• 首字母必须大写!
Name *name;
};
枚举
data Dimension =
D1 | D2 | D3
// C
enum dimenson{
D1;
D2;
D3;
};
Tagged Union
type V = Double
data Point =
P1 V
| P2 V V
| P3 V V V
typedef V double;
struct P1 { V v; };
struct P2 { V v1; V v2; };
struct P3 {
V v1; V v2; V v3;
};
struct Point {
enum dimension d;
union {
struct P1 p1;
struct P2 p2;
struct P3 p3;
};
};
函数的类型声明
unary :: Int -> Int
// C
unary x = x + 10
int unary (int x);
binary ::
Int -> Int -> Int
-- Int->(Int->Int)
binary x y = x + y
int binary(
int x, int y);
List
-- 以此开头的是行注释
list1 = [7,8,9] -- 7:8:9:[]
-- (:)是List的constructor,a->[a]->[a]
-- infixr 5
v1 = head list1 -- 7
v2 = tail list1 -- [8,9]
v3 = take 2 list1 -- [7,8]
v4 = drop 2 list1 -- [9]
Haskell是强类型
似乎
强类型意味着
没有运行时类型错误!
换句话来说
自动转型
转型 == coercion /= cast == 野蛮的类型强转
一个例子
foo :: Double -> Double
foo x = 2.0 * x
arg :: Int
arg = 3
v1 = foo 3
-- OK
v2 = foo arg
-- type error
v3 = foo (fromInteger (toInteger arg)) -- OK
Constructor & Pattern
构造器放在等号左边表示pattern,用于匹配数据
f :: Point -> Double
f P1 v = v
f P2 v1 v2 = sqrt (v1 ^ 2 + v2 ^ 2)
f P3 v1 v2 v3 =
sqrt (v1 ^ 2 + v2 ^ 2 + v3 ^ 2)
首字母大写的构造器名可以将其与普通变量区分
Shape
data Shape = Rectangle Float Float
| Ellipse Float Float
| RtTriangle Float Float
| Polygon [(Float,Float)]
deriving Show
circle, square :: Float -> Shape
circle radius = Ellipse radius radius
square side = Rectangle side side
Deriving Show
• 解释器用show函数将求值结果转换成字符串
打印出来
• show :: a -> String
• 让系统自动为我们定义的数据类型生成show
函数
Shape的面积
area
area
area
area
:: Shape -> Float
(Rectangle s1 s2) = s1*s2
(Ellipse r1 r2)
= pi*r1*r2
(RtTriangle s1 s2) = (s1*s2)/2
那Polygon的面积呢?
一种凸多边形的面积算法
ABCDEF的面积 = 三角形ABC的面积 + ACDEF的面积
A
B
F
E
C
D
完整的面积函数
area :: Shape -> Float
area (Rectangle s1 s2) = s1*s2
area (Ellipse r1 r2)
= pi*r1*r2
area (RtTriangle s1 s2) = (s1*s2)/2
area (Polygon (v1:pts)) = polyArea pts
where polyArea :: [(Float,Float)] -> Float
polyArea (v2:v3:vs) = triArea v1 v2 v3
+ polyArea (v3:vs)
polyArea _
= 0
• 在Polygon (v1:pts)里使用了内嵌pattern
• 使用了where子句
• 使用了通配(wild card)pattern _,它可以匹配任何值
Layout Rule
• 跟在let或者where后头的第一个字符确定了一
组等式的起始列,因此可以在同一行开启一组
等式,也可以换行
• 确保这个起始列比包含它的子句更靠右
• 如果有东西比一个等式的起始列一样或者更靠
左,那就意味等式的定义结束了
Layout Rule
thegood = let tmp1 = 1
tmp2 = 2
in let tmp1 = 5
in tmp1 + tmp2
thebad = let
tmp1 = 4
tmp2 = 9
in let
tmp1 = 1
in tmp1 + tmp2
theugly
tmp1 =
b1 =
b2 =
tmp2 =
= tmp1 + tmp2 where
b1 + b2 where
1
2
5
模块化
• module Shape where
• module Shape
(Shape(..),circle,square,area) where
• module Shape
(Shape,circle,square,area) where
Shape是模块名,首字母必须大写
层次化的模块名
module Aa.Bb.Cc where ...
GHC对Haskell 98的扩展
import
import Shape
shape1 = RtTriangle 3.0 4.0
a1 = area shape1
shape2 = circle 8.9
a2 = area shape2
shape3 = Polygon [(0,0), (1,0), (1,3), (0.8,2.7), (0.5, 1)]
a3 = area shape3
• Haskell标准并没有对模块名和文件名的关系有所规定
• 但GHC按照如下规则搜索导入的模块
– 依序在一组搜索路径里找basename.extension文件
– basename是模块名,只不过将'.'替换成路径分隔符('/'或者'\')
– extension是源文件后缀(hs或者lhs)
有没有问题?