Haskell简介
Posted 石头块
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Haskell简介相关的知识,希望对你有一定的参考价值。
最近在学习一种纯函数式的编程语言Haskell,其中涉及到了一些数学中范畴(Category)论和Lambda演算的内容,通过对一些数学理论的了解,可以帮助我们更好的运用Haskell。这里先对其中所运用到的数学知识进行一下归纳说明。关于Lambda演算的内容,特别推荐[9],其中的作者自己实现了一个Lambda演算的解释器,配合其写的说明文档,能够对Lambda有一个大致的认识。由于集合论,群论代数,范畴论等来源于数学和物理上很多分支的高度抽象,难免会有很多的定义,不过例子都是很显然的。
说明:空心圆点是定义,实心圆点是例子。
0. 预备知识
0.1 集合 (Set) S为非空集合, *为二元运算(代指任意的运算)
封闭性: 任意 x, y ∈ S, x*y ∈ S。
含幺元: 任意 x ∈ S, x*1 ≡ 1*x ≡ x, 则1为集合S的幺元。
结合律: 任意 x, y, z ∈ S, x*(y*z) ≡ (x*y)*z。
交换律: 任意 x, y ∈ S, x*y ≡ y*x。
有逆元: 任意 x ∈ S,一定存在逆元 y ∈ S,x*y ≡ y*x ≡ 1。
若在集合S对于二元运算运算*满足封闭性条件,并且其元素满足结合律,则称之为半群(Semigroup)。
若一个半群含有幺元,则称之为含幺半群(Monoid)。
若一个含幺半群中的元素都含有逆元,则称之为群(Group),若还满足交换律则称之为阿贝尔群(Abelian Group)。
通过添加其他一些律,例如分配律,全序关系,偏序关系等等还可以定义出更加复杂的结构,例如环(Ring),域(Field)和格(Lattice)等等。
例:
全体整数集合Z在运算加法上构成一个群。
全体非0实数集合R在乘法上构成一个群。
n阶可逆矩阵集合GL(n)在矩阵乘法上构成一个群。
量子色动力学QCD中所满足的对称性SU(3)群。
一些函数的集合在满足一定条件下对于函数复合运算可构成半群或含幺半群。
关于这些代数的知识很推荐看[1],其中覆盖面比较广,通读下来,能够对代数结构有一个定性的了解,这本书我前前后后每隔一两年就会翻一下,每次都会有不一样的体会。
0.2 全序和偏序 S为非空集合,R为二元关系。
自反性: 任意 x ∈ S, 都有xRx。
传递性: 若 x, y, z ∈ S 且 xRy, yRz, 则 xRz。
反对称性: 若 x, y ∈ S 满足 xRy 和 yRz 则 x ≡ y。
完全性: 若 x, y ∈ S, 至少满足 xRy 和 yRx之一。
若集合S中元素在关系R下满足自反性、传递性和反对称性,则为偏序关系。
若一个满足偏序关系的集合还满足完全性,则为全序关系。
例子:
实数集R和整数集Z集合在≥和≤上满足全序关系。
字符串比较大小所利用的字典序就是一种全序关系。
一个离散集合的所有子集所构成的集合(幂集),在⊆和⊇上满足偏序关系。例如S = {1, 2}, 它的幂集为{{}, {1}, {2}, {1,2}},{2} ⊆ {1,2}, 但是 {2} ⊄ {1}。一个拓扑(Topology)就是由一个集合和其的幂集构成。
集合论的内容,我是参考的[11]中第一章的内容。
1. 范畴 (Category)
1.0 函子 (Functor)
我们可以在各种群之间定义各种映射(Map记号为->),函数就是一种映射关系。
例:
三维线性空间 R³ -> R 中的点积运算以及R³ -> R³叉积运算。
n阶可逆矩阵集合GL(n)可通过行列式运算映射到非零实数集R*中的。
另外有两种特殊的映射成为同态(Homomorphism)和同构(Isomorphism)。
已知对运算+封闭G集合和对*运算封闭的S集合以及映射 G->S 的映射f。
同态: 若任意 x, y ∈ G, 若 f(x+y) ≡ f(x)*f(y),则称f为G到S的同态映射。若f为满射,则成为满同态,记为 G ~ S。
同构: 若满同态映射为双射,则为同构,记为 G ≌ S。
例子:
n阶可逆矩阵集合GL(n)可通过行列式运算映射到非零实数集R*中,这里的行列式运算即为同态映射,因为一个不同的矩阵的行列式可能得到相同的结果。
复数集C到实数集R的模运算为同态映射。
所有正整数N,可通过*2的同构映射到全体正偶数集E。
特殊的有集合S到集合S自身的映射,称之为自同构,例如f(x) = x。
两个满足满同态的群G -> S,可以将群中元素进行某种满足同余关系的"归类"变为商群,G的商群可以和S构成同构关系。例如将N阶可逆矩阵乘法群中行列式相同的矩阵进行归类则可以同构映射到非0实数乘法群中。
而范畴论讲的是这些映射之间的关系,一个范畴C含有和满足以下几个条件:
一族元素(object): 记为ob(C)。
任意一对象 x, y ∈ ob(C),对应一个集合C(x, y),其元素成为态射(morphism,图中标注的是Map)。
复合运算律(composition law): 若 x, y, z ∈ ob(C), f ∈ C(x, y), g ∈ C(y, z), 则存在唯一的 gf ∈ C(x,z), 称之为f和g的复合。
结合律: 若 x, y, z, w ∈ ob(C), f ∈ C(x, y), g ∈ C(y, z), h ∈ C(z, w), 都满足 h(gf) ≡ (hg)f。
单位态射(identity morphism): 对于每一个对象x存在一个态射 1x 属于 C(x,x), 使得任意 f ∈ C(x, y) 和 g ∈ C(z, x) 有 f1x = f 和 1xg = g。
这里有范畴A和范畴B,函子(Functor)就是两个范畴之间的"映射",如图所示,经过Functor的作用,范畴A中的对象和运算都被"映射"到了范畴B中。特殊的有范畴A到自身的函子为自函子(Endofunctor)。函子到函子之间的"映射"被称为自然变换(Natural transformations)。
例:
所有群的同态或者同构映射可以构成一个范畴。
不同偏序或者全序集之间也可以构造群范畴。
[3]和[10]中作者中给了很多更具一般性的例子,[12]中给的例子更具体一些。
当一个数学分支中看似比较困难的证明或许可以通过函子变换到另一个领域中解决,有数学家说范畴就是数学的数学。
1.2 单子 (Monad)
单子是定义在自函子范畴上的幺半群。
若将"范畴"当作范畴中的对象,那么对应的态射就是函子,对应的函子就是自然变换。
单子一共有两个自然变换(接下来代码中会有更清晰的认识):
unit: 1x -> T
bind: TT -> T
其中T是自函子,这两个运算就构成了一个幺半群。
2. Haskell中的实现
数学上的定义做完之后,让我们看看Haskell中关于这些是怎样实现的,[5]中通过很形象的插画撇开具体的数学,大致讲解了这部分内容,比较易懂,[7]中利用JS实现了一些代数结构,源代码都可以在[6]中很方便的查询到。
在Haskell中的Functor, SemiGroup, Monoid, Monad等都是类型类(Typeclass),类型类实例化的对象才是类型,这个在一般的命令式语言中是没有对应的。Rust中的Trait就是借鉴于Haskell的类型类,但不太一样。
2.1 Semigroup
源码:
class Semigroup a where
(<>) :: a -> a -> a
...
其中<>代表半群中元素的运算,并满足结合律x <> (y <> z) = (x <> y) <> z。
2.2 Monoid
源码:
class Semigroup a => Monoid a where
mempty :: a
mappend :: a -> a -> a
mappend = (<>)
mconcat :: [a] -> a
mconcat = foldr mappend mempty
这里代表的含义是若是要定义一个类型的Monoid首先得先定义其的SemiGroup。
mempty对应的是幺半群中的幺元,
mappend对应的是幺半群中的运算,
mconcat代表将这个幺半群中所有元素从右向左进行运算。这个比较像python中的reduce操作,只不过是从左到右。
元素为列表的半群实例化为:
instance Semigroup [a] where
(<>) = (++)
instance Monoid [a] where
mempty = []
mconcat xss = [x | xs <- xss, x <- xs]
这里的含义是将列表从右向左进行拼接。
例:
mconcat [[1],[2,3],[4]] -- 输出 [1,2,3,4]
2.3 Category
源码:
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
instance Category (->) where
id = GHC.Base.id
(.) = (GHC.Base..)
(<<<) :: Category cat => cat b c -> cat a b -> cat a c
(<<<) = (.)
(:: Category cat => cat a b -> cat b c -> cat a c )
f g = g . f
源码中id就是单位态射,(.)运算是复合操作(从右到左)。
这里对(->)函数操作进行了实例化。
例:
import Control.Category ((>>>))
let fun1 = (+5) . (*3) . (\x -> x-2) -- . 等价于 <<<
let fun2 = (+5) >>> (*3) >>> (\x -> x-2)
fun1 4 -- 输出 11 (((4-2)*3)+5)
fun2 4 -- 输出 25 (((4+5)*3)-2)
其中\x -> x-2是匿名函数,lambda表达式。
2.4 Functor
源码:
class Functor f where
fmap :: (a -> b) -> f a -> f b
(<$) :: a -> f b -> f a
(<$>) :: Functor f => (a -> b) -> f a -> f b
(a -> b)对应于图中的F,f a对应于图中的F(x)。
fmap就是实现了图中的F(f),<$>是fmap的中缀形式。
<$ 实现的是x到F(x)的转换。
列表和Maybe的Functor实例化:
instance Functor [] where
fmap = map
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just a) = Just (f a)
这里fmap对列表类型实现的就是类似python中的map功能,Maybe则是对Maybe的两种结果分别进行了匹配,Maybe比较类似Rust中的enum Option,Just对应于其中的Some。观察源码能看出,F(x)比较像x某种意义上的"容器(Container)"。
例:
fmap (+3) Just 2 -- 输出 Just 5
fmap (+3) Nothing -- 输出 Nothing
fmap (+3) [1,2,3] -- 输出 [4,5,6]
let ff = fmap (*2) -- 定义 F(f)
ff [1,2,3] -- 输出 [2,4,6] F(f)(F(x)) -> F(y)
1 <$ [2] -- 输出 [1] x -> F(x)
1 <$ Just 2 -- 输出 Just 1
2.5 Applicative
源码:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id
liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)
(*>) :: f a -> f b -> f b
a1 *> a2 = (id <$ a1) <*> a2
(<*) :: f a -> f b -> f a
(<*) = liftA2 const
...
Applicative将运算(a->b)也进行了包装。
列表的实例化为:
instance Applicative [] where
pure x = [x]
fs <*> xs = [f x | f <- fs, x <- xs]
liftA2 f xs ys = [f x y | x <- xs, y <- ys]
[y | _ <- xs, y <- ys] =
: f a -> f b -> f a :
liftA2 const =
...
例:
[(+3),(*2)] <*> [2,4] -- 输出 [5,7,4,8]
[(+3),(*2)] *> [2,4] -- 输出 [2,4,2,4]
let g = [(+3)] <* [1]
:info g -- 输出 g :: Num a => [a -> a]
g <*> [1,2] -- 输出 [3,4,3,4]
2.6 Alternative
源码:
> Alternative f where =
empty :: f a
: f a -> f a -> f a :
some :: f a -> f [a]
some v = some_v
where
many_v = some_v <|> pure []
some_v = liftA2 (:) v many_v
many :: f a -> f [a]
many v = many_v
where
many_v = some_v <|> pure []
some_v = liftA2 (:) v many_v
empty是其的单位元,<|>的作用就是找到第一个满足条件的对象并返回。
关于Maybe的实例化:
instance Alternative Maybe where
empty = Nothing
Nothing <|> r = r
l <|> _ = l
例:
import Control.Applicative ((<|>))
(Just 1) <|> (Just 2) <|> (Just 4) -- 输出 Just 1
Nothing <|> Nothing <|> (Just 4) -- 输出 Just 4
Nothing <|> Nothing <|> Nothing -- 输出 Nothing
这个在dfs时很有用。
2.7 Monad
源码:
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
(>>) :: forall a b. m a -> m b -> m b
m >> k = m >>= \_ -> k
return :: a -> m a
return = pure
列表的实例化为:
instance Monad [] where
xs >>= f = [y | x <- xs, y <- f x]
(>>) = (*>)
其中运算>>=将自己作用到了自己身上。
return就是unit,>>=就是bind。
例:
plus3 x = [ x+3 ] -- 定义一个从x到F(y)的映射
[1,2,3] >>= plus3 -- 输出 [3,5,6]
[(+3),(*2)] >> [4,5] -- 输出 [4,5,4,5]
return 2 >>= plus3 -- 输出 [5] 这里的return也可以换成pure
-- >>= 还可以嵌套, 这是Monad的精髓所在
-- 若是一个函数输入和输出不同的情况下,是不能嵌套执行的
return 1 >>= plus3 >>= plus3 -- 输出 [7]
[1,3,4] >>= plus3 >>= plus3 >>= plus3 -- 输出 [10,12,13]
fun = do -- 这里的do语句和上面是等价的
x <- [1,3,4]
x <- plus3 x
x <- plus3 x
plus3 x
fun
Monad将[]的Functor"映射"到了下一轮的Functor中,起到一个桥梁作用。
Control.Monad中实现了许多Monad,例如求和Sum和求积Product。
Monad可以在很多语言中实现,好些时候也一直在用,只是不是到叫这个名字,例如在一个带有记忆性列表(非全局变量)的dfs。
在Haskell标准库中还有其他很多有意思的类型类有待探索。
难免文中有错误或不准确的地方,望指正。
参考文献
[1] 代数 Algebra, Michael Artin 机械工业出版社
[2] Haskell趣味指南, Miran Lipovaca 人民邮电出版社
[3] Basic Category Theory, Tom Leinster https://arxiv.org/pdf/1612.09375.pdf
[4] 范畴论, 贺伟 科学出版社
[5] http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
[6] https://www.haskell.org/ && https://www.stackage.org/
[7] https://github.com/fantasyland/fantasy-land
[8] 离散数学 屈婉玲, 耿素云, 张里昂 高等教育出版社
[9] https://github.com/txyyss/Lambda-Calculus/releases/tag/v1.0
[10] Category Theory, Randall R. Holmes
https://web.auburn.edu/holmerr/8970/Textbook/CategoryTheory.pdf
[11] 拓扑学 James R.Munkres 机械工业出版社
[12] https://zhuanlan.zhihu.com/p/33920955
Ps: 在我的Github上更新了一些基础算法,例如排序、图论、聚类和一些比较有趣的算法,例如:并查集、匈牙利算法等,另外还有一些较为高级的数据结构,例如:线段树,LRU和前缀树等,欢迎各位阅读。
https://github.com/chichuyun/Algorithm
以上是关于Haskell简介的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向类加载器 ClassLoader ( 类加载器源码简介 | BaseDexClassLoader | DexClassLoader | PathClassLoader )(代码片段
Android 逆向Linux 文件权限 ( Linux 权限简介 | 系统权限 | 用户权限 | 匿名用户权限 | 读 | 写 | 执行 | 更改组 | 更改用户 | 粘滞 )(代码片段