Haskell 笔记:Category Theory and Functor

en

Category Theory

A category consists of three parts:

  • A collection of objects.
  • A collection of morphisms, each of which map one object to another.
  • A composition operator of these morphisms, i.e. morphisms can be composed. If f: A -> B and g: B -> C are morphisms, f.g generates a new morphism A -> C.

Note that a morphism has no specific semantics of mapping, but simply links two objects together. Morphisms are also called Arrows.

Examples

Set Category: Set

All sets and standard functions form a category. Functions need not to be surjective, since morphisms have no mapping semantics.

Group Category: Grp

All groups and homomorphisms between groups form a category. A group has specific algebaric structure, which morphisms should preserve.

Laws

Three laws that a category should obey:

  • Composition should be associative.
  • Composition operation should be enclosed in the category, i.e. if f: A -> B and g: B -> C, there must be a h: A -> C satisfying h = f . g.
  • For each object A, there should exist an identity morphism id(A): A -> A s.t. for every f: A -> B, f = id(A) . f = f . id(B).

Note that:

  • There may exist serveral morphisms between A and B.
  • An identity has type A -> A, but a morphism with such type needs not to be an identity.

Functors in Category Theory

A functor maps a category to another category. It should contains two mappings for objects and for morphisms, with composition operation and category laws preserved.

There’s a trivial functor from Grp to Set, which maps groups to their underlying sets, and group morphisms to functions with same behavior but defined on sets instead of groups.

Paramateric Types in Haskell

It’s common to create new types that hold values of other types. List[a] type constructor creates types that holds sequential values of same type; Maybe[a] creates types that hold operation states (failure, or success with returned values).

Usually we expect derived types to inherit functions from types being wrapped. For example, List[Int] should have element-wise addition as Int does, and Maybe[Int] should have similar operations with no burden of re-wrapping and unwrapping. Such ‘inheritance’ should be done automatically if possible, since it is only concerned with the structure of types instead of specific functions.

Hask Category

Haskell language itself forms a category, with all types being objects, and functions being morphisms. Such category is called Hask.

Hask Functors

class Functor m where
fmap :: (a -> b) -> m a -> m b

A parameteric type implementing class Functor is a category functor, mapping Hask to one of its sub-category, where types m a are the object collection. The type constructor m maps objects, and specific fmap defined on m maps corresponding functions.

It’s worth noted that (a -> b) -> m a -> m b can also read as (a -> b) -> (m a -> m b), as -> is right-associative. This may provide a clearer view of fmap, which takes a regular function in Hask and returns the corresponding function in sub-category.

Examples:

fmap (+) :: List[Int] -> List[Int] generates element-wise addition in List[Int].

fmap (+) :: Maybe Int -> Maybe Int generates such function:

maybePlus :: Maybe Int -> Maybe Int
maybePlus _ Nothing = Nothing
maybePlus Nothing _ = Nothing
maybePlut (Just x) (Just y) = Maybe (x + y)
READ MORE

Haskell 笔记:data, type, newtype

zh

新类型有自己的 data constructor(literals 可以看成特殊的 data constructor),由这一点来区分是否创建了新类型。

  • data 创建了新类型,可以有多个 data constructor。
  • newtype 创建了新类型,只能有一个 data constructor,同时新类型的内存布局与原来的类型相同。
  • type 没有创建新类型,只是建立了 alias,没有新的 data constructor。

type

常用于语义化类型,是业务逻辑层的概念。

type ID = Int

a = 1 :: ID
b = a + 2 -- legal

showID :: ID -> IO ()
showID x = print x -- legal, since Int has already been an instance of class Show

-- illegal, since Int has already been instantiated
instance Show ID where
-- ...

newtype

在编译期创建新类型,但差异在运行期被抹去。带有一个构造器。

newtype ID' = ID' Int

a = ID' 1
b = a + 2 -- illegal, since Int and ID' are totally different types

showID' :: ID' -> IO ()
showID' x = print x -- illegal, since ID' is not an instance of Show

