不用f前缀但是像fstring一样Print (fstring print without f-prefix)

Python 早期的Print语句受到 C 的直接影响,在把变量嵌入到输出的字符串时采用了占位符语法。使用这种打印语句,程序员需要在脑子里把所有的变量处理两次,一次是它们在字符串中的位置,一次是它们的具体名称。这样处理实际上比较繁琐而且容易出错的:

name = 'Bob'
age = '60'
print('%s is % years old.' % (name, age))

新的f-string 语法可以直接把变量嵌入到字符串中,算是朝正确的方向前进了一大步:

print(f'{name} is {age} years old.')

现在的问题是,我们总是要加上 f 这个恼人的前缀,特别是如果输出的消息是多行的话,每一行都要写上这个前缀,更是麻烦。我琢磨了很久,终于搞明白怎样在正常的字符串中嵌入变量名处理了,真是不错:

def Print(message):
  import inspect
  frame = inspect.stack()[1][0]
  print(message.format_map({**frame.f_globals, **frame.f_locals}))

name = 'Bob'
age = '60'
Print('{name} is {age} years old. - Print magic')

长久以来的一个心病终于解决了。

Del Valle Regional Park

感恩节期间,朋友邀请我们一家去 Del Valle Regional Park 游玩。本来以为就是一个普通的公园,没有想到深秋的湖水美极了。特别是后来两家人都在湖边扔石子玩,而我一个人沿着湖边向尽头的大坝走的时候,湖边很安静,阳光映在湖面波光粼粼却又静谧,天空高远,而山的线条也很柔和,很久没有这么心旷神怡的时候了。

大自然真是美好。

Jiggler 是个很棒的程序 (Jiggler is a Pretty Useful Application For Mac OS).

公司的电脑为了安全起见,自动锁定的时间设计的特别短。写程序的时候常常一思考,结果笔记本先自己锁定了,然后SSH连接也自动超时断开了,每次想好了主意要开始操作,结果先要手忙脚乱的解锁和重新连接,浪费不少时间。

最近静下来一个人琢磨,突然意识到,我其实可以找个程序自动的移动鼠标,防止屏保启动。一旦有了主意,上网一搜就看到了 Mac Jiggler 这个程序,还是个开源项目。装上一用,顿时觉得设计的特别贴心,思考的时候,它帮助你保持机器处于非锁定状态,如果你要主动让机器休眠,它也不会干涉你。几乎就是装上以后再也不用管了,真的节省了时间啊。

C++: Variadic Template and Fold Expression are Powerful.

这两天做一个Pipeline的Framework,见识了现在C++ Generic Programming 的强大功能,比原来的 Object Oriented Programming 看上去 Cool 多了:完全没有关系的一堆类型,一样可以放在框架里操作,写出来的程序也没有任何效率的损失,看来这些年C++又进步了很多。下面简要说说我的设计。

Class BaseData 表示 Pipeline 中的一个数据节点。 Class Phase 表示针对数据的某个操作。 Phase 接受一些 BaseData 作为输入,并输出到另外一些BaseData。为了灵活和简单起见,Phase 不设任何 Virtual Method 作为接口,只要就实现一个 Run Method 即可。程序概要如下:

class BaseData {
public:
 get() ...
 set() ...
};

class DataA : public BaseData {...};
class DataB : public BaseData {...};
class DataC : public BaseData {...};
class DataD : public BaseData {...};

class Phase {};

class PhaseA : public Phase {
  void Run(DataA*, DataB*, DataC*) {...}
};
class PhaseB : public Phase {
void Run(DataC*, DataD*) {...}
};

class Pipeline {
private:
  DataA a_;
  DataB b_;
  DataC c_;
  DataD d_;

public:
  Run() {
    AddPhase(new PhaseA(), &a_, &b_, &c);
    AddPhase(new PhaseB(), &c_, &d_);
  }

  void AddData(BaseData* base_data) {
     ...
  }

  // This is variadic template.
  template<typename PhaseType, typename... Types>
  AddPhase(PhaseType* phase, Types args...) {

    // This is fold expression, avoid template recursion.
    (..., AddData(args));

    // This is variadic forward.
    std::function<void()> run = 
        std::bind(&PhaseType::Run, phase, args...);
  }
};

