preload
Jul 07

好久没写博了啊,因为太懒了,同时因为没啥好写的,有 Evernote 和微博了嘛。

首先,下载并安装 Mac OS X 64 Bit 版本的 Eclipse IDE for C/C++ Developers ,然后通过 Eclipse 的插件管理器安装 EGit 。这都没啥好说的。当然还要装 cmake ,可以通过 homebrew 来装,具体请参见这篇文章

这时,假设你在 GitHub 上面已经有了一个项目,那就可以按照如下步骤在 Eclipse 环境中建立起相应的工程了:

  • 选择 File -> Import... 菜单项,如下图:
  • 选择类型 Projects from Git ,然后继续:
  • 点击右侧的 Clone 按钮,在弹出的对话框中,首先选择 Protocol 为 git+ssh ,然后在 URI 栏的 git+ssh:// 字符串后面输入你的 GitHub 工程目录。注意,与 GitHub 页面上显示的不同,github.com 后面的冒号要换成斜杠,如下图所示:
  • 点击 Next 之后,如果你之前没有在 Eclipse 中用过你的 Git SSH 私钥,这时就会弹出一个对话框问你私钥的保护口令。不管怎样,之后就是选择 Git 开发分支的对话框:
  • 选好之后点 Next ,之后就是设置本地的检出目录、本地分支、远程仓库之类的了,一般默认就好,大概:
  • 点击 Finish ,git 就开始工作了,完成之后,就会回到之前的选择 Git 仓库对话框,不过这次就多了一个可以选择的仓库,点它吧:
  • 点了 Next 之后,下一步就是选择从该仓库建立新的 Eclipse 工程的方式了:
  • 根据 cmake wiki 中的说法,推荐采用的方式是由 cmake 来生成 Eclipse 的工程文件,所以这时我们需要打开一个终端,进入刚刚检出的 Git 仓库目录,然后执行以下命令:
    $ cmake -G"Eclipse CDT4 - Unix Makefiles" -D CMAKE_BUILD_TYPE=Debug .
    

    这个命令就会在这个工程目录中建立 .project 和 .cproject 这两个 Eclipse 的工程配置文件啦。回到之前的 Eclipse 对话框,选择其中的 Import Existing Projects 就可以了。点击 Next :

  • 勾上你的工程之后,就可以点 Finish 了:
  • 打完收功。

PS:话说我在贴图的时候,为什么不能选择 Medium 的图片大小呢,选了之后文章里面的缩小了的图就显示不出来,可是点进去之后又能看到单独的图片页面。真是奇了怪了,难道有什么限制?

Tagged with:
Mar 03

Update 2010-03-04: 问题解决了(部分,见后)

首先,你要安装好UnixODBC软件包,这个就不多说了。

然后,安装Oracle官方客户端,因为我的使用环境为Fedora 12,所以我下载安装的是oracle-xe-client-10.2.0.1-1.0.i386.rpm。

装好之后,要设置一些环境变量,我是用的一个Shell脚本来完成这项工作的,你可以把它放在/etc/profile.d/目录下并加上可执行权限来让其在系统启动时自动执行,也可以直接运行这个脚本来使其立即生效。脚本如下:

  1. # File: oracle.sh
  2. export ORACLE_HOME=/usr/lib/oracle/xe/app/oracle/product/10.2.0/client
  3. export PATH=$PATH:$ORACLE_HOME/bin
  4. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib
  5. export TNS_ADMIN=$ORACLE_HOME/network/admin
  6. export TWO_TASK=test
  7. export NLS_LANG=”Simplified Chinese_china.UTF8”
  8. export NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS"

其中TWO_TASK变量的值应设为tnsnames.ora文件中的那个键名(下面再讲),NLS_LANG是为了UTF-8编码,而NLS_DATE_FORMAT设的是DATE类型的数据在插入和显示时所用的格式,这里设的是我比较习惯的一种。