-- either
showID' (ID' x) = print x
-- or
instance Show ID' where
show (ID' x) = show x
READ MORE

Haskell 笔记:folds

zh

Prelude.foldl

foldl 为 left-associative folding。

foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f acc [] = acc
foldl f acc (x:xs) = foldl f (f acc x) xs

foldl (+) 0 [1..3] 等价于 (((0 + 1) + 2) + 3)

  • 尾递归,因此有 strict 版本 foldl'
  • 求值时必须先到达栈底,遍历完列表,因此无法处理无穷列表

Data.List.foldl’

foldl'foldl 的 TRO 版本。

Prelude.foldr

foldr 为 right-associative folding。

foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f acc [] = acc
foldr f acc (x:xs) = f x (foldr f acc xs)

foldr (+) 0 [1..3] 等价于 (0 + (1 + (2 + 3)))

  • 没有尾递归,有爆栈的危险。
  • 有向右展开的特点,而 Haskell 中许多数据结构都有向右递归的特点(如 Cons),因此可以很好地处理无穷递归的数据,从而更加通用。

Prelude.foldl1 && Prelude.foldr1

Helper functions。将 operator 限制为同一种类型,同时约去 accumulator。

foldl1 :: (a -> a -> a) -> [a] -> a
foldl1 f (x:xs) = foldl f x xs
foldl1 _ [] = error

foldr1 :: (a -> a -> a) -> [a] -> a
foldr1 f (x:xs) = foldr f x xs
foldr1 _ [] = error

即,foldr1 将列表的第一个值作为 accumulator,将剩余部分作为 list,传给 foldrfoldl 同理。

实践

用 folds 实现 reverse

reversel, reverser :: [a] -> [a]
reversel list = foldl (\acc x -> x : acc) [] list

reverser list = foldr (\x acc -> acc ++ [x]) [] list

用 foldr 实现 foldl

先归纳出 foldr 的泛性质。如果一个函数 g s.t.

g [] = v
g (x:xs) = f x (g xs)

g list === foldr f v list.

再看 foldl 的定义:

foldl f v [] = v
foldl f v (x:xs) = foldl f (f v x) xs

===>

foldl f v list = g list v
where
g [] v = v
g (x:xs) v = g xs (f v x)
-- 从左到右依次更新 v

===>

foldl f v list = g list v
where
g [] = id
g (x:xs) = \v -> g xs (f v x)

应有 g (x:xs) === k x (g xs),我们计算 k

g (x:xs)       === k x (g xs)
g (x:xs) v === k x (g xs) v
g xs (f v x) === k x (g xs) v
(g xs) (f v x) === k x (g xs) v
g' (f v x) === k x g' v
k === \x g' v -> g' (f v x)

所以

foldl f v xs =
(foldr
(\x g' v -> g' (f v x))
id
xs
) v
READ MORE

使用 Aria2 在 Ubuntu 中下载百度云资源

zh

可以实现满带宽下载。

配置 Aria2

Github 下载源码 ./configure && make -j8 && sudo make install

配置 Chrome 插件

clone https://github.com/acgotaku/BaiduExporter

$ cd ariac
$ cat > start.sh
> #!/bin/bash
> aria2c --conf=aria2.conf
> ^D
$ chmod +x start.sh
$ ./start.sh

安装 Chrome 插件

打开 chrome://extensionsLoad Unpacked 选择 chrome/release

完成后在百度云页面上会有 导出下载 按钮。

READ MORE

从伪并行的 Python 多线程说起

zh

写在前面

  • 作者电脑 CPU 为 4 核,因此使用 4 个线程测试是合理的
  • 本文使用的 cpython 版本为 3.6.4
  • 本文使用的 pypy 版本为 5.9.0-beta0,兼容 Python 3.5 语法
  • 本文使用的 jython 版本为 2.7.0,兼容 Python 2.7 语法
  • 若无特殊说明,作语言解时,python 指 Python 语言;作解释器解时,pythoncpython
READ MORE

Linux 文件权限

zh

概念

Linux 中的每一个文件都有其 所属用户所属用户组,根据这两个属性可将文件访问者分为三类:所属用户自己所属用户组中的用户其他用户,我们可以针对不同的访问者设置不同的用户权限。

“访问”可分为三类:执行。我们可以用 ls -l 命令查看一个文件的权限:

$ touch test
$ ls -l test
-rw-rw-r-- 1 hsfzxjy hsfzxjy 0 Jul 3 23:44 test

首部的 -rw-rw-r-- 即为文件的权限位。权限应该分为四部分来看:-/rw-/rw-/r--。第一部分标志文件的类型,如 普通文件(-)、目录(d)、UNIX 套接字(s)、符号链接(l)、块设备(b)等等。接下来的三个部分依次代表 所属用户所属用户组其他用户 的权限,每部分由三个标志位组成:读标志位写标志位执行标志位

目录的权限

目录是一种特殊的文件,因此也拥有文件权限的概念,但权限的语义与普通文件稍有差异:

  • 读:读取目录下文件列表,相关命令如 ls
  • 写:创建、删除目录下的文件,相关命令如 touch(当文件不存在时)、rm
  • 执行:进入目录,相关命令如 cd

特殊权限

出于某些特殊目的,Linux 中存在两个特殊的权限位:粘滞位(t)、Set Id(s)。这两个权限可以 叠加 在执行权限位上,其中 Set Id 可以置于 所属用户所属用户组 的权限组上,而 粘滞位 只能置于 其他用户 权限组上。当特殊权限被设置时,执行权限位上即会显示 s/t (已有 x 权限)或 S/T (尚未有 x 权限)。

粘滞位

粘滞位的作用是 防止他人误删自己的文件。当某个目录的其他用户权限组有 w 权限时,系统中的其他用户即可随意删除目录中的文件。而一旦叠加上 t 权限,只有文件的所有者方能删除文件。一个经典的例子是 /tmp

$ ls -l /
drwxrwxrwt 13 root root 12288 Jul 4 00:15 tmp/

Set Id

Linux 中的进程也有自己所属用户与用户组。一般而言,进程的所属用户即为其发起者,但这会引起一些麻烦。一个例子是 passwd 命令,该命令需要修改属于 root 用户的系统文件以保存密码,倘若进程所属用户即为所属者,此功能则无法实现。

Set Id 权限的作用是:在文件被执行时,将其有效用户/用户组设置为文件的用户/用户组,而不是当前执行者。下面是一个演示:

设当前用户为 hsfzxjy,我们在 /tmp 下创建一个 test 文件,并删去其他用户的 r 权限:

$ cd /tmp
$ echo test text > test
$ chmod o-r test
$ ll test
-rw-rw---- 1 hsfzxjy hsfzxjy 0 Jul 4 00:28 test

由于 test 文件的所属用户是 hsfzxjy,其他用户没有权限读取其中的内容:

$ sudo -u mysql cat test
cat: test: Permission denied

现在我们修改一下 cat 命令的权限,为了不影响系统文件,我们拷贝一份 cat 副本至当前目录:

$ cp /bin/cat .
$ chmod u+s cat
$ ll cat
-rwsr-xr-x 1 hsfzxjy hsfzxjy 52080 Jul 4 00:34 cat*

再以 mysql 的身份执行命令:

$ sudo -u mysql ./cat test
test text

可见 ./cat 在执行时所属用户是 hsfzxjy。我们可以使用 ps 命令更清楚地看到这点:

$ sudo -u mysql cat
# 在另一个终端中
$ ps -eo euser,ruser,comm | grep cat
mysql mysql cat

# -----------

$ sudo -u mysql ./cat
# 在另一个终端中
$ ps -eo euser,ruser,comm | grep cat
hsfzxjy mysql cat
READ MORE

HSFZMUN 4.0 部署小记

zh

技术流水账一篇,记录踩过的坑

Channels 异构

Django Channels 官方文档宣称 channels 的最佳配置是使用其自带的服务器组件 Daphne,但在开发中我发现 daphne 处理普通请求比在 WSGI 架构下慢了好几倍,更何况使用 daphne 派发静态文件是十分不切实际的。于是我将 http.requestwebsocket.* 两个 channel 解耦,前者使用 nginx 配合 uwsgi 处理,后者使用 nginx 反向代理至 daphne 处理。这样一来便可充分利用两种架构的优势。

旧架构:

新架构:

READ MORE

午后雨·科大

zh

从昏睡中惊醒——现在是下午,屋内却很昏暗。屋外,隐约有一阵持续的背景噪音,我拉开窗帘,只见白茫茫的一片。

下雨了,而且是大暴雨。

打开窗,一股辛辣扑面而至——雨和着尘土的气息,再熟悉不过了。伴随着辛辣,一种沁人心脾的清凉涌入屋内,稀释着绿军衣挥发的氨味,一同驱走的,还有午后应有的烦躁。

八月的这里,原来可以不那么热。

一直以为,旱和热是这里夏天的常态。走出机场时,第一感受,也是几天来唯一的感受,便是热——无风的热,黏人;无云的热,灼人。想到将在这样的地方生活,心中顿生怯意——

但现在看来,似乎并不那么糟。

合上窗,雨声再次成为背景音。白色的水汽慢慢爬上玻璃,模糊了摇曳的树,漫水的街,雨中的整个世界。

这一幕似乎很熟悉。

没错,在更早些的时候,当我还在另一个校园时。那是个多雨的城市,雨大而急。记忆中,不知有多少次,也是透过模糊的窗,看着屋外湿透的一切。

面对暴雨,心中有过埋怨,有过恐惧,有过敬畏——但此时,我却体会到了偶遇老友般的亲切。

在陌生的校园,我又遇到了熟悉的雨。

我开始喜欢这里了。

READ MORE

最后的雨夜·广州

zh

明天就要走了。

我贪婪地呼吸着这座城市,想要记住一切。

这是一个雨夜。雨不大,淅淅沥沥的,一反八月羊城的狂暴。雨丝划过夜空,拂去了暑气,夹带着一丝清冽;雨点打在地上,泛起一阵辛辣,这是雨的独特气息。昔日湿热而令人厌烦的南方雨,此刻却如此亲近。

步入校园,城市的灯光隐去了,笼下来的是夜的静谧。走在林荫道,可以闻到空中漫着的甘甜——这是芒果花的香气。

记忆中,最为深刻的花香便是芒果花。这种热带的植物,在羊城开得到处都是——在童年的大院里,在中学的校道里,在这邻近的大学里,在记忆中的每一个角落。游子最难以忘怀的,当属这南国的果香。

出了大学的门,转过街角,城市的灯又亮了。这是学校后门的一条食街,寄存了我六年的回忆。

那股诱人的酸菜味,来自一家无名的东北菜馆。“15 块吃到饱”。豪爽的东北大叔,曾在那个三月给予我信心。

空中那悠扬的,是全家的迎客铃。数不清有多少个早晨,因匆忙而在此买饭团。一口咬下,让柴鱼的美味充盈鼻腔,让翻腾的蒸汽吹走清晨的倦意。

远处是那家熟悉的 M 记。24 小时永不停歇。在这里,我度过了高三的每个周末,一份板烧一杯咖啡,一小时的免费 wifi,抹去一上午的沮丧,换来下午持续的好心情。

再往前便是学校。此时正值军训,校园里却一片静寂——也许是太晚了。六年前,梦从这里开始;六年后,我又将梦重新埋在这里。

雨下大了,我却不急——毕竟,我将去往一个少雨的城市,这里的雨,我要细细记忆。

这是一座现代化的都市。雨中的光光影影,雨中 BRT 的笛鸣,雨中泛着的柏油气息,都是这座城市的印记。

这是一座古老的城市。人们仍执念于古老的语言,过着悠然的生活。饱经沧桑,却历久弥新。

天一亮,我就将去往一个陌生的城市。那里少雨,泛着中原常见的普通。我将在那里度过四年,甚至更久。

而在此之前,一个恋家的动物,要努力带走所有的回忆。

READ MORE