一月二十六日杂感

小姑躺在床上,脸色苍白。一根管子从胸口伸出来,随着呼吸剧烈起伏着。长长的管子在床下绕了一圈,接上了一个白色塑料桶,里面尽是浑浊的黄色液体。病床对面坐着阿伯,一脸忧愁的样子。

我顿时感到有些难过——但也仅此而已。这感觉像是一种怜悯,却不是因为血缘,而是来自本性深处,对病危的同物种的怜悯。换句话说,如果躺在面前的是一个素未谋面的人,我的怜悯不会因此减少半分。这么说很残忍,但确实是我内心的真实写照。这种想法当然没有向任何人透露过,可在自己的博客我不想隐瞒这一点。

我身后有一个庞大的家族,但我时刻都想着和他们切断联系。这种想法来源已久了,或许是受到母亲的影响。母亲时常叨念家乡的事,这个人的小心眼,那个人的不作为。但由于外婆依旧健在,母亲还是保持着较高的探亲频率。说是探亲,不过也是周末偶尔回去住个两天,便匆匆回来了。四百公里的路,火车跑起来快得很。

我所不喜欢的,是老家那种到处是熟人的氛围。在那个小镇子上生活的人们从几代以前就互相认识了,出门走几步遇到的人大都能叫出你的名字。熟人社会有着他们自己认为的优点,办事方便,有困难时可以相互扶持。但在我看来这种关系状态是一种束缚。「熟人」们的指指点点会限制你的行为,即使你的行为并没有什么问题。如果将熟人社会看做一个整体,这个庞然大物的思想进步是非常缓慢的。社会中的人相互牵制,根深蒂固的观念不断同化着想要脱离的人。封闭而保守,也许有人喜欢那种状态,反正我是不喜欢的。

亲人又怎么样呢?如果不是经常见面,相互没有过印象深刻的经历,不过也是「熟人」罢了。血缘这种东西,粗暴地将一个人与另一个人捆绑在一起,物质上或许有一些牵连,但精神关系的认同仍然得看彼此的共同经历。自小我便和父母在另一个城市生活,除了曾经照顾过我的,家族中的许多人,同辈或是不同辈的,对于我都只是「熟人」。但遗憾的是,我常常需要回到这些「熟人」当中,有时甚至需要过多地流露一些感情,让我感到如坐针毡。而这一切,只因为我们有「血缘关系」。

有人说,计划生育和网络的崛起让我们这一代变得孤独而冷酷。但我觉得,如果孤独和冷酷不影响我们在现代社会生活的话,倒也无可厚非。熟人的相互关心也罢,沉迷于网络中的光怪陆离也罢,都是为了使精神不空虚,这是一个人活下去的必要条件。过去的人们没有过多的方式解决这个问题,但现在有了,我们应该有权利依自己的意志去选择。我们已经过了几千年的熟人/家族式社会,但不意味着这是常态。二三十年前的文学作品描绘了一个个乡土社会,它们透着幸福的气息,却不能使我产生憧憬。科技在瓦解旧的人际关系和社会结构,在此之前生活的人或许会感到痛惜,但在此之后的人只会无感甚至欣喜。我想,之前许多时代的接口处都会有这样的阵痛。

我盼望着,能尽早告别这一切。


SS Configuration

SS Client

$ [sudo] pip3 install shadowsocks

/etc/ss.json :

{
"server": "<server ip>",
"server_port": "<server port>", // must be Number
"password": "<password>",
"local_address": "127.0.0.1",
"local_port": 1081,
"timeout": 300,
"method": "aes-256-cfb",
"fast_open": false
}
$ [sudo] sslocal -c /etc/ss.json -d start

proxychains

clone repository from https://github.com/rofl0r/proxychains-ng , make && sudo make install .

Append following lines to /etc/proxychains.conf :