程序的精妙全在最后的AddPhase 这个函数,它首先通过一个 Variadic Template 来处理任意接口的函数,然后又通过一个 Fold Expression 对所有的数据进行了类型检查和处理,最后又使用 variadic forward 创建了一个 Closure 供以后调用,真是妙不可言啊。

在做了若干后续改进之后,今天终于在Team Meeting 上把这个东西讲出去了,心头考虑了大半年的东西终于一朝做出,真是很开心。 🙂

Use Mouse in Terminal Vim like a modern editor

现代的编辑器(Ultraeditor, Editplus) 对于鼠标的使用非常频繁,最常见的是使用鼠标单击来移动光标,鼠标滚轮来上下翻页,或者鼠标双击选中某个词,然后拷贝或者粘贴替换。我最近学到了如何在Terminal-Based Vim 中也这样操作。

首先设置Vim 在 Normal Mode 和 Visual Mode 下激活鼠标:

set mouse=nv

这样我们就可以用鼠标单击或者滚轮移动光标了。另外,鼠标双击选中单词也正常工作了。

值得一提的是,我们也可以用键盘来选中一个词:假定我们的光标停在 information 这个词上(任何位置都可以),按下

viw

就可以选中整个词,这个操作不需要鼠标,在某些情况下很方便。

接下来的事情就容易了,拷贝:

y

粘贴并替换当前选中的词:

p

 

编译 Vim (Compile Vim Wiht Python 3 Support on Debian)

下载源文件

$ git clone https://github.com/vim/vim.git
$ cd vim/src
$ vi Makefile

编辑Makefile

  • 选择编译器
# COMPILER - Name of the compiler {{{1
# The default from configure will mostly be fine, no need to change this, just
# an example. If a compiler is defined here, configure will use it rather than
# probing for one. It is dangerous to change this after configure was run.
# Make will use your choice then -- but beware: Many things may change with
# another compiler. It is wise to run 'make reconfig' to start all over
# again.
#CC = cc

# For some reason, gcc does not install itself as /usr/bin/gcc
CC = gcc-9
#CC = clang

if gcc is not installed, installing it by running

$ sudo apt update
$ sudo apt install build-essential
  • 关闭图形
# GUI - For creating Vim with GUI (gvim) (B)
# Uncomment this line when you don't want to get the GUI version, although you
# have GTK, Motif and/or Athena. Also use --without-x if you don't want X11
# at all.
CONF_OPT_GUI = --disable-gui

# X WINDOWS DISABLE - For creating a plain Vim without any X11 related fancies
# (otherwise Vim configure will try to include xterm titlebar access)
# Also disable the GUI above, otherwise it will be included anyway.
# When both GUI and X11 have been disabled this may save about 15% of the
# code and make Vim startup quicker.
CONF_OPT_X = --without-x
  • 支持  Python3

安装 Python3 库文件

$ sudo apt install python3.8 python3.8-dev python3-distutils

静态链接 Python 解释器。

# PYTHON
# Uncomment lines here when you want to include the Python interface.
# This requires at least "normal" features, "tiny" and "small" don't work.
# NOTE: This may cause threading to be enabled, which has side effects (such
# as using different libraries and debugging becomes more difficult).
# For Python3 support make a symbolic link in /usr/local/bin:
# ln -s python3 python3.1
# If both python2.x and python3.x are enabled then the linking will be via
# dlopen(), dlsym(), dlclose(), i.e. pythonX.Y.so must be available
# However, this may still cause problems, such as "import termios" failing.
# Build two separate versions of Vim in that case.
#CONF_OPT_PYTHON = --enable-pythoninterp
#CONF_OPT_PYTHON = --enable-pythoninterp --with-python-command=python2.7
#CONF_OPT_PYTHON = --enable-pythoninterp=dynamic
#CONF_OPT_PYTHON3 = --enable-python3interp
#CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.8
#CONF_OPT_PYTHON3 = --enable-python3interp=dynamic

CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.8 --with-python3-config-dir=/usr/lib/python3.8/config-3.8-x86_64-linux-gnu

