about.me/acgtyrant

2018 Jul 3
我又双迁移到 NeoVim 了

两年前,我曾在技术博客发表过《用 Neovim 取代 Vim》,但当时我很快又放弃 NeoVim 了,继续用 Vim。因为我始终没找到令我满意的 NeoVim 的 GUI;再次,Ubuntu 16.04 没有打包 NeoVim,每次在新开发机上部署开发环境编译安装 NeoVim 也很麻烦;最后,Vim 7.5 也支持异步并发 API,以致我不太青睐 NeoVim 了。

其实我之所以一定要用 GUI,因为我一直没真正配置好虚拟终端,终端多路复用器和文本编辑器的色彩,导致 Vim 在虚拟终端和多路复用器会话里显示的色彩非常怪异,所以我实际上过去一直只用 GVim,不爱用 Vim。一月半前,在机缘巧合的他人帮助下,我正确配置好了 Vim 的 256 color,以致以后不论在本地还是远程主机上,都可以直接在虚拟终端下愉快地用 Vim 开发了,这也间接地为后来的迁移大作战扫除了障碍。

一月前,我偶然读到Vim 8 下 C/C++ 开发环境搭建,里面介绍了众多令人叹为观止的现代 Vim 插件,有些需要在 NeoVim 上才能用。于是我心血来潮,又双迁移到 NeoVIm 了,并且迁移大作战成功!

其间我发现 NeoVim 本身已经编译好了 Linux 上通用的二进制包,直接下载 nvim.appimage(版本为目前最新的 3.1)到 $HOME/.local/bin 并命名为你喜欢的即可,当然别忘了修改其为可执行文件。这样以后只要如此少量的命令,就可以快速在开发机上部署新开发环境了,毕竟不比安装发行版的 Vim 二进制包麻烦多少,何况 Ubuntu 16.04 发行版提供的二进制包 Vim 版本还处于 7.4 时代,老掉牙。

我再重新解说下迁移到 NeoVim 的好处:

全面并发

虽说 Vim 8 也支持了,但这当初应该是 NeoVim 倒逼 Vim 出来的,不可忽略。此外在全面并发的支持下,vim-plug 安装我高达三十多个的插件,不到半分钟!这可怕的速度在以前 Vim 时代是不可想象的。Vim 8 下 C/C++ 开发环境搭建 里面提到的很多强大插件,也实实在在得到了全面的速度提升好处。

默认配置更加友好

说起来 Vim 的一些过时默认配置会让你大吃一惊,比如它默认的 encoding 至今依然是 latin1 !Neovim 当然早改用 utf-8 了。还有一些不合时宜的默认 setting 也纷纷得到了修改,免去用户手动配置之苦,我就在迁移原 .vimrc 时删去了 25 行多,净化完毕,更加极简!

充分遵循 XDG 规范

Vim 默认的 .vimrc.vim 均一般在 $HOME 下,Neovim 则全挪为 $XDG_CONFIG_HOME/nvim/init.vim$XDG_CONFIG_HOME/nvim.

Vim 编辑文件时,可以有多达四个的数据文件:backup, swapfile, undofileviminfo. Unix 下的 Vim 分别默认存在 ".,~/tmp,~/", ".,~/tmp,/var/tmp,/tmp", ".", 其中 viminfo 的具体储存位置我一时还查不出来,就懒得深究了。后来依云指出:undofile 默认没有值,不保存撤销记录,viminfo 位于用户目录。

NeoVim 则全改储存在 $XDG_DATA_HOME/nvim/ 下各自的目录里,此外 viminfo 更是被抛弃,被叫 ShaDa 且更为先进的二进制文件所代替,后者位于 $XDG_DATA_HOME/nvim/shada/main.shada.

再加上用户自行安装的 NeoVim 二进制包也在 $HOME/.local/bin 里,一家人更加整整齐齐。

完美无瑕的真彩

具体详见在 Linux 下全面使用真彩

看!我的括号会发光!

那么问题来了:怎么迁移?

Arch Linux 用户都装 neovim, python-neovimpython2-neovim. Ubuntu 用户下载官方编译好的二进制包并更改为 $HOME/.local/bin 的可执行文件。