[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
socks5 127.0.0.1 1081

Usage: proxychains [command] .

Chrome Addons

SwitchyOmega .


一月七日杂感

前几天和阿三聚了一餐,这大概是一年到头来我们为数不多能好好聊天的时间。

我们谈了很多,从考试到动漫到未来。令人惊讶的是,他表现出了一种忧虑,对未来的忧虑。在我看来这是很少见的。忧虑的一方面是关于短期的未来,有 CSST 面试的压力,以及成堆的考试;另一方面是更遥远的未来,暑研或是升学或是就业。再往前两天的团队聚餐上大家的讨论,关于资本寒冬的,关于机器学习的逐渐饱和,都给我们留下了深刻的印象。

我问他不做金融吗。他说经过暑假的实习他也对这个行业失去了兴趣,量化交易不过也是机械性的脑力劳动(如有冒犯请见谅,这是我凭回忆归纳的,然而我对这个行业一窍不通)。现在他也开始迷茫将来要做什么。

他说有点羡慕我的状态,对周围的环境不 care,沉浸在自己的小世界当中。这个学校没能让他满意,没有地缘优势,身边足够优秀的人还是少了些。但这些在我看来都不重要,甚至还是优点。

吃完饭后已经是九点半了。雾霾洇开了路灯的光晕,整个城市笼上了一种眩目的橘黄色。

在那之后,我又断断续续想了很多。机器学习真的是我喜欢的吗?钻研理论时我是开心的,但我没有记笔记的习惯;做实验有时会让我很头疼,冗长而累人,很多时候结果还并不好。让我快乐的其实是在概念间的思维游走,但真正深入细节却是有些乏味的。许多创意到了底层,无非也是各种指标的相互比较——除非你是一个子领域的开山鼻祖。但这需要热情和灵感,我认为我没有。我之所以处在这其中,是因为它热门——至少当时是的,而且不让我讨厌,无论是数学或是 CS。

我想我最纯粹的快乐就是写代码了,次之就是钻研各种新东西,数学上或程序上的。但这两者都不能让我活下去。逐利的代码是需要迎合市场的,但这很累,而且会包含许多我讨厌的东西。我从来不是一个好的产品经理,也不想成为。我只希望能依自由意志写代码,折腾各种东西,这些产出多是无用的或是无法迎合市场的,但又确实能让我感到快乐。这个愿望其实是奢侈的。如果把「写代码」替成「追番」、「玩手机」、「打机」,那就能契合到很多人身上。我还是需要一门手艺,支撑我活下去,而让我的纯粹爱好成为我的业余游戏。

既然如此,为什么不尝试将手艺的价值最大化呢?比如出国。在另一个环境凭这门手艺或许能活得更好。我觉得这其中有太多令我望而却步的东西,包括和很多人打交道,包括环境的切换。很多人觉得没什么,但我却十分在意。或许是刻在基因里的一种病吧。不想和人说话;一旦浸入了一个环境,出来时会痛苦万分。升学时也是,回家时也是。想改也改不了。

所以呢,依然没有结论,未来依旧是迷茫的。这两年多我错过了很多机会,实不相瞒是故意的。但也意外收获了一些好处,或许可以成为新的契机。很多问题要随着时间的流动才能找到答案,我也祈祷如此吧。


四月·病
mybirth:FADO+tjeoO88yaFhw0deZQ==:/kAejdHo/9YWWwOJNib3AQ==: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
**THIS CONTENT IS ENCRYPTED**
 
DECRYPTING...

Haskell 笔记:State Monad

一个依赖于外部状态 s 的伪函数 f' :: a -> b,我们可以将其改写为 f :: a -> s -> (b, s) 使其良定。即,在输入输出中显式传递状态 s。现在,我们需要利用 Monad 将状态传递过程隐藏起来。

注意到,输出值 (b, s) 中的末状态 s 不仅依赖于输入状态,更依赖于之前更改过状态的一系列函数及其逻辑。因此我们不能简单地将 Monad 定义为 (a, s) 类似的形式,否则两个函数用 >=> 结合的结果将与函数逻辑无关,这与我们的期望不符。

考虑如下定义:

newtype State s a = { runState :: s -> (a, s) }

由于 -> 的右结合性,f :: a -> s -> (b, s)f :: a -> State s b 等价。固定 s,则 State s 可以成为一个 Monad。一个类型为 State s a 的值通常也被称为一个 state processor。

现在尝试定义 (>>=) :: State s a -> (a -> State s b) -> State s b。若 p >>= f,则 p 蕴含了在此之前所有的状态处理逻辑,我们希望将 pf 的逻辑融合在一起,成为一个新的 state processor,并作为返回值。

p >>= f = 
(
State $ \s -> (b, s'')
where
(a, s') = (runState p) s
p2 = f a -- :: State s b
(b, s'') = (runState p2) s'
)

return 是平凡的:

return a = State $ (\s -> (a, s))

fmap 可以作如下定义:

fmap :: (a -> b) -> (State s a) -> (State s b)
fmap f =
(
\pIn -> (
\s -> (b, s')
where
(a, s') = (runState pIn) s
b = f a
)

如此一来,我们可以将一系列的依赖外部状态的函数串成一个依赖外部状态的函数,传以初始状态,便可得到结果。


Haskell 笔记:Monad 引论

动机

pure functions 看似完美,但却不能模拟现实世界中的诸多任务。这是由于 pure functions 是良定的映射,对于特定的输入值会返回唯一的输出。这种模式在面对如下任务时会显得苍白无力:

  • 有可能失败的任务。如大多数的 IO。
  • 依赖外部状态的任务。如(伪)随机数生成器。
  • 非确定性任务,即对于确定的输入可能有多个输出。这种在 IP 中较为少见。
  • 对外界会造成影响的任务。如大多数的写入过程。

这些问题可以用数学中的域扩充技巧来解决。

域扩充

在数学中,当定义问题的范畴不足以容纳问题的解时,我们通常会对相关的范畴进行扩充。类似的技巧同样也可以应用在这里。

假设一个不良定的函数 f: A -> B

  • 如果 f 有可能失败,我们可以将 B 扩充为 Err(B) ∪ { reasons of failures },其中 reasons of failures 可能是对异常的描述,也可以是空值一类的东西。则 f': A -> Err(B) 是良定的映射,且与 f 行为一致。事实上,这就是 Maybe Monad 和 Either Monad。
  • 如果 f 依赖于外部状态,我们定义 Pref(B)从外部状态空间到 B 的映射的全体,则 f': A -> Pref(B) 为良定的映射,且行为和 f 一致。换言之,对于特定的输入 af'(a) 返回一个函数,其中蕴含了已知 a 时如何从各种不同状态得到结果的逻辑。事实上,这就是 State Monad。
  • 如果 f 具有非确定性,我们将 B 扩充为 Power(B),即 B 的幂集。则 f': A -> Power(B) 为良定的映射,且行为与 f 一致。事实上,这就是 List Monad。
  • 如果 f 依赖于真实世界,我们将 B 扩充为 IO(B),其中的元素为一些值域为 B伪函数,可能对真实世界有影响。这些伪函数已经脱离了 pure functions 的范畴,但将它们看成元素是没有问题的。如此一来 f': A -> IO(B) 为良定的映射,且行为与 f 一致。事实上,这就是 IO Monad。

以上操作都有一个共同点,即对一个不良定函数的值域做了扩充,使之变成良定函数。如果用 Haskell 语言描述,它们都有相似的型:f :: a -> m b,其中 m 为扩充规则。

一个问题随之而来:这样的新函数该怎么结合?为此我们要对相关逻辑进行抽象。这就是 Monad。

Monad

这里我们尝试从实际需求出发,导出一个 Type Constructor 成为 Monad 的必要条件。

约定两个名称:

  • a -> m b 型函数为 monadic function
  • a -> b 型函数为 non-monadic function

首先需要解决的是 monadic functions 如何结合的问题。这个问题具有重要的现实意义。monadic function 常常代表某种计算任务,它们之间的结合相当于把若干计算任务串行化,而后者是非常常见的需求。

我们希望有一种运算符有如下的类型 (b -> m c) -> (a -> m b) -> (a -> m c),在此记为 >=> (因其形状,常被叫做 fish operator)。一个自然的想法是,Monad m 需要某种平凡的拆箱操作 extract' :: m a -> a。所谓“平凡”,即 extract' 不应该丢失参数的任何信息。但这往往不能实现,因为 m a 通常会比 a 包含更多的信息,导致 extract' 无法构成良定的映射。例如 Maybe a 中的值 Nothing 就无法在 a 中找到对应的值。

而事实上,我们不需要条件这么强的拆箱操作。在 m 已是 Functor 的情况下,拆箱操作可以弱化为 join :: m (m a) -> m a。我们尝试用 fmapjoin 合成 >=>

f :: b -> m c
g :: a -> m b

fmap f :: m b -> m (m c)
(fmap f) . g :: a -> m (m c)
join . (fmap f) . g :: a -> m c

-- i.e.

f >=> g = join . (fmap f) . g

Functor 的假设是容易成立的。当然我们可以定义多个不同的 fmap,如此产生的 Monad 会有不同的语义。join 的假设也是容易成立的,m (m a) 通常和 m a 包含相同多的信息。故此做法是实际可行的。

我们再考虑 monadic function 和 non-monadic function 结合的问题。期望有如此一个运算:>.> :: (b -> c) -> (a -> m b) -> (a -> m c)。注意,此处返回值是 a -> m c 而不是 a -> c,因为我们不希望 a -> m b 产生的额外信息有所丢失。自然地,我们希望有一个平凡的装箱操作,return :: a -> m a。如此一来便可结合 >=> 完成上面的运算:

f :: b -> c
g :: a -> m b

return . f :: b -> m c
(return . f) >=> g :: a -> m c

-- i.e.

f >.> g :: (return . f) >=> g

non-monadic function 和 monadic function 另一个方向的结合是平凡的。

综上我们可以得到成为 Monad 的基本条件:

  • 是 Functor,存在 fmap :: (a -> b) -> m a -> m b
  • 有一个平凡的拆箱操作 join :: m (m a) -> m a
  • 有一个平凡的装箱操作 return :: a -> m a

为了描述平凡,我们要求三个函数必须满足如下公理(下面的 f 为 non-monadic function):

  1. return . f == (fmap f) . returnreturn 的平凡性)
  2. join . fmap (fmap f) == (fmap f) . joinjoin 的平凡性)

事实上在 Category Theory 中,还有另外两条公理:

  • join . (fmap join) == join . join
  • join . fmap return == join . return == id

以上四条公理描述了 Id(恒等 Functor)、mm^2m^3 之间的泛性质,并使图交换。

Monad Typeclass

以下为 Prelude 中的定义:

class Functor m => m a where

return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b

此处没有出现 join,也没有 fish operator,而是使用了一个更常用的算符 >>= (通常称为 bind operator)。这是因为在实际中我们不直接将函数结合,而是使用 non-pointfree 的写法。

此外,还有 >> :: m a -> m b -> m b 运算符。return>>=>> 三者是构成 do-notation 的基础。此处不再赘述。

References


Haskell 笔记:Applicative

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.


Haskell 笔记:Category Theory and Functor

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)

Haskell 笔记:data, type, newtype

新类型有自己的 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

Haskell 笔记:folds

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