然后在$ORACLE_HOME目录中建一个子目录network/admin,并在其中新建两个文本文件如下:

  1. # File: sqlnet.ora
  2. SQLNET.AUTHENTICATION_SERVICES = (NTS)
  3. NAMES.DIRECTORY_PATH = (TNSNAMES, EZCONNECT)

这个文件中的都是一些固定的配置,其中TNSNAMES和EZCONNECT分别为我所用到的两种指定连接目标的方式。另一个文件:

  1. # File: tnsnames.ora
  2. test =
  3.   (DESCRIPTION =
  4.     (ADDRESS =
  5.       (PROTOCOL = TCP)
  6.       (HOST = 192.168.0.2)
  7.       (PORT = 1521)
  8.     )
  9.     (CONNECT_DATA =
  10.       (SID = test)
  11.     )
  12.   )

第二行中的test就是TWO_TASK环境变量的值,SID就是数据库名,其它的就不需要说明了。当然,在这个文件中可以配置多个数据库连接,用不同的名称来标识(TWO_TASK就是用来选择的),这个例子中只有一个配置。

以上的步骤主要是Oracle相关,都完成了之后,可以先测一下Oracle客户端是不是能正常使用了:

$ sqlplus username/password@test

这个命令可以连接数据库。这种参数格式就是上面提到的TNSNAMES方式,而EZCONNECT方式则是下面的样子(这种方式就不需要在tnsnames.ora文件中进行配置了):

$ sqlplus username/password@192.168.0.2:1521/test

成功之后可以试试下面的SQL语句:

> select * from some_table;

接下来就是配置UnixODBC了。首先是odbcinst.ini文件:

[oracle]
Driver = /usr/lib/oracle/xe/app/oracle/product/10.2.0/client/lib/libsqora.so.10.1

然后是odbc.ini文件:

[oracle]
Driver = oracle

都没什么可配的。

最后就是程序了,跟其它的ODBC程序没什么不同,除了连接字符串,需要写成这个样子:

“DSN=oracle;UID=username;PWD=password”

DSN就是ODBC Data Source名,也就是上面odbc.ini文件中方括号内的名字;UID就是用户名,PWD就是密码。

好了,就这么多。

PS. 我和另一位同事分别碰到过一次一个特别令人头痛的问题,就是在连接时,UnixODBC说libsqora.so.10.1(就是在odbcinst.ini文件中指定的那个)找不到,虽然这个文件明明就在那边;用ldd命令查看该文件的结果是说“这不是一个动态可执行文件”。更不幸的是,我不知怎么在我的机器上把这个问题给莫名其妙地解决了,却想破头都不知道是怎么解决的。太失败了。

Update: 问题解决了,出问题的都是新装的Fedora 12机器,后来做了次全系统的更新,再建个链接,就好了。只是不知道倒底是哪个软件包的版本太老,试了glibc、libtool、kernel都不是。

