Fortran 中根据语义的补全
Fortran 中的 if 结构,函数声明等,都有不少的信息冗余。如
if (3 > 2) then !... endif real function add(a, b) ... end function
后面的 endif, end function 等,长度都不少。这样虽然提高了可读性,但手动敲下未免还是太麻烦。把时间花在敲入一个很长还必定会有的东西上面,很不划算。如果用 snippet 之类的方案,需要记住一个 key, 然后每次按 tab 展开,除了语言本身,又多了一个记忆 key 的负担。
于是,我想到了一个更为自动化的补全方案,就是让程序根据已经输入的内容,在敲入回车键换行时,自己判定这是不是一个未完成的 if 结构,然后自动在后面补全 endif.
因为这个功能绑定到了一个很重要的按键上,所以功能要尽量的不添乱 。所以,我想到把当前行按照程序语义给解析出来。如果能成功的解析,就补全后面的内容;如果不行,就执行普通的Enter.
解析程序的语义,还可以带来另外的好处。一个是自动加空白,如
do i=1,n
给自动化成为
do i = 1, n
有了空格之后更加的好读。另一个功能是把一些冗余的东西省掉,如
if (a > 3) then
有用的信息只有
if a > 3
因此,只需要敲下上面的文字,再回车,就可以补全成符合 Fortran 语法规范的形式。
代码在此 https://github.com/fermat618/fortran-construct-complete
整数,小数;整型,浮点型
这四个概念,前面两个,表示数学中的整数,小数;后面两个,表示计算机中相应的表示。
在C语言中,整数一般用整型表示,而小数一般用浮点型来表示。先学C语言的人往往容易对此有固定的印象。同时会觉得浮点型表示的数都是不精确的。然而,这个印象是不准确的
浮点型数也可以表示整数,而且在整数不是非常大的情况下,这种表示是精确的。对浮点型表示的整数,进行加、减、乘操作,得到的仍是精确的结果。仍可以用来表示长度,表示数组下标等。
在很久以前,浮点数的计算很慢。然后,现在的发展下浮点数的计算已经快多了。而对于脚本语言来说,速度往往不是那么的重要。Cpython 的运行要比相应的C语言慢100倍左右。这时,使用整型来表示整数的速度优势完全没有了,而空间优势也不大。所以,在脚本语言中区分整型和浮点型已经是件很尴尬的事。而 Javascript 的数值类型已经默认只有一个,就是浮点型。Matlab 的默认类型也已经是浮点型。Python 3 中 int 已经默认可以为任意大的整数,算是给整型的存在又找回了点面子。
Python 中的 sum 函数
做 projecteuler.net 上面的题时,遇到一个问题,需要对某个范围内的数进行筛选,并且把所有符合的结果都给出来。直接些的做法是做一个数组,在筛选的时候,遇到符合条件的,就放进那个数组里面。但维护状态容易出错,我尽量避免。另一种做法就是
def foo(n): if p2(n): return [a] else: return []
然后把结果集合在一起。
集合在一起的方法在 Haskell 里面就很多了 concat, join, >>= id 等。Python 里面,我首先想到的就是用 reduce 和 + 弄了个。
from functools import reduce from operator import add reduce(add, map(foo, range(10000)), [])
但看着总是有些不爽。因为对于普通数的相加,一个 sum() 函数就搞定了,哪用得着那么麻烦。于是试了一下
sum(foo(x) for x in range(10000))
报错,再了看那错误信息,说是什么 int 不能和 list 相加。然后又去查了一下 sum 函数的文档
sum(iterable[, start]) -> value Returns the sum of an iterable of numbers (NOT strings) plus the value of parameter 'start' (which defaults to 0). When the iterable is empty, returns start.
虽然上面写了是求数值的和,而不是字符串的,我还是试了下把最后面的那个参数置为 []
sum((foo(x)for x in range(10000)), [])
果然是可以啊。
接着我试了一下字符串的相加,得到一个有意思的错误。
sum(['foo','bar'],'') TypeError: sum() can't sum strings [use ''.join(seq) instead]
它报了个错,然后给了个更好的写法。
''.join(seq)
用这个作为一大串字符串的相加,不但可读性好,而且效率上可以预期将比 sum() 函数的直接使用加法把字符串联接起来要好。这点在 PEP8 里面也说过,虽然 Cpython 实现的字符串的 += 运算符比较高效,但不能对其它实现作如此预期。
s = '' for s2 in xxx: s += bar(s2)
的代码应该写成
''.join(bar(s2) for s2 in xxx)
debian 下编译安装同时支持 python2 和 python3 的 vim
Debian 下编译安装同时支持 python2 与 python3 的 vim
由于 python 相比 vim 原来的那个脚本实在是强太多,我转到用 python 来写 vim 代码了,并且是坚定的 python3.
而有两个我想用的 vim 插件却是 python2 的,自己改成 python3 版本的话工作量太大,而且升级的时候又得操 心。
以前在 Debian 下编译的 vim 无论怎么都没法做到同时支持 python2 与 python3. 最好的情况是,编译好后 同时有 +python/dyn
与 +python3/dyn
特性,但只要执行了其中一个 python
版本的命令,另一个 python
版本的命令便无法执行。
后来发现是 Debian 自带的那个 python 的问题,具体问题我忘了。
既然系统自带的那个有问题,解决的办法就是自己安装一个没问题的 python 了。但系统的 python 不能 随便换,否则遇到的麻烦接连不断。那就编译一个 vim 专用的 python 吧,不放在 $PATH
当中,也不影响 系统原来的 python 和其它程序。
编译 vim 专用 python, 安装目录当然就放在 vim 的目录之来了。下载 python2 与 python3 的源代码,并 如此
./configure --prefix=/usr/local/share/vim/usr --enable-shared
定义 ./configure
时的选项。这样安装时就自动安装到 vim 所属的目录下面了,并且其共享库也编译好了。
然后就是编译 vim 了。经试验,改如下两个环境变量
PATH=/usr/local/share/vim/usr/bin:$PATH LD_RUN_PATH=/usr/local/share/vim/usr/lib export PATH LD_RUN_PATH
之后,再进行 ./configure
$PATH
环境变量,决定 vim 找到的 python 的位置。而 $LD_RUN_PATH
环境变量,则是链接 vim 时所需 要的,可以让 vim 找到 python 的 so 文件的位置。有了这个之后便不再需要 $LD_LIBRARY_PATH
变量了。
./configure \ --with-features=huge \ --enable-python3interp \ --enable-pythoninterp
接着 make
完成后测试 ./src/vim
不出问题的话,就可以成功了。
需要注意的是,每次成功编译后,需要执行
make clean && make distclean
否则不能编译成功。
安装好后再看两个 python 所占大小,才区区 202MB
,想一下可以不费劲地用现有的 python2 的插件了,真是很 划得来啊。
掉 intel fortran 编译器返回派生类型的方式的坑里了
最 近写 matlab 与 Fortran 的混编程序时,为了使程序写起来更容易,采用了 Fortran 2003 中 iso_c_binding 模块的一些内容。这些内容的采用使得那些接口写起来 更容易 了,本来松了一口气,想换到 intel fortran 编译器下的时候,又来了那个让我 一看到 就头疼的段错误。
然后又得开始调试了。在仔细查看了程序后,没发现有问题。 然后开始写小程序测试 里面用到的语句。吸引以前的教训,现在仔细检查调用 matlab mex API 后的返回值。可 是 type(c_ptr), 这个 iso_c_binding 模块中规定的放 C 指 针值的数据类型, 却没法打印出来看。
通过 locate iso_c_binding
命令。找到了 intel 的 iso_c_binding.f90 这个文件。打开后查看其内容,发 现
type, bind(c) :: c_ptr integer(xx), private :: ptr end type
type(c_ptr) 的内容被声明为了私有的。 把这个文件拿出来,去掉 private 属性,再手动编译,总算是可以看到 type(c_ptr) 的 内容了。 把同一个 mex 程序用 intel fortran 与 gnu fortran 编译器分别编译两份, 再开一个 matlab, 在里面声明一个变量,传进去,查看各个函数的返回,找到了 mxGetPr 函数返回的结果不对。
又另写了一个函数,把 mxGetPr 函数的返回值申明为 integer(8), 再看结果,却又是对 的了。
终于定位到了是 integer*8 和 type(c_ptr) 的区别,才想到可能是 intel 对于后一 种可能采取了不是传值而是传值的方式。另写函数一验证,果然如此。
派生类型与内置类型一样,也是标量。同样的标量,在 intel fortran 中的处理,却是 很不相同,这很超出我预期。这种处理方式真是相当的不统一。标量传值,矢量传址,这 才是统一的方式嘛。
又试了给函数加上 bind(c) 属性,这次终于可以传值了。但是加上了这个属性的函数, 其编译出来的文件的符号的处理又跟普通的 fortran 函数不一样了。 matlab 给 Fortran 的那些 api 的符号名本来是按照 fortran 来的,这么一来就得在 bind(c, name=xxx) 里面写明了。使用 nm 得到实际的符号名后,写死在里面,再加上条件编译,总算是使得 gfortran 与 intel fortran 都可以编译那个程序了,代价就是只能用在 64 平台及开启 了 -largeArrayDims 选项的情况下了。
vim打开文件时自动添加cscope连接
cscope是看源代码的得力工具。在编译进了cscope特性的vim中,可以在vim内部方便地使用cscope进行跳转. 使用cscope需要先生成一个默认文件名是cscope.out的交叉引用文件,在vim中使用cscope也需要使用:cscope add 命令指定交叉引用文件所在位置,也即所谓的建立cscope链接。
通常,cscope.out会在一个项目的根目录下面。当在这个目录下打开vim时,使用vim的cscope相关部分给出的示例设置
if filereadable("cscope.out") cs add cscope.out " else add database pointed to by environment elseif $CSCOPE_DB != "" cs add $CSCOPE_DB endif
可以自动添加cscope.out文件。然而,当在一个项目的子目录下打开一个文件的时候,这份配置却不能正确添加cscope.out文件了。往往用cscope开始查找某个东西的时候,就提示说没有建立cscope链接,很是扫兴。
想做什么事被打断是很不爽的,所以就需要自动添加cscope.out文件了。比较直接的想法就是从当前目录一层一层地向上查找,如果找到了某个cscope.out文件,就把它添加进来。考虑到python3代码写起来比vim脚本爽多了,而我的vim又是自己编译几乎必带这个支持的,所以就在vimscript中用python3来实现了。
if has("cscope") && executable("cscope") if has('python3') py3 <<EOF import os, os.path from itertools import takewhile def iterate(fun, x): yield x for element in iterate(fun, fun(x)): yield element for path in takewhile(lambda x: x!='/', iterate(os.path.dirname, os.getcwd())): cscopefile = os.path.join(path, 'cscope.out') if os.access(cscopefile, os.R_OK): # file readable vim.command('cscope add ' + cscopefile +' '+ path) break EOF endif if $CSCOPE_DB != "" cs add $CSCOPE_DB endif endif
用python的好处是容易让代码成为自己想法的自然表达。程序的目的是在当前目录及其父目录中检查cscope.out文件的存在,再添加这个文件,那么这个程序自然的结构就是首先得到当前目录和它的父目录,然后再对这些目录进行一个一个地检查。生成父目录的方式就是对一个目录不停地用basename函数,想到一个生成某个函数对一个值不断应用而产生的序列的函数 iterate(), python 的itertools中竟然没有,那就自己定义一个吧。python的生成器使得程序既不会在当前目录下已经找到cscope.out的时候还去傻傻地生成当前目录的所有父目录的列表,还能让程序看起来很漂亮。用for in结构可比while漂亮。
最后再说下vim中的:cscope add命令。cscope.out文件中需要保存文件所在的位置,而这个位置通常是相对位置。如果vim的当前目录与生成cscope.out时的当前目录不一样,vim就会找不到cscope给出的文件位置。所以,就需要显示式指定cscope.out里面的路径是相对于哪个目录的,这就是cscope add命令的第二个参数。第一个参数自然就是cscope.out文件的路径了。