用 Angular 弄了一个背单词的网站——eliseos.org

Like
Like Love Haha Wow Sad Angry
18211

弄了一个背单词的网站——eliseos.org,或者叫 jingtu.io。(这俩名字其实是一个意思。当然这个网站不主要是为了背单词而开发的,不过目前只有背单词的还能用。)


它是 Angular 开发的,同时用了一些时新的技术,比如前端的 redux-observable, graphql, apollo client,后端的 inversify, sequelize-typescript,另外我还自己搞了一些用于自动生成 graphql schema 的 decorator——

@Table
export class Language extends NoPrimaryOrdinaryModel<Language> {

  @Default(DataType.UUIDV1)
  @Column
  @GraphQLFieldToType
  id: string;

  @PrimaryKey
  @Column
  @GraphQLFieldToType
  name: string;

  @GraphQLFieldJsonLike(() => Kv)
  @Column(DataType.JSONB)
  namesInLang: KvInterface;

  @HasMany(() => LanguageOfLanguage, 'languageName')
  languageOfLanguages: LanguageOfLanguage[];
}

 

是不是有点像 Java 了?这么写就会生成一个叫 Language 的表,有这么些 column 和 association,这部分是 sequelize-typescript 的功能,还会生成一个叫 Language 的 graphql schema,这部分是我自己写的。难也不难,主要是提供一种思路。