Tagged with:
Mar 02
  1. -- 首先是删除该数据库中该用户名下的所有表、序列与触发器,
  2. -- 其中触发器是通过表格级联删除的。
  3. declare
  4.   cursor usertables IS
  5.     SELECT *
  6.     FROM user_tables
  7.     WHERE table_name NOT LIKE 'BIN$%';
  8.   cursor usersequences IS
  9.     SELECT *
  10.     FROM user_sequences;
  11. begin
  12.   FOR next_row IN usertables loop
  13.     execute immediate
  14.       'drop table ' || next_row.table_name ||
  15.       ' cascade constraints';
  16.   end loop;
  17.   FOR next_row IN usersequences loop
  18.     execute immediate
  19.       'drop sequence ' || next_row.sequence_name;
  20.   end loop;
  21. end;
  22. /
  23.  
  24. -- 然后,就是一砣……的建表语句啦,
  25. -- 比方说下面就是两个含外键的表。
  26. CREATE TABLE "A" (
  27.   "ID" integer PRIMARY KEY,
  28.   "NAME" nvarchar2(64) NOT NULL         -- nvarchar2类型支持UTF8
  29. );
  30. -- Oracle中的表名与字段名最好写成这样引号加全大写的形式。
  31.  
  32. CREATE TABLE "B" (
  33.   "ID" integer PRIMARY KEY,
  34.   "A" integer NOT NULL REFERENCES "B" ON DELETE cascade,
  35.   "VALUE" nvarchar2(64) NOT NULL
  36. );
  37.  
  38. -- 接下来是第三部分,为所有的表创建对应的序列和触发器,以实现自增主键。
  39. declare
  40.   cursor usertables IS
  41.     SELECT cols.table_name, cols.column_name
  42.     FROM all_constraints cons, all_cons_columns cols
  43.     WHERE cols.owner = (
  44.       SELECT sys_context('USERENV', 'SESSION_USER')
  45.       FROM dual)
  46.     AND cols.table_name NOT LIKE 'BIN$%'
  47.     AND cons.constraint_type = 'P'
  48.     AND cons.constraint_name = cols.constraint_name
  49.     AND cons.owner = cols.owner
  50. begin
  51.   FOR nextrow IN usertables loop
  52.     -- 1000以下的留作初始测试数据用,见下
  53.     execute immediate
  54.       'create sequence "' || nextrow.table_name ||
  55.       '_PK_SEQ" start with 1000';    
  56.     execute immediate
  57.       'create or replace trigger "' || nextrow.table_name ||
  58.       '_PK_TRG" before insert on "' || nextrow.table_name ||
  59.       '" for each row begin if :new."' || nextrow.column_name ||
  60.       '" is null then select "' || nextrow.table_name ||
  61.       '_PK_SEQ".nextval into :new."' || nextrow.column_name ||
  62.       '" from dual; end if; end;';
  63.   end loop;
  64. end;
  65. /
  66.  
  67. -- 最后当然就是测试数据啦。
  68. INSERT INTO "A" (ID, NAME) VALUES (1, 'first name');
  69. INSERT INTO "B" (ID, AID, VALUE) VALUES (1, 1, 'Jay');
  70. INSERT INTO "B" (ID, AID, VALUE) VALUES (2, 1, '杰');
Tagged with:
Dec 19

这主意是我一同学想到的(至少是他告诉我的),基于一种实际的需要。假设你有两台内网机,都没有公网地址,你想从一台机器访问另一台机器的服务,这时要怎么办呢?当然很可能是我无知了,就我所知,网上倒是有一些提供这类服务的网站,但都是基于 Windows 的远程桌面的,至于 Linux ,我没找过。

于是那家伙联想到了 IM Bot 这种东西。现在网上有各种各样的 IM Bot ,可以通过与之聊天执行各种各样的自动化任务,那为什么不能让它把发过去的命令交给另一台机器的 Shell 执行,并把结果返回呢?这不就实现了一个简单的 Telnet 了吗?这就是最初的想法了(我没去找现成实现,因为我也该练练手了)。而我刚好才折腾过 OpenVPN ,于是就有了标题所说的这个最终想法。这样的话,就能一下子从只支持一种应用协议到支持几乎所有的应用协议了,而且实现的工作量也同时减到了最低。真是好主意啊,自我夸奖一下 :-)

其实真正解释起来是很简单的想法啦,就是把从虚拟网卡读到的 IP 包,base64 一下,当聊天内容发给对端就万事 OK 。

选择 XMPP 当然不仅是因为它的开放性,最重要的原因就是有一个现成的冤大头给我们利用,就是 Google 啦,而且还有在线的聊天记录供调试用,何乐而不为呢 :-)

实现这个想法的关键就两点,一是 XMPP 协议库,Python 的有好几个;二是对虚拟网卡的读写,Linux 下有现成的虚拟网卡,Windows 也有,不过要另装,而 Python 的标准库就完全能实现对其的读写了。不愧是胶水语言啊。

目前来说,一个还只支持 Linux 的阳春版已经实现出来啦,在这里,欢迎大牛们前去参观。

之后的开发可能会转向 C 也可能不会,就看 Python 在 Windows 下的表现啦(当然也可能是应用户需求)。

Tagged with:
Dec 13