除非你用干净的 dotfiles 管理 Vim, 否则自行清理插件;再迁移到 XDG 目录下;改用支持 Neovim 的 vim-plug 并重新安装所有插件,可以先按 https://github.com/junegunn/vim-plug/wiki/tips#automatic-installation 加入相关代码,以致只要一打开 NeoVim 它就会自动安装包管理器本身和所有包,超方便的;按 vim-difference 来打扫 init.vim 中已无用的设置。

Written with StackEdit.

2018 Jun 26
在 Linux 下全面使用真彩

Termite, Tmux and NeoVim in true color.

我过去一直没真正弄好虚拟终端、Tmux 和 Vim 的配色,今天下定决心,终于彻底理清了它们在颜色上的来龙去脉。本文目标如题,那些本来支持真彩的成熟 GUI 程序就不说了,重点便是如何配色好 Termite,Tmux 和 NeoVim 等。

颜色有色彩深度之分。

8-bit color 又名 256 color,用于最早期的彩色 Unix 工作站,于是一些古代虚拟终端连同这种局限也虚拟出来了,即它们只支持 8 位颜色,比如 xterm 和 urxvt。还有古老的终端多路复用器 screen 和 Tmux 也默认用 256 颜色。

24-bit color 又名 true color,一共有 16,777,216 colors,于是有人习惯用 “16 million colors” 称呼它,就像先前大家有时用 256 color 称呼 8-bit color 一样。它的表示形式要么是 a 24-bit hex value (e.g. #4a32b1),要么是 an rgba vector (e.g. rgba(16, 32, 64)

32-bit color 基于 24-bit color 而生,增加了 8-bit 透明通道。真・现代虚拟终端 Termite 就支持,以致它的背景可以变成半透明,阿宅喜欢靠它来看 waifu。此外 Termite 还可以直接把 8-bit color 单射到 24-bit color,比如 color0 = #073642 意味着把第一个 8-bit color 映射到某 24-bit color #073642

有个环境变量叫 TERM,contains the type of the running terminal, e.g. xterm-256color. It is used by programs running in the terminal that wish to use terminal-specific capabilities. 我猜它主要是用来告知程序当前虚拟终端支持的颜色,好让后者自动选择相应合适的颜色空间。我发现 Termite 和 Tmux 会自动设置相应的 TERM,即分别为 xterm-termitescreen,由此看来用户不需要手动指定 TERM 了。

不过上面既然提到了 Tmux 的 TERMscreen,而后者又只支持 256 color,于是得让它改用 true color,好在 Tmux 2.2 已经支持了,设置也不难

最后的关键便是 Vim 的配色了,256 colors in vim 发表于二〇〇六年,它还把支持 256 color 的 XTerm 当成所谓的现代虚拟终端,还说若 vim 想在 256 color 虚拟终端使用 256 color colorscheme,需要在 .vimrc 显式设置 set t_Co=256,这说明 Vim 本身默认的颜色空间可能都小于 8-bit!它不愧比虚拟终端还要古老。

五年后,依云发表《让 Vim 在终端下和 GVIM 一样漂亮:gui2term.py 更新至 3.0 版》,按这篇的说法,Vim 在 256 color 虚拟终端里没法像 GVim 用 24-bit color 即 True Color ColorScheme,需要通过某脚本把 True Color ColorScheme 转换成 Vim 可加载的 256 color ColorScheme,一劳永逸解决配色问题,虽然是近似的。

如今,@NanozukiCrows 发了一条推:

就是这推促使了我彻底折腾并搞定颜色的来龙去脉。现在御用真・现代虚拟终端 Termite 本来就支持 32-bit color,御用终端多路复用器 Tmux 2.2 也支持 True Color。尽管我不清楚 Vim 对 True Color 的支持如何,但御用现代文本编辑器 NeoVim 设置 termguicolors 就可以支持了,再用任意 True Color ColorScheme,比如 Plug 'morhetz/gruvbox, colorscheme gruvbox 即可,当然,别忘了去掉 set t_Co=256 这蛋疼的古代颜色支持方案。

True Colour (16 million colours) support in various terminal applications and terminals 也科普得很棒,里面有若干 console programs 实在让人眼一亮,比如 timgls-icons,原来真彩支持好了,在虚拟终端看图像和 Icons 也不难。

Written with StackEdit.

2017 Sep 24
无视原则

遥想还呆在 Acfun 的当年,大家在关于自杀现象的某文章里正战得欢时,一条评论留给了我很深的印象,原话已不可考,按照模糊的印象大概是:日本社会算是彻彻底底冷漠到了极点,同情也好,挖苦也好,都意味著被自杀者触动了。然而日本大多人已经冷漠到对愈演愈烈的自杀现象依然无动于衷,该干嘛的就干嘛,不管自杀者在人生尽头故意能闹得有多惊天动地,也很快地在一阵喧嚣后被世人尽数遗忘。于是自杀现象俨然成了日本人中平淡无奇,再熟视无睹不过的日常。

此后很长时间里,它成了我一直所发现的「最残忍的被动反应」,注意这行为只属于被动激发的反射行为分类。若要考虑主动层次上的恶意行为,方法多得是,但这并不在本文的讨论范围之内。

我并不是左右脸均给敌方扇的圣人,若必要时,便只能以敌意对抗恶意。不过我贯彻更为中庸的「无视原则」,即遭到来自敌方的恶意攻击行为,若物理上的损失可忽略,就自动激发无视其的被动反应。注意,这里的「无视」纯粹就是字面上的意思,并不掺带多余的任何其他反应。而且,无视既是高贵的蔑视,又是宏大的宽容。无视原则能够化来恶意为虚无,且不作出任何攻击反弹,「我蔑视他人这恶意的攻击,但我还是宽恕他人这愚蠢的行为」,很符合我的「和」价值观。

恕我再改言之,无视意味著绝对无比的「无动于衷」,算是绝妙的蔑视态度了。那些并不造成任何物理上损失的恶意攻击,可以粗糙地归为一类:「挑衅」。这行为上的的确如同字面意义,为的是想激怒受害方,使后者产生不悦感,那么「无视」反应恰恰就能很好地挫败敌方的这一意图。心境如水,霸气十足。我来示范下:我在知乎上拉黑用户的原则是一旦遭受来自恶意用户的人身攻击,就直接「静默拉黑」,这举动在敌方看来就仿佛当事人泰然不为所动,没有任何反应,只轻轻地把他一手指弹到黑名单池,一劳永逸且不可逆转地切断了一切接触途径。毫无疑问,这脑补能当场给予敌方99999伤害,后者越中二伤害就越高。

于是,我反而奇怪 Real Life 中为什么仍旧有那么多人热衷于讨论令人反感的事物,例如几年前天天输的国足,这货臭名昭著得不能再臭,于是满口国足国足的观众,就如同满口排泄物排泄物一样令我厌恶。国足已经差劲到没有再被关注的任何价值,就让它自生自灭去,还偏要提,不光自我弄脏嘴巴,还影响他人心情。

我说过,欣赏也好,厌恶也好,都意味著被触动,有触动就意味著关注,而而大量的关注就能创造经济上的价值二〇一二年年初归真堂活取熊胆的事件闹得很火,其实这可以算是对官方非常有利益的炒作,试想,如果哪天某人的亲人突然处于生命危险期,要被救活只有一个办法:「使用熊胆产品」,那么他人最先想到的会是什么?没错就是那因活熊取胆而一举臭名昭著的归真堂,但当事人一般情况下会不管一二,就消费熊胆产品先救活了亲人再说,于是说到底赢家还是归真堂。凤姐,芙蓉姐姐等人颇为使人反感,却笑到了最后,也同理。

最后,我并不提倡对一切来自敌方的恶意一律采取「无视原则」,否则有时就和阿Q的「精神胜利法」没两样。那么什么情况下不便采用被动的「无视原则」呢,那便是遭到物理上的实际损失时,这时就要站出来主动维持自己的利益了。对于归真堂案例,若要惩罚这家公司,理想的情况便是舆论批评得严重到后者被法院执行经济制裁,或是遭到市场上的抵制而损失惨重。后来这家公司上市曲折无比。

2017 Sep 19
只有 Python 高手知道的 tuple

熟悉 Python 的人都知道,它从语法上支持的内置数据结构一共四种,即 tuple, list, dict, set 等, sequence comprehension 一共有三个,唯独没有 tuple comprehension, 其形式被解析为生成器表达式,而且表示单元素的 tuple 也很不一样,需要额外添加一个在新手看来十分碍眼的逗号(1,).

但其实我们只简单地把 tuple 理解为一个 immutable 版 list 了,并没有深刻理解到它的本质。推上有人如此一针见血地指出:

我终于发现了,tuple 既可以当数据结构,又可以当模式来匹配变量。所以 tuple 说穿了是一个「不规则又不可变的数据结构或模式」,自然没有「推导规律」介入的余地,也难怪没有所谓的 tuple comprehension 了。

一旦洞察 tuple 的本质后,你就发现,tuple 原来大有文章:

其一,事实上,返回多个值的函数实则是在返回一个 tuple, 只不过可以省略括号而已。一旦理解透这个,就不会再写出 tp, label_tp = [], [] if len(result) == 0 else zip(*result) 这大坑了。

其二,只有一个变量的 tuple 在模式里可谓等价于变量本身,于是不是说 (1,) 里的逗号多余,而是它本身的括号就多余,毕竟没人爱写 (a,) = (b,); 其实 () 才是唯一真正特殊的 tuple; 于是纯括号就可以被名正言顺地赋予数学上的意义了,即 (a) 等于 a, 而且其实这还带来了方便 wrap line 的额外好处:

1
2
3
hfm_dir_str = (
'/home/acgtyrant/Projects/HamsterForMTK/'
'Hamster_Android_SDK/src/com/mapbar/hamster')

其三,虽说没多少人喜欢 (1,) 这写法,但我们可以分拆 sequence 值成多行,且每行后更有一个逗号!这对强迫症患者真是终极福音:

1
2
3
4
5
6
7
8
9
10
11
12
phodopus_evaluate_command = (
command_pathname,
'-v', video_pathname,
'--proto', proto_pathname,
'--model', model_pathname,
'--mean', mean_pathname,
'-c', cascade_model_pathname,
'--lf_proto', lf_proto_pathname,
'--lf_model', lf_model_pathname,
'--lf_mean', lf_mean_pathname,
'--noshow',
)

于是我习惯分拆函数的超长形参列表多行了,且最后一行以右括号结尾;多行的 sequence 值则倒数第二行以逗号结尾,倒数一行以右括号结尾且不缩进。

其四,tuple 不光可当安全的 immutable 数据结构,而且效率公认比 list 好

此外,tuple 在模式匹配上也威力十足:

其一,方便交换变量:a, b = b, a, 毕竟等价于 (a, b) = (b, a) 模式匹配嘛。

其二,PEP 3132 – Extended Iterable UnpackingPEP 448 – Additional Unpacking Generalizations 也是模式匹配。此外,我们不能直接用星号 unpack generator, 但照样可以模式匹配 a, *_ = range(10), 毕竟只要后者能迭代就行了。现在看来,由于我已经洞察了 tuple 的本质,对它原本的坏印象也没有了。

其三,如果你想要可读性更好的模式匹配,可以试试 collections.namedtuple 库。

2017 Sep 12
「听力残疾」不完全科普

中国残疾人联合会指定的听力残疾等级标准指出:

听力残疾,是指人由于各种原因导致双耳不同程度的永久性听力障碍,听不到或听不清周围环境声及言语声,以致影响其日常生活和社会参与。

听力残疾的分为如下几级:

听力残疾一级:

听觉系统的结构和功能方面极重度损伤,较好耳平均听力损失≥ 91 dB HL
,在无助听设备帮助下,不能依靠听觉进行言语交流,在理解和交流等活动上极度受限,在参与社会生活方面存在极严重障碍。

听力残疾二级:

听觉系统的结构和功能重度损伤,较好耳平均听力损失在 81~90 dB HL
之间,在无助听设备帮助下,在理解和交流等活动上重度受限,在参与社会生活方面存在严重障碍。

听力残疾三级:

听觉系统的结构和功能中重度损伤,较好耳平均听力损失在 61~80 dB HL
之间,在无助听设备帮助下,在理解和交流等活动上中度受限,在参与社会生活方面存在中度障碍。

听力残疾四级:

听觉系统的结构和功能中度损伤,较好耳平均听力损失在 41~60dB HL
之间,在无助听设备帮助下,在理解和交流等活动上轻度受限,在参与社会生活方面存在轻度障碍。

我属于一级,但有很多人难以暸解其具体困难之处,以致有相当多不准确的解读,我便感到很有必要一次性澄清了。

所发现的解读主要有:「听不见」、「听不清」和「听不懂」等。若你语文嗅觉足够敏锐,当然容易一眼看出其不同:

  • 听不见:又称「听不到」,即几乎感觉不出一切声音
  • 听不清:听得见声音,但是「干扰」很严重,从而只接收到「失真」的声音,听障人本身的听力缺陷有可能会引发此现象。
  • 听不懂:听得见又听得请,但是不知要如何理解其意思。就像小孩听不懂大人的高深话题,中国人听不懂老外说啥一样。

不过,就我的经验来看,实际情况是要更复杂些,以上三种并非彻底彼此独立,即彼此有多为交集:

其一,若听不见的缺陷并不完全,也就是说还是能听得见部分声音,就会产生由「听不见」缺陷所导致的「听不清」障碍。打个比方,一个听障人能明确听得见「aoe」,但是几乎听不见「iu」。于是若一个人对ta说了「啊噢呃咦唔」,但是ta是「听不清」完整的句子的,因为ta只能明确地听得见「啊噢呃」,「咦唔」就听不见,于是ta或许可以通过对方的口型看出有发出了五个字的声音,ta 还知道前三个字是什么,但是就是不清楚后两者又是什么。于是此谓由并不完全的「听不见」缺陷,所导致的「听不清」缺陷

其二,若一个人听不清对方的话,当然就会不好理解对方所表达的,即由「听不清」所导致的「听不懂」的障碍。我猜学过英语的诸位应该深有体会:在正式学习英语之前,当听见一段纯粹的英语对白时,只会觉得它如同「呼噜呼噜」一样足够「模糊不清」的一段声音,要模仿发音更是不可能,因为我们连其所包含的每一个音标单位的发音都不知道。于是我们若要听得懂这段对白,就得弄清楚句子的完整读法,即把它分割成众多独立的单词,再一个一个地弄清楚其意思,其发音又是什么。当我们熟悉了每一个单词,于是要听得懂完整句子就不难了,理解其意思也水到渠成。所以结论是:若要听得懂外语,就必须先克服「听不清」的障碍,而这恰恰是需要练习的。其实对母语的学习也是如此,只不过因为在母语环境中,实在足够「耳濡目染」,以致大家长大了,都会几乎忘了自己对母语的掌握,其实是有「练习」过的印象。

我说过自己属于听力残疾一级级别,当然也就处于纯粹的「听不见」处境了,听力损失有一百分贝多,是什么样子的?家庭聚餐时,我扭头看电视,全然听不见位置相邻的父亲狂涛怒吼过。若你靠近我耳旁用力尖叫,能听得见一点点点。不过如今可不好说,因为听力衰退越来越厉害了,其实据母亲说,一开始听力分贝损失是 85-110, 可至少在两年前衰退到 100-120 了。

好在「助听器」能打破这困境,原理应该是通过耳背上的仪器收集物理上的外部声音,并放大声音响度并输送到耳部,突破听力损失的阈值,从而让用户「听得见」了。

仍无法解决「听不清」的障碍,我说过听力缺陷比较复杂,其实上面所用到的「啊噢呃咦唔」例子还不够贴切,用颜色来类比更好:正常人能看到五颜六色的世界,但听障人就像色盲一样,只能看到有颜色偏差的世界。什么颜色看得见,什么颜色看不见?这个就因人而异了。

于是就轮到我正在使用的「人工耳蜗」隆重登场了。听障人的种种问题,大多应该可以归结于「耳蜗」的生理缺陷,但人工耳蜗就可以取而代之,即通过手术,把它嵌入到耳蜗处,并通过「电磁感应」现象,来接收附在外部的「言语处理器」所在物理上所收集到的外部声音信号,转化为神经上的脉冲信号,绕过了原本功能缺失的耳蜗,直接发送给大脑,从而让大脑直接感到「听觉」。

既然彻底绕过了原本有缺陷的「耳蜗」,则「听不清」障碍就几乎全无,用户能像正常人一样直接听到极其不失真的「原声」。所以如今我实际上只处于「听不懂」处境,即对汉语和英语的掌握,前者需要进一步的练习,后者需要从零开始,这些都只是如同小孩牙牙学语的另一外回事了。

当然,就目前的技术来说,人工耳蜗并不能像天然耳蜗输送纯粹的「原声」,只能尽可能地减少「失真度」而已。就目前来说,我的困难之处如下:

  1. 需要花时间从头开始学习英语听说,很久以前我就开始上某英国绅士的英语口语课,当前只听得懂大概六分之一;该绅士真是好人。
  2. 几乎没法进行「多人对话」,很多饭局就只能默默地低头吃饭……
  3. 在空间相当大却又嘈杂的地方,很难听清对方的话。
  4. 没法一边注意力分散在别处时,一边听同行者的话,比如上下楼梯。
  5. 很疲劳的时候,听力效果也会大幅度下降,可谓累得不想再听了……
  6. 普通话口音很怪,先天没训练好,矫正要付出大量精力。

2017 Sep 4
单位元对编程的启发

最近,我在处理一个文本文件,它有三列,分别为文件名,数字,字符串,且其中第一列中有若干重复了的部分,于是我需要把这些重复的行合并起来,即第二列合并为总和,第三列合并为一条新字符串,且用空格分隔。

我一开始想如此合并:

1
2
3
4
5
sum = 0
string = ''
for duplicate_line in duplicate_lines:
sum += int(duplicate_line.split()[1])
string = ' '.join(string, duplicate_line.split()[2])

但迭代完后 string 并不符合我的期望,它包含了一个 heading space! 这令我想起了抽象代数学上的「单位元」,即它在某二元运算中,与任意元素运算后,结果的值恒为后者本身。比如加法中的 0 与任何数字相加,恒返回后者的原值;乘法中的 1 也同理。

于是我们可以说,把 ‘ ‘.join() 当二元运算符来看时,不存在单位元,即 ‘ ‘.join([a, b]) 中 b 为任意字符串时,不存在 a 变量能使这函数返回 b 本身,哪怕变量 a 为空字符串时,也会返回包含了一个 heading space 和 b 值的新字符串!就像我上面的代码一样。不过 ‘’.join() 当然就有单位元即空字符串。

反观 Python 的 int 加法,它就有单位元,即 sum 的初值 0, 也难怪它和悲剧的 string 君不同,可以安全地返回我期望的值。

这启发了我又一条编程规范:要小心那些没有单位元的函数或运算符,并处理得当。其实上面合并 string 的代码可以妙用 list comprehension, 回避掉 heading space.

1
' '.join([duplicate_line.split()[2] for duplicate_line in duplicate_lines])

举一反三,Python Sequences 的 index 为什么从 0 而不是 1 开始计呢?您倒不如想想另一个问题:Sequences Index 的单位元是什么?即对 Index 进行二元运算时,加多少就返回原 Index? 当然是 0 了!再看 C 指针,它和 0 相加时就返回原指针,也难怪 C 的 Array Index 和 Python 的 Sequences Index 都从 0 开始,这是为了当直接把原 Index(甚至 C 的指针即地址)和 Index 初始值(即单位元)相加时,能方便且直接地得到原 Index(甚至 C 的原指针即原地址)

2017 Sep 3
什么情况下 map 比 list comprehension 好?

事实上,list comprehension 公认效率比 map 好所以当 map 比 list comprehension 可读性好且不在乎性能时,可以优先用前者。

那问题又来了:什么时候 map 的可读性比较好?当且只当低阶函数是你所熟悉的 Python 函数,便利的 sequence 对象也够一目了然时。比如我就经常用它转换命令列表,够一气呵成:

1
2
3
4
5
6
7
8
traincascade_command = [
command_pathname,
'-w', weight,
'-h', height,
'vec', vec_pathname,
]
traincascade_command = map(str, traincascade_command)
subprocess.call(traincascade_command)

妈妈再也不用担心我不小心传 int, pathlib.Path 进命令列表了,写的 list comprehension 又长又臭得超出 79 个字符了。同理,rects = map(int, rect_strs), positives = map(abs, integers) 之类的精炼表达式也可以。

因为你很清楚 str, int 和 abs 等是一元函数,作用是什么;反之,即用你所不熟悉的 Python 函数,比如 a = map(b, c), 这时你往往会产生四重疑惑:

其一,b 是一个 callable 对象吗?
其二,b 可以只接受一个形参吗,即它到底是不是一元函数?
其三,c 是否为 sequence 对象?
其四,c 的 iterable 元素又是什么鬼?

更别说用雪上加霜的 lambda 了。

但换用 list comprehension 就没这问题:a = [b(d) for d in c], 显然 b(d) 表达了 b 彻彻底底是个一元函数,且 c 作为 sequence 对象时 iterable 元素是 d, 最终 a 会被 bind 到一个 list 对象。可读性更胜一筹。

举一反三,你可以琢磨什么情况下 filter, funtools.reduce 等也能如法炮制。

2017 Sep 2
using 指示命名空间习惯不好

如果不禁止 using 指示,那么你在多人项目里,如何判断同一个标识符在由不同工程师编写的源代码里,是否为同一个东西?

如果大家保持一致性,即某些命名空间都要 using 指示出来,别的不 using 指示。然而,又如何规定哪些要指示哪些不用指示?害得大家束手束脚地保持一致,规定也众口难调地麻烦;对个人项目也是一样的道理,如果开发者随心所欲地在这里不 using 指示 std, 却又在那边 using 指示它,从长远来看维护将是灾难。

最后,你能保证将来永远不会取消对某命名空间的 using 指示吗?否则你得给这被 using 指示的命名空间里所有用到的标识符一一加上命名空间作用域前缀,你想象一下怎么弄吧,你有办法自动化处理且绝不出错吗?

对我来说,与其大费周章地划出可以被 using 指示的命名空间,还不如一劳永逸地用奥卡姆柴刀砍掉 using 指示用法,也省了我在 tweets 里列举的种种缺点。

我写 C++ 时,爱把迭代器对象直接声明为 iterator 并拿来用,也很清楚 vector 是一个「向量」对象或别的什么,反正不会是 std::vector 那种容器,我放心我舒服。你呢?

2017 Aug 31
代码,编码,编程,规范和风格

我最近下定决心不再拖延博客的更新,但也懒得重启独立的技术博客,于是以后技术博文就在这里发表。

计算机科学基础老生常谈,编程语言也是如此。我没兴趣当 language lawyer, 就讲讲颇为人文的编程规范吧。

大家过去应该也看到过不少类似的关键词:代码风格、编码风格、编程风格和编程规范等等。光一家著名的 Google Coding Guide Style 的民间翻译就囊括了以上用词。最初,李开复先生称它为「编码规范」,且我以前翻译 Google C++ Guide Style 时,没仔细琢磨好翻译用词,就同时用到了「风格」,「编码」,「编程」等等,不一致啊。

咬文嚼字了一会,先从「代码」、「编码」、「编程」汰劣存优开始吧。「代码」只是一个名词,不如动词来得亲切;「编码」即是表示一系列机器级别指令的名词,又是表示手动转换机器码与汇编码的动词;然而人类死跟机器码较劲的时代已经结束了,现在是高级编程语言的天下了,我们不想拘泥于表面的「编码」,而只关心足够抽象的 Program, 即程序,所以现在看来「编程」是最理想的翻译用词。

至于「风格」与「规范」又如何呢?我倾向于 The conventions we follow 对此的分析:「风格」只讲究代码的格式化形式,比如缩进啊空白啊注释形式啊,然而「规范」涉及的更为广泛,即同时包括了编程风格和其它规范,就像该不该用某语言特性,用什么编译工具链,甚至如何版本控制等等。

编程可谓一门艺术,又是一项工程。若要运用得炉火纯青,可不能光划个风格就了事,还要相当洞悉所用编程语言的特性,以及开发环境的理想部署等等,这时「规范」就很重要了。从此以后,我将不时地探讨种种编程规范。

2017 Aug 30
C++ 怎么念

我有听力障碍,于是就算三次元里周围有人谈及 C++ 且把它读成「C加加」,我也不怎么有印象。倒是长期以来受英文资料薰陶,英文上的 C++ 便先入为主了,毕竟 + 在英文里就是 “plus”. 于是我凡看到这词时,脑海就默默地响起 “C plus plus”, 也难怪我在上北京大学的公开课时,不由得呵呵一笑。不过推上大多数人不以为然,有的猜读为 “C double plus”, 有的搞怪成「C艹」,有的恶俗地叫「C屁屁」。

其实 C++ 到底读成什么并不重要,就看你和谁打交道了。如果就只局限于品读谭浩强大师著作、不会上 StackOverflow、天天百度 C++98/03 文档、从来没去过官网 isocpp.org、靠 Visual C++ 6 开发的小圈子里,大家约定俗成地一起读成C加加,也挺好;如果你向来满口污言秽语,「C艹」很适合你;如果你想和 Bjarne Stroustrup 之风流谈笑风生,小心点别念错 cplusplus 是了。

其实查 C++, C#, TeX 之类的英文读法也不难:去英文维基百科看条目即可,一般开头就有音标,C++ 念 /siː plʌs plʌs/.