另外写的过程中我也理解了为什么 sequelize-typescript 的 decorator 参数里,类型是 lazy 的(即以上代码里的 () => LanguageOfLanguage。这是因为如果不 lazy,出现类的循环引用的时候,有一方会变成 undefined。我碰到了这个问题,所以我自己也用了 thunk 形式作为参数。

然后对于 query 也有这么些个 decorator,比如——

@GraphQLQueryField(userGraphqlType, {
  token: {
    type: new GraphQLNonNull(GraphQLString)
  }
}, 'Checks if a token is valid. Returns the validated user.')
async checkToken(parentValue: any, {token}: {
  token: string
}, context: ContextInterface) {
  return await context.services.userService.checkToken(token);
}

 

其实也没什么学问,主要是我看着原来 graphql-js 的 object literal 结构太不稳当了,嵌套一深,随便哪里类型写错了,TypeScript 就抛一大堆错(其实挺稳当的,只要你把 object literal 拆分开来,每一级都标注类型,报错就不会大规模传播)。

用上了 decorator,顺带用了 inversify——一个依赖注入管理器,以及 TypeScript 的一套东西,谁说 Node.js 不可以工程化开发呢?

至于前端,也是从 TypeScript 中获益良多。Angular 原生就是依赖 TS 的,也是原生就依赖 decorator 的,也是原生就依赖注入的。这么看 JS 生态系统前后端的技术选型已经趋同了,我相信这是好的趋势。

还有什么要说的话,就是我把以前答过的Axurez:如何评价 TypeScript 最新加入的 Discriminated union type?这个定义 tagged union 的简便语法,真的给用到了实战里——不仅仅是定义 redux actions,而且我写了一个可以深度 get 和 set 的 Map,因为要用递归指涉自身,还必须用到 tagged union。这个类型如下:

export type RecMap<K, V> = Map<K, { type: 'V', value: V } | { type: 'M', map: RecMap<K, V> }>;

 

整个弄下来的感想就是,Angular 是真的好用,Angular 生态是真的不错,universal 完全按官方走一遍就活了,现在线上运行的版本就是 universal 的,右键查看源码可以看到是渲染好的页面发过来的。angular cli 一路可以 generate 到底,基于 NgModule 的路由懒加载也是开箱即用,不需要任何配置,非常美妙。

还有一点就是,还是纯 css 库比较稳妥,带 JS 的反而不行,我用了 material design components 和 material design lite(因为前者没实现 chip……),还是把 js 拆了用的。一方面是它带的 JS 不一定合你的意,另一方面是它哪怕是用了你用的框架,它的 API 设计也不一定合你意(这也是我不用 Angular Material 的原因)。另外用了 Angular 这样的框架,它封装的那点功能你自己封装也要不了多久。

如果说还有一点感想,那就是我好像有点达成以前的目标了。以前总是感觉 sequelize 要写一套定义,要写一套接口,graphql 还要写一套定义,来来回回同样的东西要写好多遍。现在借助 decorator 真的实现了只写一遍。现在还有 sequelize-auto-migrations 这样的东西,可以自动根据模型的更改生成 migration,简直不要太省劳动力。


说了这么多关于开发的功能,那么这个网站怎么用呢?(推荐桌面使用,虽然移动端也完全可用)

关于背单词功能,如下图,点击右侧的「巴别」(巴别,用 wikipedia 的人应该见过,典出巴别塔,我用来指代和语言相关的模块)中的「学习」,就来到了学习界面(同时在线统计是用 socket.io 实现的,不过用户数量目前并不对,页面是对的。这里有个彩蛋,没网的时候 logo 就会黑掉):

你可以在右侧的「语料」里粘贴进英文文章,然后点击「使用这篇文章」:

它会把文章跟服务器同步,解析出你认识的、不认识的、没决定过的词汇,分别标上白色、红色和绿色:

正如提示所说的

在左边粘贴文章,并点击按钮,来提取文章中的单词。
绿色表示状态未知的单词,划过标记为认识,单击标记为不认识,再单击标记为认识,以此类推。
你可以在概览中回顾不认识的单词。

照着做就行了。我还弄了一个功能就是添加和查看单词释义。把鼠标悬停在不认识的(即红的)单词上,会出现悬浮框:

点击加号就会出现文本框,可以添加释义,以及选择释义的语言(这个网站处于我个人的恶趣味,是支持了四种语言的,用右上角的语言选单可以极速切换语言)。添加完之后,或者本来就已经有人添加过,就会是这个样子:

本来应当是可以代理一个其他网站的 API,或者干脆服务器上搞一个词库的,实际上应该是可行的,但我还是想先试试 UGC 一段时间(估计效果不好)。添加释义的另一个好处就是可以在个人主页上显示……比如我的主页

个人主页怎么进?登陆之后右上角的用户名可以直接点击:

或者鼠标悬停,在下拉菜单里选「个人档」也行。

如果你误操作了,一方面你可以再次点击单词,在白色和红色之间切换,另一方面也可以在右下角看到所有红色、白色词汇的列表,点击就可以复归「未确认」状态,即绿色。

确认单词为未知之后,不仅单词会被同步到服务器,单词所在的句子也会被同步到服务器,在右侧的「巴别—概览」中可以回看:

未知单词在句子中的未知也会标红。(这里本来是给单词加上 html 标签,然后渲染 html 的,但是会有注入的危险,所以最后强行把句子 split 成一个结构体,用 ngFor + ngIf 渲染了)

以上大概就是目前能用的内容。设置的个人档也可以更新

还可以生成邀请链接,推广注册。

如果遇到什么 bug,请一定联系我。

至于为什么要做这个网站?这个网站除了背单词之外,还是干什么的?就如关于页面所说:


Eliseos 就是「极乐净土」的西班牙语形式。我用西班牙语是因为其他语种类似的域名都注册了(elysium, elysian, elysion 之类的)。其实我原来有个 elysion.tech,但是 .tech 太冷门了,很多地方识别不出来。这网站还有个域名,叫「净土」—— jingtu.io

根据维基百科:

至福乐土(Elysium)或是乐土平原(Elysian Fields)(古希腊语:Ἠλύσιον πεδίον,Ēlýsion pedíon,音译:伊利西恩、伊利西昂。中文翻译上亦可名为归静乐土或是归净乐土,意为回归安静或回归纯净的乐土。)是一个来世观的理念,随著时间的推移并由一些希腊的宗教教派的仪式与哲学流派所延续发展。

我最早是在《圣斗士星矢》里接触到这概念的。在这里,我想用它指代这个网站,是因为我想做一个能进行深刻思想交流(包括语言、知识)的地方。

Like
Like Love Haha Wow Sad Angry
18211

Linux 下使用 Nvidia 显卡和 Virtualbox 遇到的一些坑

Like
Like Love Haha Wow Sad Angry
431

nouveau 显卡驱动,并不好。

Virtualbox 的问题

Virtualbox 安装完毕 Windows 10,却莫名其妙卡在了登陆之后应该出现桌面的地方。而且它不仅仅是客户机卡死,宿主机也一并卡死了。

我没太注意这个问题。大不了暂时不用 QQ 好了。

Nvidia 显卡

无独有偶,另一个问题的出现使整个事情出现了转机。

使用 Linux 后不久,就发现了问题。桌面环境(xfce4)经常无故完全卡死,甚至连 Ctrl+Alt+Fn 切换 tty 都没有用。偶尔出现还能忍,但是在短短的两三天就数次出现,呈现一定规律。我决定一探究竟。还好直接看日志就能发现问题——journalctl -xe里面出现了红红的nouveau sched_error ctxsw_timeout,几乎能让人确信是 nouveau显卡驱动的问题。

查了一下,貌似是一个普遍的问题:Bug 93629 – [NVE6] complete system freeze, PGRAPH engine fault on channel 2, SCHED_ERROR [ CTXSW_TIMEOUT ],而且看不到被解决的趋势。

对症下药,我赶紧卸载 nouveau,切换了 Nvidia 的官方驱动,至今运行良好:

sudo pacman -S nvidia

Virtualbox 和 Nvidia 显卡

这时我突然想起,会不会 Virtualbox 的问题,也是 nouveau显卡驱动的问题呢?我激动地打开了 Virtualbox,登陆,一切如期。看来真的是缘起这个显卡驱动。

我还没激动多久,又出现了一个问题。用 Windows 的目的之一,是测试 Angular 对 IE11/Edge 的 polyfill 管不管用。所以我打开 Edge,加载最近在写的 www.zjuqsc.com/m,却发现整个图形界面都在不停闪烁。起初我以为是 UWP 的 bug,然而换了 IE 之后依然如此,安装了 QQ 之后依然如此。我又一次失望起来——这事情就搞不好了吗?

还好,搜索 “virtualbox windows10 flickering” 直接给出了答案:SCREEN FLICKERING IN VIRTUALBOX WITH 3D ENABLED [UPDATED SEP 21, 2017]

看起来,是 Nvidia 显卡的 3D 加速驱动的问题。根据文中内容,虽然只要改动一行代码,但是由于会「破坏一位付费用户用的某些功能」,所以官方不会修改,只能用户自己动手。

好在作者和一位评论者,分别提供了 5.1.85.1.30 编译好的、要打补丁的那个文件VBoxOGLrenderspu.so,只要用它替换/usr/lib/virtualbox/里的同名文件就行了。后者经过我测试,是可以在 ArchLinux 下的 Virtualbox 5.2.* 下正常工作的。

后记

Linux 下的桌面环境,还存在种种问题。还好这些问题现在基本是可以修复的。尤其是相比 Windows 下不可忍受的效率问题,还是早日切换到 Linux 的好。

Like
Like Love Haha Wow Sad Angry
431

什么是 Haskell 中的 GADT(广义代数数据类型)?

Like
Like Love Haha Wow Sad Angry
3912

先看看没有 GADT 的时候我们在做啥。最简单的,比如定义一个列表:

List a = Nil | Cons a (List a)

它是在做啥呢?

  • 先看等号左边,它首先定义了一个叫 List 的 type constructor,它接受一个类型,并返回一个新的类型。其实就是类型的函数。
  • 再看右边,它告诉我们有两种方式获得一个类型为 List a 的值,一个叫 Nil,一个叫 Cons a (List a)。
    • Nil 的话,你直接写 Nil,它就是一个 List a 类型的值,至于这个 a 具体是啥,需要结合上下文推导才知道。
    • 而 Cons a (List a) 呢,它是接受两个参数,一个类型是 a,一个类型是 List a,然后返回一个 List a 类型的值。至于这个 a 是啥,我们同样是需要推导才能知道的,不过是这里的推导比较显著罢了,因为直接就是第一个参数的类型。

那么我们可以用函数形式给这俩表示出来: Continue reading “什么是 Haskell 中的 GADT(广义代数数据类型)?”

Like
Like Love Haha Wow Sad Angry
3912

例说 C 语言类型声明

Like
Like Love Haha Wow Sad Angry
921
C 语言的类型声明,由于某些历史局限性,在某些情况下显得相当复杂。下面让我们来渐渐深入 C 语言类型声明的谜团,一探究竟。

 

指针和数组

一重声明

以下这些问题,我相信即使是最基本的初学者也不会有太大困难:

你会声明数组吗?

int a[5]; // 包含 5 个元素。

你会声明指针吗?

int *a;

二重声明

你会声明双重指针吗?

Continue reading “例说 C 语言类型声明”

Like
Like Love Haha Wow Sad Angry
921

【C++ 模板元编程入门】在编译期实现 Peano 数

Like
Like Love Haha Wow Sad Angry
4

基本知识

类型的函数

我们都知道模板可以接受类型作为「参数」。同样地我们也可以有「返回值」,从而构造类型的函数。基本的范式是:

template<class T>
struct Computed {
  using type = T;
}

这就构造了一个名为 Computed 的,接收一个类型参数,返回这个类型本身的函数,用法如 Computed<float>::type,这个类型应当还是 `float`。
为什么要包一层 struct?这是因为 C++ 不支持对 using 的特化。这样的代码是不行的:

template <class T>
using computed<T> = T;

template<>
using computed<int> = double;

至于为什么不支持,我没有了解。

特化

什么是特化?你可以理解为模式匹配,就像 Haskell 中的写法一样。

template<class T>
struct Computed {
  using type = T;
}

template<>
struct Computed<int> {
  using type = double;
}

这样当你调用 Computed<bool>::type 时,得到的结果是 bool,而调用Computed<int>::type得到的结果却是double。当然这种匹配是遵循一定规则的,比如更「具体」的特化优先匹配,这跟 Haskell 谁在前谁先试着匹配不太一样。在 Haskell 中就好比:

data Type = Bool | Double | Int

computed :: Type -> Type
computed Int = Double
computed t = t

实际上有了这层对应,如果你知道怎么在 Haskell 中实现 Peano 数,那么 C++ 中的实现基本就是无脑翻译了。如果你不知道怎么在 Haskell 中实现 Peano 数,那你知道 Peano 数是什么,也能大差不差知道答案了。

Peano 数

Peano 数是什么?Peano 数是归纳定义的自然数,准确地说应该是一个表现形如直觉中「自然数」的公理系统,也就是「自然数」的形式化。这个系统里只有两个符号,Zero——表示 0,以及 Succ——表示后继。那么 1 就是 Succ<Zero>2 就是 Succ<Succ<Zero>>,以此类推(归纳,其实就是「以此类推」的形式化)。
我们可以在 C++ 中如此表述:

struct Peano {};
struct Zero: Peano {};
template<class T>
struct Succ: Peano {};

那么加法又是什么呢?从例子出发,我们需要定义一个两个类型参数的模板:

template<class T1, class T2>
struct Add {
  using type = ???;
}

满足直觉中的运算规律,比如 2+1=3,翻译成 C++ 就是 Add<Succ<Succ<Zero>>, Succ<Zero>>::type = Succ<Succ<Succ<Zero>>>。当然类型之间没有等于号,准确地说应该用 std::is_same<T1, T2>,这其实也是通过特化实现的,比如(示意,非官方实现):

template<class T, class U>
struct is_same {
  static constexpr bool value = false;
};
 
template<class T>
struct is_same<T, T> {
  static constexpr bool value = true;
};

那么如何定义加法呢?对于有限的元素,我们当然可以为每一个实例做特化,比如:

template<>
struct Add<Succ<Succ<Zero>>, Succ<Zero>> {
  using type = Succ<Succ<Succ<Zero>>>;
}

也就是打表。C++ 编译器的模板深度一般都是有限的,所以这理论上是可以在实际操作中覆盖所有用例的。但是这明显太傻了。其实加法的定义只需要两条规则就可以覆盖:0 + b = b, (Succ a) + b = Succ (a + b)。翻译成 C++ 就是:

template<class T1, class T2>
struct Add;

template<class T>
struct Add<Zero, T> {
  using type = T;
};

template<class T1, class T2>
struct Add<Succ<T1>, T2> {
  using type = Succ<typename Add<T1, T2>::type>
};

注意那个 typename,gcc 并不知道后面那个 ::type 成员是类型还是变量,所以需要 typename关键字的提示。
这就算写完了,你可以测试看看,是不是满足 std::is_same<Add<Succ<Succ<Zero>>, Succ<Zero>>::type, Succ<Succ<Succ<Zero>>>>::value == true(需要 <type_traits> 头文件,或者上面自己写的那个模板(那就不用加 std::)。这么嵌套着写 Succ 太繁琐了,也不方便看,你可以简单地写一个模板来从整数生成类型:

template<int v>
struct peano {
  using type = Succ<typename peano<v - 1>::type>;
};

template<>
struct peano<0> {
  using type = Zero;
};

然后就可以去验证 Add<<peano<2>::type, peano<1>::type>::type 是不是等于 peano<3>::type 了。
至于加减乘除的其他运算,比较啊奇偶性啊其他的函数,只要你懂得了加法,恐怕就不难了。

练习:

Peano numbers | Codewars 完成加减乘除、奇偶性和比较大小的撰写,并通过测试。

广告时间:

Codewars.com 是一个很好的综合性、游戏化 OJ,除了算法(多是入门级的)之外,考察语言特性(较为深入)是其一大亮点,同时有很多 Haskell 方面的内容,包括我们喜闻乐见的读论文然后完形填空。题图是 Codewars 上上述练习的界面截图。

后记

当然我们还可以进一步「证明」我们印象中的结论,比如加法是满足交换律的,加法和乘法是满足分配率的,等等。这就是后话了。

Like
Like Love Haha Wow Sad Angry
4

关于游戏王全卡档的几点注解

Like
Like Love Haha Wow Sad Angry
3

正文:

问:为什么我下了游戏王全卡档文件,也拷到了存档目录,却无法在游戏里解锁全卡档?

答:原因可能是多样的,但本质上都是由于游戏读取到的校验码和存档文件不匹配导致的。

问:那么会有哪些原因呢?怎么知道我是哪个原因呢?

答:首先,我们要了解这个校验码是放在哪里的。答案是——注册表。这也是网上很多教程会让你导入注册表文件的原因,也是它们说“要备份存档文件,必须要备份 flcrc”的原因。准确地讲,这个校验码是放在注册表某个节点下的 \KONAMI\Yu-Gi-Oh! Power Of Chaos\system 节点下的 flcrc 键值,是一个 13 字节的二进制。在我的电脑上,它长这样:

游戏王的注册表
游戏王的注册表

在同样的节点下,CommonDir 这个字符串键值则记录着存档文件的位置。我相信,如果之前你没有手动创建/导入这个键,它应该会在游戏开始时被设定为“当前工作目录”的父目录(这是因为游戏王不仅有城之内篇,还有游戏篇和海马篇,默认状态下,它们会被分别安装在 %PROGRAMFILES%\KONAMI 目录下的三个子目录中,但是它们的存档是可以共享的——这也正是这个目录名为 Common 的原因)。

在很多情况下,这不会有问题

你是什么版本的操作系统?是 Win XP, Win 7 还是 Win 10 呢?是 32 位还是 64 位呢?

问:那我应该怎么做呢?怎么才比较稳妥?

答:比较稳妥的做法是,不要导入网上的注册表文件,直接先打开一次游戏再退出。这时候它会自动创建存档文件,以及注册表中的键值。你只要在注册表中搜索 flcrc 这个键,就会知道正确的地方在哪了。

问:我下到的存档文件,它的 flcrc 不符合你说的这些,怎么办呢?

答:最好的方法是去下一个符合这个 flcrc 的文件。你可以直接在这里下载。

问:那这个 flcrc 到底是怎么生成的?它的内容是什么意思?

答:我目前只发现了这些。等到我有时间、有机会的时候,也许能了解它的真相。

后记:

昨晚把游戏王城之内篇又翻出来玩。

当初还是数学竞赛培训时,季泽推荐给我的。

前一阵子也有一次动了玩游戏王的念头,配置好之后却发现不能解锁全卡。当时没有深究,打了几局就不想打了。

这回再次安装,我心里有了阴影。安装好之后,一看,果然按照它里面的说明配置全卡档,并不能解锁全卡。这下我就慌了。毕竟我只想打几局,可不想浪费时间在配置文件上面。

 

定下心来,我开始研究到底怎么回事。它的操作,无非是两件事,一件事是复制存档文件到指定位置,名为 system.dat。另一件事则是导入一个注册表文件。

 

汇总了一些资料,都大同小异。

 

第一个难点是。

 

关键的问题是,在我的系统中(其实是 64 位系统中),这个键的位置变了,原来是在\HKEY_CLASSES_ROOT\VirtualStore\MACHINE\SOFTWARE\WOW6432Node\KONAMI\Yu-Gi-Oh! Power Of Chaos\system,现在是在\HKEY_CLASSES_ROOT\VirtualStore\MACHINE\SOFTWARE\WOW6432Node\KONAMI\Yu-Gi-Oh! Power Of Chaos\system

 

 

Like
Like Love Haha Wow Sad Angry
3

一个不正确的 TypeScript 类型定义引发的血案

Like
Like Love Haha Wow Sad Angry
4

Mongoose 的 TypeScript 类型定义并不完全正确!

昨天晚上在 bgs 研究了(或者说调试了)XGS 写的 nodejs 服务器项目。他在用 koa 作为服务器,mongodb 作为数据存储,写一个不知道什么项目(似乎名为「写作者平台」/ “Write’s Platform”)。反正当时还处于很初级的阶段,只有两个路由,一个 get,一个 post,后者是用来向 mongodb 提交一篇文章的。在测试过程中,他发现了一个问题——他在保存文章的回调中更改了响应的内容(如下),但是实际测试得到的响应内容却是 koa 默认的 OK。

router.post('/path', (ctx, next) => {
    const article = new Article( /* something */ );
    article.save((err, article) => {
      if (err){
        ctx.body = err;
      } else {
        ctx.body = "success";
      }
    });
})

起初我发现,他的代码中,router.post('/path', (ctx, next) => { /* body */ });/* body */这段中并没有返回 next(),这应该会导致异步的回调没有被阻塞。

接着我想,可能又是 koa 的版本搞混了什么的,因为 koa v2,koa router 什么的版本管理都很混乱,长期处于虽然大家都想使用最新版,但是默认的却一直是旧版的情况。后来又出现了 koa 是新版,router 是旧版的情况,曾经我为别人解决过这个问题。

但是这次却不是。并且把返回 promise 切换为使用 async/await之后(如下),情况依然没有好转。

router.post('/path', (ctx, next) => {
    const article = new Article( /* something */ );
    return article.save((err, saved) => {
      if (err){
        ctx.body = err;
      } else {
        console.log(saved);
        ctx.body = "success";
      }
      // or `return next();` here.
    }).then((thing) => {
        console.log(thing);
        return next();
    });
})

(过程中我们发现这个传给回调的 thing居然是undefined,这是个伏笔)

渐渐地我被各种函数的签名(比如究竟应该返回一个什么,是 next() 还是一个 promise,而 next 的类型又是什么?)搞得烦躁起来。于是我提议使用 TypeScript 来辅助检查。

我们开始怀疑 article.save这个函数。因为在 koa 官方(仅有)的、涉及到等待异步动作的 router 代码样例里,完全类似的代码是可以工作的:

router.get(
  '/users/:id',
  function (ctx, next) {
    return User.findOne(ctx.params.id).then(function(user) {
      ctx.user = user;
      return next();

    });
  },
  function (ctx) {
    console.log(ctx.user);
    // => { id: 17, name: "Alex" }
  }
);

于是我们着重检查了 mongoose 的 TypeScript 类型定义文件。这个 articleArticle的实例,而Article实际上是 mongoose 动态构造出来的一个类(应该是通过了某种元编程技术)。在类型定义中是这么反映的:

interface Model<T extends Document> extends NodeJS.EventEmitter, ModelProperties {
    new(doc?: Object): T;
    /* other things */
}

而 article 就是那个<T extends Document>,而 Documentsave方法是这么写的:

    /**
     * Saves this document.
     * @param options options optional options
     * @param options.safe overrides schema's safe option
     * @param options.validateBeforeSave set to false to save without validating.
     * @param fn optional callback
     */
    save(options?: SaveOptions, fn?: (err: any, product: this, numAffected: number) => void): Promise<this>;
    save(fn?: (err: any, product: this, numAffected: number) => void): Promise<this>;

(取自 npm install @types/mongoose的结果,不知为何与 DefinitelyTyped 上的有所不同。)看到这,特别是Promise<this>,我就确信了,这个函数没有问题。可是上面返回的thing又确乎是一个undefined,难道是我们的使用姿势有问题?

万般无奈之中,TypeScript 也不靠谱之际,我们只能诉诸源码了。鉴于 mongoose 的代码风格,直接搜索 .prototype.save就能找到。它实际上是在这里,但是看到这里让人更崩溃了:

Model.prototype.save = function(options, fn) {
  if (typeof options === 'function') {
    fn = options;
    options = undefined;
  }

  if (!options) {
    options = {};
  }

  if (fn) {
    fn = this.constructor.$wrapCallback(fn);
  }

  return this.$__save(options, fn);
};

它返回的是this.$__save的结果,但这个函数并没有返回值!日志显示,这里的this就是这个article实例(一个Document)。然而线索到这里似乎断了。怎么能凭空从一个没有返回值的函数返回一个 promise 呢?

我们又想到了一招:查看调用栈。但是 node 的调试器在这时却不好使了。我们只能用原始粗暴的console.trace手动搞出调用栈。虽然没有直接的线索,这个调用栈给了我们一些提示——save并不是直接调用的,而是经过了某种hook的封装。

这时,我想到了直接console.log(article.save),它居然是一个wrappedPointCut出来的结果!再在代码里搜索wrappedPointCut就非常接近真相了。关键的代码在这里

model.prototype[pointCut] = (function(_newName) {
      return function wrappedPointCut() {
        var Promise = PromiseProvider.get();

        var _this = this;
        /* something omitted for simplicity */
        var promise = new Promise.ES6(function(resolve, reject) {
          args.push(function(error) {
            if (error) {
              /* something omitted for simplicity */
              reject(error);
              return;
            }
            $results = Array.prototype.slice.call(arguments, 1);
            resolve.apply(promise, $results);
          });
          _this[_newName].apply(_this, args);
        });
        if (fn) {
          /* something omitted for simplicity */
          return promise.then(
            function() {
              process.nextTick(function() {
                fn.apply(null, [null].concat($results));
              });
            },
            function(error) {
              process.nextTick(function() {
                fn(error);
              });
            });
        }
        return promise;
      };
    })(newName);

注意到如果没有fn,那么会在第 33 行返回第 7 行定义的 promise,如果有fn,就会直接在第 21 行返回接续之前的 promise 的另一个 promise,这个接续(.then())里面利用了fn处理了结果,却没有返回。也就是说,这时候的返回值是Promise<undefined>而不是Promise<this>——我们被这样的 TypeScript 类型定义坑惨了!如果它一开始就告诉我们会这样,那该多好呀,就像这样:

    save(options: SaveOptions, fn: (err: any, product: this, numAffected: number) => void): Promise<undefined>;
    save(fn: (err: any, product: this, numAffected: number) => void): Promise<undefined>;
    save(options?: SaveOptions): Promise<this>;
    save(): Promise<this>;

2017 年 5 月 4 日

Like
Like Love Haha Wow Sad Angry
4

多项式和幂级数 (I)

Like
Like Love Haha Wow Sad Angry
2

今天我们讨论多项式

交换环上

Nilpotents and units are closely related. In a commutative unital ring R, if x nilpotent, a unit, then a+x is again a unit. If 1+x y is a unit for every y\in R, then x\in\mathfrak{R}, the Jacobson radical, approximately nilpotent. Continue reading “多项式和幂级数 (I)”

Like
Like Love Haha Wow Sad Angry
2

《交换代数》习题研讨 (I)

Like
Like Love Haha Wow Sad Angry
2

  1. Units
    , nilpotents, and idempotents lift from A/\mathfrak{N} to A.Proof: Units and nilpotents are obvious. In fact they lift to any of their representatives.
    For idempotents, if x^2=x\in A/\mathfrak{N}, then (1-x)x=0 \in A/\mathfrak{N}, so (1-x)^kx^k=0\in A for sufficiently large k. And (1-x)^k+x^k=1-x+x=1\in A/\mathfrak{N}, so lifts to a unit (1-x)^k+x^k. Moreover, its inverse u=1\in A/\mathfrak{N}. So (ux)^k(u(1-x))^k=0,ux^k+u(1-x)^k=1\in A and ux=x,u(1-x)=1-x\in A/\mathfrak{N}.
    This can be interpreted by sheaf theory, which is to be discussed in later posts. Continue reading “《交换代数》习题研讨 (I)”
Like
Like Love Haha Wow Sad Angry
2

一个简单的组合问题及相关的思考

Like
Like Love Haha Wow Sad Angry
2

Problem: If the decimal expansion of a contains all 10 digits (0,...,9), then the number of length n strings (shorted as n-strings) is greater than n+8.

If you’ve established the simple lemma, the solution is instant. Otherwise very impossible.

Lemma: The number C_n of n-strings is strictly greater than C_{n-1}, that of n-1-strings.
Actually,  we always have C_n \ge C_{n-1}: Every n-1-string corresponds to an n-string by continuing 1 more digit. The map is clearly injective. If C_n=C_{n-1}, it is bijective, meaning we have a way to continue uniquely, which means rationality. Rigidly, at least one of the n-1-strings occurs infinitely, but all digits after some n-1-string is totally determined by it. So if an n-1-string appears twice, it must appear every such distances, and so do the digits between. Continue reading “一个简单的组合问题及相关的思考”

Like
Like Love Haha Wow Sad Angry
2

生成函数和形式幂级数

Like
Like Love Haha Wow Sad Angry
2

今天我们讨论生成函数

Problem 1: Give a finite set of positive integers T. Let \mathfrak{T}_n be the collection of sequences (t_1,t_2,...,t_m), such that \sum_{i=1}^m t_m=n, and each t_i\in T. Let a_n=|\mathfrak{T}_n| for n\ge1 and a_0=1, and f(x)=\sum_{n=0}^{\infty}a_n x^n. Find out what is f(x) explicitly.

Solution: It is not hard to note the recursive relation a_n=\sum_{t\in T} a_{n-t} for n\ge1, if we set a_i=0 for negative i. So that f(x)=1+\sum_{t\in T} x^t f(x) and f(x)=1/(1-\sum_{t\in T} x^t), which is a rational function.

Variantion 1: If T is infinite, what would happen? Would f(x) still be rational?

We first analyze simple cases. If T=\mathbb{N}^+, it is expected that f(x)=1+\sum_{t=1}^{\infty} x^t f(x)=1+f(x) x/(1-x), so that f(x)=(1-x)/(1-2x)=1+\sum_{n=1}^{\infty} 2^{n-1} x^n. Indeed, in this case, counting the sequences amounts to divide a sequence of n objects arbitrarily. You can choose to divide between the ith and i+1th for 1\ge i\ge n-1, so in all 2^{n-1} choices, justifying the expansion.

I think it is equivalent to f being rational.

Theorem: \mathbb{Q}_p(t)\cap\mathbb{Q}[[t]]=\mathbb{Q}(t)

It is very interesting that this theorem is used for the rationality of \zeta-functions for algebraic varieties, which is part of the Weil conjectures.

2013 年 7 月 10 日

Like
Like Love Haha Wow Sad Angry
2

從張鈺哲說開去

Like
Like Love Haha Wow Sad Angry
4

大概一年級的時候,看一本書,名字忘了,反正講的是宇宙啊、星星啊、航天啊相關的知識(或者八卦?),與之配套的還有一本講地球的。那時我就是這麼喜歡這種書,經常成套地買。有一天就看到一段講月蝕和日食,說是雖然日食發生的比月蝕還要頻繁一點,很多人一輩子都沒見過日食卻見過月蝕,爲什麼呢,它說因為日食能看到的只是一小片區域,畢竟月球能擋住的小,而地球則是整個把月球都擋住了,另外日食時間短,就更容易受天氣影響。這時候就舉了個例子,說是某年中國某地發生日食,古稀之年的張鈺哲先生趕過來,適逢陰雨天憾而沒能看到。配圖是一位瘦削、長臉的老者,頗有混血風範。其時就對張鈺哲這個人很感興趣,然而那個時代信息閉塞,我一個小城市的小屁孩,沒有網絡,上哪去主動獲取信息呢?但這種好奇就一直在我腦中徘徊不去。另,我當時的眼光反而落在“古稀”一詞上更甚,一是沒什麼文化,覺得很新鮮,二個,是書的另一處講到人的壽命,說活七十歲,其實才兩萬多天,而那已經是“人生七十古來稀”了,我就常常覺得悲哀,因為各種書上常常提到冥王星公轉要二百多年,哈雷彗星也要七十幾年才迴歸一次云云。

而後很多年,都沒有網絡。日子一天天地過,到初一有了網,我又沉迷遊戲(黑歷史就在QQ空間,感興趣可以慢慢翻),然後又英語、又數學、又政治、又數碼地,就淡忘了這些事。 Continue reading “從張鈺哲說開去”

Like
Like Love Haha Wow Sad Angry
4