肯定有许多人遇到过这个问题,也肯定有许多现成的解决方案被发现,可是我没找到(或者说没耐心找)。无所谓,自己找到的话,得到的就不仅仅是最终结果了。

闲话少说。其实以前也干过类似的事情,就是在保存的时候自动去掉所有的行末空白;不过这个操作使用 Vim 本身的功能就能实现,不需要借助于外部程序,所以只要简单地设一个 BufWrite 时的自动命令就行了。但当需要借助外部程序的时候,在 BufWrite 或者 BufWritePre 或者 FileWritePre 时执行的自动命令就不行了,老是提示说什么文件本身已更改,是否确定写入。我在这上面反覆试了好多次,才发现是自己没有搞清楚底下到底发生了什么。要让外部程序对文件内容进行格式化(或者其他什么操作。我想调的是 gofmt,顺带一提),首先当然得要那个外部程序能看到你做过的,还没有保存的更改,所以说应该是先写入再操作的,即在 BufWritePost 的时候执行自动命令。而我一开始就把基本步骤给搞反了。

这一步搞对了之后,接下来的问题就是要让 Vim 知道格式化后的变化。这个我之前倒是真不知道,原来每天都要用到的 :e 命令,全称就是 :edit ,在不加参数的情况下就是干这个事情的。

:edit 这个命令本身的话,应该是影响不到语法高亮功能的,在我看来。我的意思是,原来是语法高亮的,执行这个命令之后应该还是会语法高亮。但是在我把上面的两步命令做成自动命令之后,代码格式化好了,却也变黑了。不得其解,于是又在后面加上了 syntax enable 这条命令。结果,倒是连执行外部命令之后的那个回车确认步骤都给屏掉了,算是一个意料之外的成果。

最后,做为参考,把我的相关配置给贴出来吧。就是在文件 ~/.vim/ftplugin/go.vim 中,加了这么一段代码:

  1. function! b:Format()
  2.     !gofmt -w %
  3.     edit
  4.     syntax enable
  5. endfunction
  6.  
  7. au BufWritePost <buffer> :call b:Format()

最后的最后,在 c.vim 中也如法炮制了一下,不过用的是 indent 。

Update (2010-08-18): 参考这篇文章的话,可以将上面的函数修改如下:

  1. function! b:Format()
  2.     let regel=line(".")
  3.     let regec=col(".")
  4.     %!gofmt
  5.     call cursor(regel, regec)
  6. endfunction
  7. autocmd BufWritePre <buffer> :call b:Format()

这样的话,Undo History 不会因为对外部程序的调用而被清空,而且外部程序所作的修改也同时成为了 Undo History 中的一步。

Tagged with:
Dec 05

我的编译环境是Visual Studio Team System 2008版本9.0.21022.8 RTM,Windows Mobile 5.0 SDK R2(VS2008自带的版本)。当然,Perl也是需要的,我装的是ActivePerl。我要编译的OpenSSL版本是0.9.8e。

VS2008的安装

那个Web Developer Tools(好像叫这个)的安装会失败,又不能不装,根据网上的说明,要把它的目录单独从光盘上解压出来安装,且安装的时候要挂Office的安装光盘(我的Office版本是2007)。这个装好了之后,再从光盘上安装VS2008就没有问题了。

配置编译环境

首先要写个定义环境变量的脚本,以把CE的路径给配置好。VS2008的那个命令行启动脚本只配置了VC的环境变量,没有配CE的环境变量,因为这个是要根据用户需要来做的。我写的脚本内容如下,你可能要根据自己的VS2008相关安装目录做调整。

set PATH=D:\Program Files\Microsoft Visual Studio 9.0\VC\ce\bin\x86_arm;%PATH%
set INCLUDE=D:\Program Files\Microsoft Visual Studio 9.0\VC\ce\include;C:\Program Files\Windows Mobile 5.0 SDK R2\PocketPC\Include\Armv4i;D:\Program Files\Microsoft Visual Studio 9.0\VC\ce\atlmfc\include;%INCLUDE%
set LIB=C:\Program Files\Windows Mobile 5.0 SDK R2\PocketPC\Lib\ARMV4I;D:\Program Files\Microsoft Visual Studio 9.0\VC\ce\atlmfc\lib\armv4i;D:\Program Files\Microsoft Visual Studio 9.0\VC\ce\lib\armv4i;%LIB%