编译和安装

$ make

$ sudo make install

清理一切,从头开始

$ make distclean

 

第一代之后,基因型不再变化(Stationary GenoType Distribution)

基因 A 和 a是一对等位基因,在人群中构成了三种基因型:AA, Aa 和 aa,其中A是显性,a是隐性。在某些遗传性状比如蓝眼睛、左撇子等,基因型决定了实际表现的性状,假定A代表右撇子,a代表左撇子,那么Aa和AA都表现为右撇子,只有aa表现为左撇子。

假定这三种基因型在男性和女性中的分布概率是一样的,分别是 u, 2v, w,那么我们有:

(1)   \begin{equation*} \begin{split} P(AA) = u \\ P(Aa) = 2v \\ P(aa) = w \\ u + 2v + w = 1 \end{split} \end{equation*}

如果我们用p代表基因A在人群中的概率,我们有:

(2)   \begin{equation*} P(A) = p = u + v \end{equation*}

同样,我们用q代表基因a在人群中的概率,我们同样有:

(3)   \begin{equation*} P(a) = q = w + v \end{equation*}

因为我们假定男性和女性的基因型分布是一样的,那么对于下一代子女来说,因为他(她)们的基因一半来自父亲,另一半来自母亲,所以他们之中纯合基因型AA发生的概率就是继承自父亲和母亲的基因都是A的概率相乘,也就是:

(4)   \begin{equation*} u_1 = p^2 = {(u+v)}^2 \end{equation*}

根据同样的道理,子女代中基因型Aa和aa发生的概率分别是:

(5)   \begin{equation*} \begin{split} 2v_1 = 2pq = 2 (u+v)(w+v) \\ w_1 = q^2 = {(w+v)}^2 \end{split} \end{equation*}

上面的公式中我们依然使用 u, v, w 代表 基因型AA,Aa和 aa 的概率,但是使用下标1来表示这是子女代。有了每种基因型的概率之后,我们可以知道子女代中实际基因A和a的分布概率是:

(6)   \begin{equation*} \begin{split} p_1 = u_1 + v_1 \\ q_1 = w_1 + v_1 \end{split} \end{equation*}

那么再下一代的三种基因型的概率分别是多少呢,同样我们可以简单计算如下:

(7)   \begin{equation*} u_2 = p_1^2 = {(u_1 + v_1)}^2 = u_1^2 + 2u_1v_1 + v_1^2 \end{equation*}

(8)   \begin{equation*} \begin{split} &2v_2 = 2 p_1 q_1 \\ &= 2(u_1 + v_1)(w_1 + v_1) \\ &= 2u_1w_1 + 2 u_1v_1 + 2v_1 w_1 + 2v_1^2 \end{split} \end{equation*}

(9)   \begin{equation*} w_2 = q_1^2 = {(w_1 + v_1)}^2 = w_1^2 + 2w_1v_1 + v_1^2 \end{equation*}

同样,第二代中基因A和a的概率分别是:

(10)   \begin{equation*} \begin{split} p_2 &= u_2 + v_2 \\ &= (u_1^2 + 2u_1v_1 + v_1^2) + \\ &\quad (u_1w_1 +  u_1v_1 + v_1 w_1 + v_1^2) \end{split} \end{equation*}

(11)   \begin{equation*} \begin{split} q_2 &= w_2 + v_2 \\ &= (w_1^2 + 2w_1v_1 + v_1^2) + \\ &\quad (u_1w_1 +  u_1v_1 + v_1 w_1 + v_1^2) \end{split} \end{equation*}

计算了这么拉拉杂杂一大堆,到底有什么用处呢?下面是关键的一步,因为我们有:

(12)   \begin{equation*} p + q = u + 2v + w = 1 \end{equation*}

显而易见:

(13)   \begin{equation*} q = 1 - p \end{equation*}

我们很容易推导出上一代的基因A的概率和下一代基因A的概率关系如下:

(14)   \begin{equation*} p_1 = u_1 + v_1 = p^2 + pq = p^2 + p(1-p) = p \end{equation*}

同样的道理,我们也可以看到:

(15)   \begin{equation*} \begin{split} q_1 = q \\ p_2 = p_1 \\ q_2 = q_1 \\ p_3 = p_2 \\ q_3 = q_2 \\ ... \end{split} \end{equation*}

上式说明,在理想的情况下(基因A和a在男女中比例一致,每一个人都有同样的机会生育下一代,下一代的生男孩女孩的概率一致),每一代中基因A和a的分布实际上没有任何变化,这也符合我们关于遗传的直觉。但是基因型AA,Aa和aa的概率就不是这样了。一般情况下:

(16)   \begin{equation*} \begin{split} u_1 \ne u \\ v_1 \ne v  \\ w_1 \ne w \end{split} \end{equation*}

但是

(17)   \begin{equation*} \begin{split} u_1 = u_2 \\ v_1 = v_2  \\ w_1 = w_2 \\ u_2 = u_3 \\ v_2= v_3  \\ w_2 = w_3 \\ ... \end{split} \end{equation*}

这说明实际的基因型在第一代之后就稳定下来,不再变化。回到我们前面左撇子的例子:在理想情况下,第一代和第二代的左撇子比例(基因型,u,v,w)可能不一样,但是他们中的左撇子基因(p,q)实际是一致的,从第二代以后,基因型的比例也稳定下来,不再变化。

C++ 的一些新感想

因为在工作中C++用的比较多,听说 C++ 17 最近又引入了很多新特性,就买了几本 C++ 的书回来看。看了一阵,感慨颇多,罗列如下:

  • C++ 在 Library 开发人员和普通使用人员之间的鸿沟是越来越大了。新增加的很多特性都是给Library作者提供的,普通用户平时根本用不到。特别的,我以前总是以为所谓 Library 就是在语言之上提供一些包装好的功能模块,现在看到标准库中很多 Type Traits (比如 is_trivially_destructible) 都需要编译器的特殊支持才能实现,远远地超出了我以前的理解范畴。一般的 C++ 用户,如果打开标准库里面的程序想要研究一下某个特性是如何实现的,多半和天书一样看不懂。
  • C++ 现在的救命稻草就是运行时刻的性能了,这集中体现在C++为程序员提供的内存控制上。为此 C++ 几乎牺牲了其他可能的前进方向,并且不惜在语言上增加了巨大的复杂性。C++ 的创始人 Bjarne Stroustrup 前些年还提要在 C++ 中引入自动内存管理,现在是绝口不提了,估计搞出来也没有人用;为了更有效的支持大型对象,C++ 搞出来 Rvalue Reference 和 Move Constructor,让我这样的 C++ 的老鸟程序员也感到太过复杂;更不用提新的 std::string_view,本身大概唯一的作用就是在大字符串上搞一些子串操作比较有效率,结果强行塞进标准库,连 C++ 的资深作者 Nicolai M. Josuttis 都看不下去,在他的书中大声疾呼:string_view is considered harmful! 即便如此追求性能,看着 Programming Language Popularity Trend 上面 C++ 日渐下滑的曲线,我估计日后 C++ 也就只能守着高性能计算这块阵地了。
  • C++ 现在把编译时刻多态搞得很深,这一点其实和 C++ 把性能作为卖点也息息相关。原本是一个编译时刻模板替换的简单玩意,后来发现这个东西因为没有 Virtual table lookup 从而不损失运行时刻的效率,结果在这个方向上一发不可收拾,各种 type traits 如野草般生长,Library 全部都是模板库,里面根据用户提供的类型各种模板特化层出不穷,对于一般的 C++ 程序员来说根本就是天书,唯一的好处就是运行时刻生成的代码都是直接针对用户提供的类型操作的,效率很高。这方面C++阵营一直使用的例子就是 std::sort vs qsort from C。不过在我看来,最初的简单模板替换就已经完成了 90% 的工作,后来加上的一大堆特性,最多不过使得这个编译时刻多态(现在都叫 meta programming了,据称在 Type Domain 还是 Turing Complete)能够在其他 10% 的场合使用一下,实在算是事倍功半的一个好例子吧。

自己呢,还是好好的搞自己的数学,统计,概率,算法等等。这类语言的东西实在不值得深究啊。