类型
Type
Type 首字母必须大写, 可用 :t data
测试
ghci> :t 'a'
'a' :: Char
ghci> :t True
True :: Bool
ghci> :t "Hello!"
"Hello!" :: [Char]
ghci> :t (True, 'a')
(True, 'a') :: (Bool, Char)
Type 包括以下
Int 与机器字长有关的整数 Integer 无界的整数 Float 单精度浮点数 Double 双精度浮点数 Char 字符(单引号) Bool 布林值 [Type] 数组 (Type, Type) Tuple(包括各种Tuple类型 和 空Tuple) Type -> Type 函数
Type variable
用于写出类型无关的函数(多态函数)
ghci> :t head
head :: [a] -> a
ghci> :t fst
fst :: (a, b) -> a
以上, a
和 b
即为类型变量
Typeclass
ghci> :t (==)
(==) :: (Eq a) => a -> a -> Bool
ghci> :t (>)
(>) :: (Ord a) => a -> a -> Bool
ghci> show 3
"3"
ghci> read "True" || False
True
ghci> [LT..GT]
[LT, EQ, GT]
ghci> succ 'B'
'C'
ghci> maxBound ::(Bool, Int, Char)
(True,9223372036854775807,'\1114111')
ghci> :t maxBound
maxBound :: Bounded a => a
ghci>:t 20
20 :: Num t => t
对于类型约束(=>
左边的部分)中的 Eq
即为 Typeclass
Eq 包含可判断相等的类型 除函数外所有类型属于Eq Ord 包含可比较大小的类型 (比较结果为GT, LT, EQ三者之一, 用 compare 可测试结果) 若要成为 Ord 成员必须先成为 Eq 成员 被 < <= >= > 之类用于比较大小的函数使用 成为 Ord 的成员就可以进行加减运算而不一定是数字 Show 可用字符串表示的类型 被 show 函数使用 Read 可用字符串转化的类型 被 read 函数使用 Enum 表示连续的类型, 即每个值都有 后继子 和 前驱子(Bounded 情况见下) 被 succ, pred, .. 函数使用 包含类型有 (), Bool, Char, Ordering, Int, Integer, Float, Double Bounded 表示有上限和下限的类型 如果 Tuple 中的每一项属于 Bounded 则整个 Tuple 也属于 Bounded 被 minBound, maxBound 函数(多态常量)使用 Num 表示数字 被 数字 (多态常量)使用 只有成为 Show, Eq 的成员才可以成为 Num 的成员 Integral 表示整数 包含类型有 Int, Integer Floating 表示浮点类型 包含类型有 Float, Double RealFloat RealFrac
类型转换
fromIntegral
对于某些函数得到的 Integral
类型转换为更为通用的 Num
类型
length :: [a] -> Int
fromIntegral :: (Num b, Integral a) => a -> b
指定类型
对于 read
函数, 有
ghci> read "5" - 2
3
ghci> read "5"
Exception: Prelude.read: no parse
ghci> :t read
read :: Read a => String -> a
若不能确定返回的类型, 则需要指定相应的类型, 如下
ghci> read "5" :: Int
5
ghci> read "5" :: Float
5.0
ghci> read "(3, 'a')" :: (Int, Char)
(3, 'a')
Type 和 Typeclass
基本类型
数字类型
数字类型类
Functor typeclass
Funtor Typeclass 的成员形如 f (Maybe a)
等价于 Maybe (f a)
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
对比 自定义Instance 中的 (Maybe a)
这里的 Functor 接受一个型别参数的型别构造子而不是具体的型别
即应该把 fmap:: (a->b) -> f a -> f b
中的 f
替换成 Maybe
,
而不是 (Maybe a)
Either a b
对于两个参数的类型, 应该固定一个参数
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
Left
不应该进行 (f x)
操作, Left
和 Right
的类型可能会不同
对二叉搜索数加入 Functor
instance Functor Tree where
fmap f EmptyTree = Empty
fmap f (Node x leftsub rightsub) =
Node (f x) (fmap f leftsub) (fmap f rightsub)
注意经过 fmap
操作后可能不是一个 合法 的二叉搜索数
Kind
建立模组
形如
data Bool = False | True
-- data 类型 = 构造子
建立简单可枚举的类型 (注意类型和构造子首字母都大写)
data Shape = Circle Float Float Float | Rectangle Float Float Float Float
建立一个 Shape 类型 (后面加 deriving (Show)
时构造子可以直接转换成字符串)
可以看到两个构造子的类型已经建立
ghci> :t Circle
Circle :: Float -> Float -> Float -> Shape
ghci> :t Rectangle
Rectangle :: Float -> Float -> Float -> Float -> Shape
可以使用构造子建立函数
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1)
还可以使用
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
的形式 (注意 Point
同时是构造子和类型的名字)
注意对应的函数也要进行改动
若要将类型变为模组则导出是需要类似
module Shapes
( Point(..)
, Shape(..)
, surface) where
的形式
Record Syntax
data Person = Person String String Float Float String deriving (Show)
let guy = Person "Alice" "Margatroid" 156 43 "Mahousii"
-- Record Syntax
data Person = Person { firstName :: String
, lastName :: String
, height :: Float
, weight :: Float
, occupation :: String
} deriving (Show)
let guy = Person "Alice" "Margatroid" 156 43 "Mahousii"
ghci> guy
-- Show 的方式与上面不同
ghci> firstName guy
....
Type Parameters
data Maybe a = Nothing | Just a
应对多种类型通用的情况, 但对于限制类型
data Vector a = Vector a a a deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
vectMult :: (Num t) => Vector t -> t -> Vector t
(Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m)
scalarMult :: (Num t) => Vector t -> Vector t -> t
(Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n
注意 不要在data生命中添加类型约束, 而应该在函数定义中添加
Derived Instances
对于 Person
, 我们假设没有相同姓名的人存在, 则判断是否同一个人可以用以下方法
data Person = Person { firstName :: String
, lastName :: String
) deriving (Eq)
加入 deriving (Eq)
后, 就可以使用 ==
, /=
来判断其相等性
对于 deriving (Read)
, 同样需要指定类型
data Person = Person { firstName :: String
, lastName :: String
) deriving (Read, Show)
read "Person {firstName = \"Alice\",
lastName = \"Margatroid\"}" :: Person
重新定义 Bool
类型
data Bool = False | True deriving (Ord)
ghci> True `compare` False
GT -- 右边的比左边的大, 且加入 Ord 后可以使用 compare 比较大小
定义一个星期的类型
data Day = Monday | Tuesday | Wednesday | Thursday
| Friday | Saturaday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
ghci> read "Monday" :: Day
Monday
ghci> minBound :: Day
Monday
ghci> [minBound .. maxBound] :: Day
[Monday, TuesDay, Wednesday, Thursday, Friday, Saturaday, Sunday]
一切正常
Type Sunonyms
使用 type
关键字来为一个既有的类型提供别名(创建新类使用 data
)
type String = [Char]
也可以这样
type FirstName = String
type LastName = String
type Name = (FirstName, LastName)
getFirstName :: Name -> FirstName
getFirstName (firstName, lastName) = firstName
alice = ("Alice", "Margatroid")
形态别名也可以带上参数
type AssocList k v = [(k, v)]
-- (Eq k) => k -> AssocList k Int
型别构造子
type IntMap v = Map Int v
-- 也可以这样
type IntMap = Map Int
Recursive data structures
递归的定义数据结构
data List a = Empty | cons a (List a) deriving (Show, Read, Eq, Ord)
fixity 宣告
infixr 5 :-:
-- infixl 表示左结合, infixr 表示右结合
-- 数字表示优先级(乘法为7, 加法为6)
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
-- 这里就可以用 :-: 表示之前的 `cons`
++
的定义
infixr 5 .++
(.++) :: List a -> List a -> List a
Empty .++ ys = ys
(x :-: xs) .++ ys = x :-: (xs .++ ys)
<<self-instance>>自定instance
在默认 deriving
生成的 instance
不能满意的情况下可以自行定义
data TrafficLight = Red | Yellow | Green
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
上面 Eq
的定义与 deriving (Eq)
相同, 而 Show
略有区别
关于定义 instance
需要注意
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
注意1 (Maybe m) 才是一个型别而不是 Maybe
注意2 (Maybe m) 并不能保证 m 属于 Eq 从而使用 (==), 因此需要 (Eq m) 来限制
语法
<<let-in>>let..in..
cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^ 2
in sideArea + 2 * topArea
let
模式匹配
(let (a, b, c) = (1, 2, 3) in a + b + c) * 100
List Comprehension 中的 let
calcBims :: (RealFloat a) => [(a, a) -> [a]]
calcBims xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
注意这里没有 in
let 的范围在
calcBims xs = [ bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
中竖线左边的部分, 竖线右边let前的部分不能使用 bmi
<<case-of>>case..of..->..
类似 模式匹配
以下两个函数等价
head' :: [a] -> a
head' [] = error "Empty lists"
head' all@(x:_) = x
head' :: [a] -> a
head' all = case all of [] -> error "Empty lists"
(x:_) -> x
函数
Pattern Matching 检测一个值是否合适(类型或数值)并从中取值 Guards 检测一个值的某项属性(数值通过某些函数)是否为真
<<pattern-matching>>Pattern Matching
为不同的模式分别定义函数本身 (case的语法糖)
注意 case 不一定要在函数内, 而模式匹配只能定义函数时使用
最后一项用万能的匹配方式避免崩溃
factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
List的模式匹配
List本身为语法糖, 即 [1,2,3]
本质上为 1:2:3:[]
故可使用 x:xs
绑定 x
为头部(1
), xs
为尾部(2:3:[]
)
注意1 x:xs 不能匹配{{{BO}}}{{{BC}}}
注意2 模式匹配不能使用 ++
List的as模式
通过 all@(x:xs)
的形式, all
表示整个 List, x
表示头部, xs
表示尾部
capital :: String -> String
capital "" = "Empty string"
capital all@(x:_) = "First letter of " ++ all ++ is ++ [x]
List Comprehension
在 List Comprehension 中也可以使用 Pattern Matching
let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
[a+b | (a,b) <- xs]
Guards
如果 Guards 没有提供 otherwise
且之前的都没有匹配则寻找下一个模式匹配.
如果模式匹配全部不合适就会发生一个错误.
bmiTell :: (RealFloat a) => a -> String
bmiTell bmi
| bmi <= 18.5 = "You're underweight"
| bmi <= 25.0 = "You're supposedly normal"
| bmi <= 30.0 = "You're fat"
| otherwise = "You're a whale"
关键字where
where
绑定的名字只对函数内有效
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight"
| bmi <= 25.0 = "You're supposedly normal"
| bmi <= 30.0 = "You're fat"
| otherwise = "You're a whale"
where bmi = weight / height ^ 2
也可以在 where
中绑定多个变量
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight"
| bmi <= normal = "You're supposedly normal"
| bmi <= fat = "You're fat"
| otherwise = "You're a whale"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0
还可以在 where
中使用模式匹配
bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight"
| bmi <= normal = "You're supposedly normal"
| bmi <= fat = "You're fat"
| otherwise = "You're a whale"
where bmi = weight / height ^ 2
(skinny ,normal ,fat) = (18.5, 25.0, 30.0)
where
绑定函数
calcBims :: (RealFloat a) => [(a, a)] -> [a]
calcBims xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
关键字let
Curried functions
对于 (-1)
处理为一个负数, 若要一个减一的不全调用使用 (subtract 1)
不全调用和函数一样不属于 Show
类型类, 无法转换为字符串输出
Lambda
匿名函数, 形如 \args -> procedure
ghci> (\x y -> x + y) 2 3
5
在可读性方面的一些区别
addThree :: (Num a) => a -> a -> a -> a
addThree x y z = x + y + z
addThree' :: (Num a) => a -> a -> a -> a
addThree' = \x -> \y -> \z -> x + y + z
使用 Lambda 可以使函数定义与类型声明一致
flip
函数声明
flip' :: (a -> b -> c) -> b -> a -> c
flip' f x y = f y x
flip'' :: (a -> b -> c) -> b -> a -> c
flip'' f = \x y -> f y x
<<fold>>Fold
Fold 可以处理所有通过遍历一个 List 来获得一个值的操作
foldl
foldl 执行过程 (真正的扩展见 foldl 与 foldl' 的区别)
foldl (+) 0 [1..3]
(foldl (+) 0 [1..2]) + 3
((foldl (+) 0 [1]) + 2) + 3
(((foldl (+) 0 []) + 1) + 2) + 3
(((0) + 1) + 2) + 3
-- 以下为 scanl 保留的过程
(((0) + 1) + 2) + 3 -- 0
((1) + 2) + 3 -- 1
(3) + 3 -- 3
6 -- 6
-- [0, 1, 3, 6]
左折叠为从 List 尾部不断取出元素计算 (不可处理无限序列)
sum' :: (Num a) => [a] -> a
sum' = foldl (+) 0
-- 等同于 sum' x = foldl (+) 0 x
foldr
foldr 执行过程
foldr (+) 0 [1..3]
1 + (foldr (+) 0 [2..3])
1 + (2 + (foldr (+) 0 [3]))
1 + (2 + (3 + (foldr (+) 0 [])))
1 + (2 + (3 + (0)))
-- 以下为 scanr 保留的过程
1 + (2 + (3 + (0))) -- 0
1 + (2 + (3)) -- 3
1 + (5) -- 5
6 -- 6
-- [6, 5, 3, 0]
右折叠为从 List 头部不断取出元素计算 (可以处理无限序列)
map' :: (a -> b) -> [a] -> [b]
map' f xs = foldr (\x acc -> f x : acc) [] xs
对比 map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs
使用 foldr 更快(:
运算比 ++
要快的多)
foldl1
无需初始值的 foldl
sum'' = foldl1 (+)
以上 sum
处理空 List 时会产生异常, 而 foldl
可以很好的应对
foldr1
无需初始值的 foldr
Scan
函数调用符
($) :: (a -> b) -> a -> b
f $ x = f x
作用相当于在 $
的位置到尾部加一对括号
ghci> sqrt (3 + 4 + 9)
4.0
ghci> sqrt $ 3+4+9
4.0
以上两者等价
还可将一个数字作为函数使用
ghci> map ($ 3) [(4+), (10*), (^2), sqrt]
[7.0,30.0,9.0,1.7320508075688772]
Function Composition
(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)
将两个包含关系的函数变为并列, 方便定义 Point Free Style
ghci> map (\x -> negate (abs x)) [5, -3, 6, 7, -9]
[-5,-3,-6,-7,-9]
ghci> map (negate . abs) [5, -3, 6, 7, -9]
[-5,-3,-6,-7,-9]
以上, 可以省去 (\x -> negate . abs x)
参数和函数末尾的 x
模组
装载模组
-- 装载整个模组
ghci> import Data.List
ghci> nub [0,3,1,2,3]
[0, 3, 1, 2]
-- 装载模组部分函数
ghci> import Data.List (nub, sort)
ghci>
-- 装载模组除去部分函数
ghci> import Data.List hiding (nub)
ghci>
-- 避免命名冲突且装入整个模组
import qualified Data.List
ghci Data.List> Data.List.nub [0,1,2,0]
[0, 1, 2]
-- 起个短一点的模组别名
ghci> import qualified Data.List as L
ghci L> L.nub [0,1,2,0]
[0, 1, 2]
以上
<<foldl-foldlprime>>Data.List的 foldl
foldl
的执行过程
foldl (+) 0 [1..3]
let z1 = 0 + 1 in foldl (+) z1 [2..3]
let z1 = 0 + 1
z2 = z1 + 2 in foldl (+) z2 [3]
let z1 = 0 + 1
z2 = z1 + 2
z3 = z2 + 3 in foldl (+) z3 []
let z1 = 0 + 1
z2 = z1 + 2
z3 = z2 + 3 in z3
let z1 = 0 + 1
z2 = z1 + 2 in z2 + 3
let z1 = 0 + 1 in (z1 + 2) + 3
((0 + 1) + 2) + 3
..
6
foldl
' 的执行过程
foldl' (+) 0 [1..3]
foldl' (+) 1 [2..3]
foldl' (+) 3 [3]
foldl' (+) 6 []
6
建立模组
单文件
文件名与模组名一致, 例如文件名 Geometry.hs
module Geometry
( sphereVolume
, sphereArea
, cubeVolume
, cubeArea
, cuboidVolume
, cuboidArea
) where
sphereVolume :: Float -> Float
sphereVolume radius = ( 4.0 / 3.0 ) * pi * ( radius ^ 3)
....
导入这个模组只需
import Geometry
即可
多文件
目录名与模组名一致, 例如目录名 Geometry
文件 Geometry/sphere.hs
module Geometry.Sphere
( volume
, area
) where
volume :: Float -> Float
volume radius = ( 4.0 / 3.0 ) * pi * (radius ^ 3)
....
文件 Geometry/cuboid.hs
, Geometry/cube.hs
类似
导出这个模组可以形如
import qualified Geometry.Sphere as Sphere
ghci相关
:q 退出 ghci :t (+) 查看类型 :m Data.List 装载模组 :l file.hs 装载文件 :r 重新装载文件 :info datatype 输出型别的信息