set OSVERSION=WCE501
set TARGETCPU=ARMV4I
set PLATFORM=VC-CE
set WCECOMPAT=D:\program\wcecompat

其中PATH、INCLUDE、LIB都是CE相关的环境变量,在编译WCECompat和OpenSSL的时候都是需要的,后面的是WCECompat需要的,其中有些是OpenSSL也需要的。

然后启动Visual Studio 2008命令提示(开始菜单),并运行上面的这个脚本,编译环境就算是准备好了,可以开始编译WCECompat了。

编译WCECompat

要编译CE版本的OpenSSL的话,需要WCECompat这个库,这个库实现了许多在Desktop开发环境中有而在CE中没有的功能,而这些功能是编译与使用OpenSSL所必需的。

不过,官方版本的WCECompat现在已经用不起来了(虽然我没有进一步确定),我们要使用OpenSSL开发团队fork的分支,其下载地址在http://github.com/mauricek/wcecompat,在这个页面上有打包下载的链接,我下载的版本是cb796f5(git是用GUID来表示版本的)。

下载下来之后,先解压,我的解压路径是D:\program\wcecompat,并进入该目录。这里注意,根据WCECompat的文档说明,不能把它解压到一个含空格的路径底下,不然会报文件找不到之类的错,这是因为WCECompat的make文件没有用引号括住文件名,而空格会把一个文件名分成两个。

然后要修改一个源码文件src/time.c,在其中找到函数_tzset的定义,这是一个空函数,把它整个注释掉(或删掉),不然在后面我们的程序要链接这个库的时候,会报这个函数重复定义的错。

运行命令

D:\program\wcecompat>perl config.pl

这样会生成需要的makefile文件,不过还不能直接用它来编译,要手动改一下,删掉或注释掉其中的``src/winmain.cpp \''这一行,不然后面链进我们的程序时会报找不到WinMain所需的main函数这个错(大概是这样说的)。

然后运行命令

D:\program\wcecompat>nmake

开始编译。完成之后,就会在lib目录中生成两个库文件wcecompat.lib和wcecompatex.lib,都是静态链接库。

编译OpenSSL

还是同一个命令行窗口,进入OpenSSL的解压目录,我的是D:\program\openssl-0.9.8e。根据INSTALL.WCE文件中的说明,运行下列命令进行编译:

D:\program\openssl-0.9.8e>perl Configure VC-CE
...
D:\program\openssl-0.9.8e>ms\do_ms.bat
...

这一步完成之后,打开生成的ms\ce.mak文件,把第19行CFLAG的变量定义中的/WX选项给删掉,不然后面有编译警告会被当成错误,从而编译失败。

D:\program\openssl-0.9.8e>nmake -f ms\ce.mak
...

这一步的编译过程最终还是会失败退出,不过不要紧(也许吧),失败的是测试程序,这时看out32_ARMV4I目录中,已经有编译好的libeay32.lib和ssleay32.lib这两个文件了。因为前面是用ms\ce.mak文件而不是ms\cedll.mak,所以编译出来的这两个文件都是静态链接库。

测试OpenSSL

最后来测试一下我们编译出来的OpenSSL(以及WCECompat)。打开VS2008,新建个智能设备的项目。然后打开项目属性对话框,先在“配置属性-> C/C++ ->常规”的“附加包含目录”中把D:\program\openssl-0.9.8e\inc32给加进来(测试的话加绝对路径就好了,实际开发的时候要把所有需要的文件拷到项目目录里面)(另,不要加WCECompat的include目录,不然会有abs函数不属于global namespace的编译错误),在“配置属性->链接器->常规”的“附加库目录”中把D:\program\openssl-0.9.8e\out32_ARMV4I和D:\program\wcecompat\lib给加进来,在“配置属性->链接器->输入”的“附加依赖项”中加入“wcecompat.lib wcecompatex.lib libeay32.lib ssleay32.lib”。

