Prolog与数据库
简介
Prolog用谓词表示数据,并且用合一将不同谓词关联,这与SQL的数据查询暗合。例如,
/* 数据库架构。
* student(id, name, age)
* course(id, name)
* studies(student_id, course_id)
*/
/* 下面的查询列出学生及所学课程的名称(SName, CName)。*/
student(S, SName, _), studies(S, C), course(C, CName).
-- 相应的SQL查询。
SELECT student.name, course.name
FROM student JOIN studies ON student.id = studies.student_id
JOIN course ON course.id = studies.course_id;
不难看到,Prolog的查询语法更简练,但它有必须写出整个tuple的缺点(将作进一步讨论)。QBE(Query By Example)是另一种采用合一的数据库查询方式。
尽管查询机制相似,但Prolog的视角完全不同。上例中,Prolog的语言是说查询成功的条件是用逗号分开的各个子句都成功。Prolog查询与SQL查询相似,都代表逻辑关系,而不是指定具体的查询实现机制,尽管二者都用简单的语法(暗示循环依次检索)来表达逻辑关系。在上述查询中,无论SQL引擎还是Prolog引擎都可以通过选择查询顺序(子句顺序)来提高查询效率。与SQL数据库相似,Prolog引擎也可以为数据建立索引或将其置入哈希表中。下面的例子则更好地说明了逻辑表达与实现机制的区别。在例中,引擎有责任优化查询,避免O(N^2)的开销。
/* 查找年龄最大的一个或多个学生。'\+' 代表“不存在”。*/
student(ID, Name, Age), \+ (student(_, _, Age1), Age < Age1).
SELECT id, name FROM student
WHERE age >= ALL (SELECT age FROM student);
SELECT id, name FROM student
WHERE age = (SELECT MAX(age) FROM student);
很明显,无论SQL还是Prolog在表达此查询时都较为笨拙。不过,语法的改进并非不可能(gprolog支持自定义运算符):
student(ID, Name, Age), \+ Age < student(_, _, #).
student(ID, Name, Age), Age >=all student(_, _, #). /* >=all 为自定义算符 */
Prolog崇尚用简单的元素组合出漂亮的功能,而不引入多余的复杂度。下例中简单的list append的递归定义,可以支持不同方向的查询。
app([], L, L).
app([A|L1], L2, [A|L12]) :- app(L1, L2, L12).
?- app([a], [b], L).
L = [a,b]
?- app(X, [b], [a,b]).
X = [a]
?- app(X, Y, [a,b]).
X = [], Y = [a,b]
X = [a], Y = [b]
X = [a,b], Y = []
重新设计Turbo Prolog GEOBASE的数据库
Trubo Prolog 2.0(Borland公司1988年发行)中包含一个美国地理数据库示例程序,它支持用简单的自然语言(英语)作查询(程序在80x25的文本窗口中运行,仅需要520K的内存)。这里仅讨论数据库的设计与查询。GEOBASE数据库的架构如下:
/* 依据最近下载的版本,源代码有很多更改过的痕迹。稍有改动。*/
state(NAME, ABBREVIATION, CAPITAL, AREA, POPULATION)
city(STATE, ABBREVIATION, NAME, POPULATION)
river(NAME, LENGTH, STATE_LIST)
border(STATE, ABBREVIATION, STATE_LIST)
highlow(STATE, ABBREVIATION, POINT, HEIGHT, POINT, HEIGHT)
mount(STATE, ABBREVIATION, NAME, HEIGHT)
lake(NAME, AREA, STATE_LIST)
road(NUMBER, STATE_LIST)
它支持的英语查询例句如下:
- give me the cities in california.
- what is the biggest city in california?
- what is the longest river in the usa?
- which rivers are longer than 1 thousand miles?
- what is the name of the state with the lowest point?
- which states border alabama?
- which rivers do not run through texas?
- which rivers run through states that border the state
with the capital austin?
尽管自然语言查询很有趣,但在我看来,数据库首先应当支持用简洁清晰的Prolog语句作灵活的(较复杂的)查询。为此,数据库可以按照下述原则重新设计:不再使用长的数据tuple,每个谓词仅表达一个特定的概念,并仅包含与之相关的项。下面的设计用gprolog实现。
实体谓词声明地理数据库中的实体对象,它的参数是对象的ID,在同类实体中唯一。实体对象可以另外指定一个或多个名称,ID是缺省的名称。
实体谓词 示例
-------- ----------------------------------------
state state(ny).
names(state(ny), 'new york').
city city('new york, ny').
names(city('new york, ny'), 'new york').
names(city('new york, ny'), nyc).
river river(delaware).
lake lake(ontario).
查询示例 说明
---------------------- --------------------------
state(X). 列出所有的州。
river(delaware). 确认delaware河存在。
names(X, 'new york'). 列出名为new york的实体。
names(city(X), nyc). 列出名为nyc的城市。
必须说明,在names声明中用到的state()与city()仅代表数据tuple,Prolog并不强制检查所指的state或city已经存在。Prolog也不会检查ID的唯一性。这些功能需要由额外的用户层或者由扩展的Prolog来支持。
属性谓词为实体标出某一性质。同一谓词可用于多种实体。
属性谓词 示例
------------ ------------------------------------------
area area(state(ny), 49100.0).
population population(state(ny), 17558000).
population(city('new york, ny'), 7071639).
查询示例 说明
---------------------------- --------------------------
population(city(X), N),
N > 1000000. 列出人口大于一百万的城市。
关系谓词表示不同实体之间的某种关联。同一关系谓词可用于不同的实体组合。
关系谓词 示例
------------ ------------------------------------------
in in(city('new york, ny'), state(ny)).
in(river(delaware), state(ny)).
in(river(delaware), state(pa)).
in(river(delaware), state(nj)).
in(river(delaware), state(de)).
capital capital(state(ny), city('albany, ny')).
查询示例 说明
---------------------------- --------------------------------
population(city(X), N), 列出人口大于一百万的城市。
N > 1000000.
C = city(_), S = state(_), 列出占州中人口比例大于0.5的城市,
in(C, S), 有washington与honolulu。
population(C, A),
population(S, B),
A / B > 0.5.
仍须指出,以上设计的严格实现有赖更多的支持。例如,应当约束谓词所接受的参数”类型“(如capital只能用在state与city之间),这在Turbo Prolog中可以用domain来声明,但gprolog缺少对domain的支持。又如,应当声明关系各方的数量比例。不过,即使缺少这样的支持,用户代码仍不难自行声明约束,并在用户查询层中确认约束的有效性。例如,
dom(state).
dom(city).
dom(river).
prop(area, float).
prop(population, integer).
rel(capital).
rel(in).
req(area, state).
opt(area, D) :- dom(D).
req(population, state).
req(population, city).
req(capital, state, city, 1:1).
req(in, city, state, 1:1).
req(in, river, state, 1:"+").
上述用户自行声明的约束条件有不少好处。一方面,它便于用简单方式列出约束:既能查出某一实体所属关系,也能查出与某一关系相关的实体。另一方面,它支持聪明的声明方式,如根据规则判断in关系能发生在哪些实体之间。(实际的地理关系远比示例中复杂,如支流与主流的关系,又如流域与流经的区别。)
与SQL数据库的存储方式比较,上述Prolog数据库设计实际上是按列存储。它不是按实体保存数据,而是按谓词保存数据。在具体实现时,实体声明如state(ny)、river(delaware)可被转换为同一谓词的声明:ent(state(ny))、ent(river(delaware)),而Prolog会自动将state(ny)等转换为整数ID,并在存储事实规则时仅使用ID。无论Turbo Prolog还是gprolog都未直接提供真正的数据库功能,而只能将关系数据以文本形式写入文件。
为了方便查询,我扩展了属性谓词与关系谓词,使它们接受按名称查询:
查询示例 说明
---------------------------- ------------------------------------
in(ontario, X). 返回state(ny)与state(ca),
ontario既是湖名也是加州的城市名。
in(cities(X), ny). 用cities而不是city将返回城市名,
而不是城市ID。
in(X, states('new york')). 按名称查询,state('new york')不存在,
但states('new york')存在。
此外,很容易将state、city等声明为算符,这样使用它们时就不必带括号,如 state ny。
评论
为不同领域的知识建立数据库是非常有趣的事。百科全书并不足够,应当有更方便准确的方法查询知识,这甚至包括查询文学、电影中的人物关系、事件情节。例如Google Earth的地图数据中按地点标出了新发生的新闻事件(最近联不上了)。
用自然语言查询也很有趣,而比之更有趣的是解析自然语言文档,将资料”同化“入数据库中。例如,从百科全书网站或地理杂志中获取地理知识,并录入数据库中。指数增长的知识需要更多的人关注、分享。Emacs中有一个小游戏Adventure,让人根据文本的场景描述想像一个空间环境,并下指令移动位置、作收集物品等动作,最后杀死怪物;更有趣的工作则是反过来,让计算机根据场景的文字描述绘制出空间位置关系图。Emacs中的另一个游戏则用自然语言假装心理医生与人交流。(Emacs编辑器有一个显眼的红牛骑在蓝色字样上的标志,牛头很接近汉字的”公“字;而vim编辑器(vi的一个版本)的欢迎页面中则有”帮助乌干达的可怜儿童“广告语。)很显然,无论分享知识还是享受游戏都需要宽松的心态与经济条件。
无论知识的传承还是与之相关的工具的研发,都必须属于民间。一方面,知识没有标准版本、不可能由权威发布考核,而国家必须依赖民间的知识积累;另一方面,知识被垄断并且被随意篡改是不可容忍的。(根据en.wikipedia.org,美国《独立宣言》的第一个签名人叫John Hancock,他的签名明显比他人的大,不知是否属实。)软件工具不能用来侵犯人,包括隐私权。
我们的软件行业(包括学校教育)有明显的问题。程序员或学生缺少好的文档与教材,而被有意置于摸索的状态。这是人的贬值,是不可接受的,编程与学习理当是享受(由他人分享的知识的快乐)。程序员与学生常被有意捉弄,看不到正确的版本或知识而猜测”标准答案“,这更是不可容忍的。考试不能无视人的尊严,而现实中有非常多的有趣的题目。西方的开源软件如Emacs、vim、gprolog都带有大量文档,不过,入乡随俗,这些文档大都可读性很差,亟待补充(学习、改进它们是更好的考试)。软件资料很差并非”一日之寒“,早在Turbo Prolog时代或更早就是这样,似乎这是天然的(控制、捉弄的)权力,而这剥夺了很多(享受知识与生活的)快乐。
补充
两个小建议:Emacs编辑器的复杂按键组合如Ctrl-x Ctrl-f可以简化为x-f(同时按下两键)。英文中似乎仅在Oxford一词里两个字母连续出现。
vi编辑器应能自然地嵌入到其他环境中,如shell、mail命令及prolog、python的解释环境中,并且能编辑特定的命令/函数/方法。也应能将其嵌入IDE或Office环境中,以便享用定制的按键组合。