Haskell 笔记:Applicative

en

Motivation

Functor solves the problem of mapping regular single-parameter functions into a sub-category, but that’s not easy for functions with more than one parameter.

Let’s consider a function with two parameters f :: a -> b -> c, which can also read as a -> (b -> c). Applying fmap on f will yield fmap f :: m a -> m (b -> c). That’s still distant from what we expect: f' :: m a -> m b -> m c. To get f', we need a transform from m (b -> c) to m b -> m c. Here we denote it as <*> :: m (b -> c) -> m b -> m c. We will later show that such transform is universal for functions with more parameters.

Now consider a function with three parameters f :: a -> b -> c -> d. We are going to transform it into a wrapped-value version, with the help of fmap and <*>.

f :: a -> b -> c -> d

(fmap f) :: m a -> m (b -> (c -> d))

\a_ b_ -> (fmap f a_) <*> b_
:: m a -> m b -> m (c -> d)

\a_ b_ c_ -> ((fmap f a_) <*> b_) <*> c_
:: m a -> m b -> m c -> (m d)

Here \a_ b_ c_ -> ((fmap f a_) <*> b_) <*> c_ is in the desired type. For most of the time, applying parameters directly is actually what we want, instead of the function itself, so the code could simply be written as ((fmap f a) <*> b) <*> c, where a, b and c are wrapped values. Parenthesis could be omitted if precedences are set properly, which leads to a neat and easy-to-read form:

f `fmap` a <*> b <*> c

In haskell, fmap has an infix name <$>. So finally we get: f <$> a <*> b <*> c.

Applicative

Haskell pre-defines a type class Applicative, which captures the pattern of <*>. Any type that implements Applicative works well with <$> and <*>.

class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a

Note that an Applicative is also a Functor. Apart from <*>, there are some other helper functions or operators in Applicative.

pure is equivalent to the default value constructor of f, e.g. (:[]) for List or Just for Maybe. This may be handful when lifting an unwrapped value to a wrapped one.

liftA2 transforms a binary operator to the corresponding version. The function exists as binary operators would be frequently passed among high-order functions.

*> takes two wrapped parameters and simply returns the second one, which sequence up two wrapped values. This is quite useful for Applicative with action semantics, such as IO. In fact, it’s so useful that Haskell introduces a syntax sugar for it, known as the do-notation. Particularly:

do
putStrLn "1"
putStrLn "2"

is equivalent to

putStrLn "1" *> putStrLn "2"

<* is similar. Both will be reviewed while studying Monad.

READ MORE

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