打开个源文件(比如某个事件处理函数定义的地方),在最开始的地方加进去OpenSSL的头文件包含

#define NO_SYS_TYPES_H
#include <openssl/ssl.h>

其中第一行的宏定义是需要的。虽然WCECompat为我们提供了sys/types.h头文件,不过我们不能用(理由见上)。

然后在事件处理函数中加条语句

SSL_CTX *ctx = SSL_CTX_new(SSLv23_method());

编译下试试吧,看有没有问题。如果没有,那就恭喜了,我们暂时解决了将OpenSSL用于CE开发的问题,我还不能保证后面不会出其他的问题。

虽然我之前配置环境变量等步骤都是基于PocketPC来做的,不过最后编出来的库貌似也能用到Smartphone程序中,不知道会不会有什么问题。Mobile开发果然还是相当的麻烦啊。

Tagged with:
Jul 14

其实嗫,这个问题已经有标准和其他的解决方案了。标准解决方案参见
Control.Applicative 中的 ZipList ,不过这东东用起来蛮麻烦的说;其他解决方案见
bff 库的 Data.Zippable 模块,嗯,我还没搞明白这玩意怎么用,不过总感觉杀鸡用牛刀
了有点(Template Haskell ,以及其他依赖)。

所以,如果你只是跟我一样,看 Data.List 中的那一砣 zipn 不顺眼的话(其实也只是看
着不顺哈,用着还是蛮顺的,反正实现不用我写),一个更简单的方案在此:

  1. > z :: [a -> b] -> [a] -> [b]
  2. > z = zipWith ($)

吼吼,够简单的吧。其实这跟 Control.Monad 中的 ap 和 Control.Applicative 中的 <*>
是一类东东啦,只不过是针对列表的 zip 功能滴。

那要怎么用嗫,也不是很麻烦啦,像这样就可以了:

*Main> (,) `map` [1,2,3] `z` "abc"
[(1,'a'),(2,'b'),(3,'c')]
*Main> (,,) `map` [1,2,3] `z` "abc" `z` [Nothing, Just False, Just True]
[(1,'a',Nothing),(2,'b',Just False),(3,'c',Just True)]

这里的 map 所对应的自然就是 Control.Applicative 中的 <$> 啦。

还有更好玩的哦,如果再加上上一篇博中的不定参函数
的话呢:

*Main> buildList `map` [1,2,3] `z` [4,5,6] :: [[Int]]
[[1,4],[2,5],[3,6]]
*Main> buildList `map` [1,2,3] `z` [4,5,6] `z` [7,8,9] :: [[Int]]
[[1,4,7],[2,5,8],[3,6,9]]

那我这次要说得就这么多啦,至于怎么用,就请大家尽情地发挥你们的想象力吧(其实是我
想象力不够 :-(

Tagged with:
May 06

说实话,我之前有就这个题目很有激情地写了很长很罗嗦的一篇草稿的,哦,确切来说是大半篇,直到被打断,激情不再,这篇草稿也就此躺了两个多月。好吧,其实是我还不是八卦那块料,就不卖弄了,直接总结。

要在 Haskell 中实现可变长参数列表,就是利用其 Typeclass 系统,对函数进行最终结果类型和中间函数类型之间的重载,然后利用 Haskell 的类型推导机制为我们自动调用合适的重载版本。嗯,就这么简单。下面是一个最简单的例子:

  1. class BuildList a r | r -> a where
  2.     buildList' :: [a] -> r
  3.  
  4. instance BuildList a [a] where
  5.     buildList' = id
  6.  
  7. instance BuildList a r => BuildList a (a -> r) where
  8.     buildList' as = \a -> buildList' $ as ++ [a]
  9.  
  10. buildList :: BuildList a r => r
  11. buildList = buildList' []

好吧,这个例子我是从这里看到的,或者说我就是从这里学到这个东东的。在此友情提醒那些和我一样不怎么有耐心的家伙一句,原理解释在后面。

按照我的想法,上面这个例子应该还可以进一步简化以去掉对两个语言扩展的依赖的,就是写一个 buildIntList 的特化版本,结果不幸地失败了。有知道的大大告诉我一声,在下洗耳恭听。

Update: 不需要大大们来告诉我了,我已经知道该怎么简化上面的这个 buildList 了,不过简化后的版本是 buildCharList ,也就是 buildString 啦,而不是原先说的 buildIntList 。先看实现:

  1. class BuildString r where
  2.     buildString' :: String -> r
  3.  
  4. instance BuildString [Char] where
  5.     buildString' = id             -- or whatever you want
  6.  
  7. instance BuildString r => BuildString (Char -> r) where
  8.     buildString' cs = \c -> buildString' $ cs ++ [c]
  9.  
  10. buildString :: BuildString r => r
  11. buildString = buildString' []

这样就把对 MultiParameterTypeClass 和 FunctionalDependency 的依赖给简化掉了,可是又同时增加了对 -XFlexibleInstances 选项的需要(因为 instance BuildString [Char] where 这一行;如果你想把 [Char] 写成 String 的话,就还要再加上个 -XTypeSynonymInstances),暂时想不到更好的办法了。

另外,关于我在这里为什么要用 Char 而不是 Int 呢,是因为 'a' 的类型很明确,就是 Char ,而 5 的类型就不明确了,因此,你可以这样调用 buildString :

*TestVarargs> buildString 'a' 'b' 'c' :: String
"abc"

可是却必须这样调用 buildIntList :

*TestVarargs> buildIntList (1::Int) (2::Int) (3::Int) :: [Int]
[1,2,3]

虽然在我看来,GHC 应该可以倒推出每个参数的类型的,可他就是不认,我暂时也没什么办法。

Tagged with:
Apr 08

C/C++ 中的宏是个让人又爱又恨的家伙(感觉这句话好俗啊),在带给我们很强大的 Power 的同时,却也需要我们绝对的细心,而这也是在宏定义中诸多惯用法的由来。

当我们要把一组语句定义成一个宏,并希望像用一条语句那样来使用它的时候,我们就可以这样做:

  1. #define MACROX \
  2.     do { \
  3.         statement1 \
  4.         statement2 \
  5.         ... \
  6.     } while (0)

之后,我们只要在调用方写 MACROX; 就可以了。之所以不能简单的用花括号把这一组语句括起来,是为了防止扰乱 if 语句中的 else 子句的匹配问题。嗯,确实是个不错的主意啊,直到我遇到了一个,嗯,也许只有我才会遇到的问题。那就是,我在这组语句中,不能用 break 和 continue 。是不是觉得这需求蛮诡异的,呵呵,连我自己都觉得,不过这是我在重构一些相似的循环体时遇到的。但总之,我是找到了另外的一种包装方法啦,自己想想也没什么问题,大概,就像下面这样:

  1. #define MACROX \
  2.     if (true) { \
  3.         statement1 \
  4.         statement2 \
  5.         ... \
  6.     } else

嗯,看起来是不是要比大家都知道的那个诡异很多啊。不管了,以后发现问题了再说吧。

Tagged with:
Mar 22

我以前只知道,在 C++ 中,如果要表示一个类是抽象基类,只要为它定义一个纯虚析构函数就可以了,不想到今天用到的时候才发现,连接的时候通不过,说没有找到那个函数的定义。到网上一搜才知道,原来纯虚函数也是可以有定义的啊。在实现文件中加上它的空定义之后,一切就 OK 了。

不过,因为我那个基类里面已经有了一个纯虚函数,所以貌似没必要把析构函数也定义成纯虚的。

PS. 在 C++ 里面要实现 AST 还真是一件麻烦的体力劳动,要写那么多类,每个类还得写那么一砣的样板代码。幸好不是那种长期运行的服务器程序,不需要太考虑 memory leak 的问题,就不用写析构函数和 delete 了(实际上是懒得写),不然样板代码还要更大砣。不知道有什么更好的方法没。

Tagged with: