保存用户对询问的回答结果,作为进一步推理的条件
还是从GOAL段开始。起始句是write_startform()
write_startform():- write("<form action=\"\cgi-bin\geni.exe\"method=\"post\">\n").
注意!上一句应为write("<form action=\"geni.exe\"method=\"post\">\n").
GENI虚拟主机没有“cgi-bin”这个子目录。
在GOAL中,紧跟上一句的是assert_conditions(ParmList1)
assert_conditions([]):-!. assert_conditions([parm(Name,Val)|Rest]):- concat("cond_",CondNumberStr,Name), str_int(CondNumberStr,COND), !, assert_cond(COND,Val), assert_conditions(Rest). assert_conditions([_Parm|Rest]):-!, assert_conditions(Rest). assert_conditions(_):- errorexit.
Prolog没有C语言的for,while等处理循环的机制。
Prolog是用递归来实现“循环”。
谓词assert_conditions的4个子句,表现了典型的、标准的递归应用。
子句1,设置递归终止条件。
终止条件是要处理的列表为空。
子句2,处理列表头,即列表第1个成员。
子句3,忽略要处理的列表头,继续处理剩余表尾。
子句4,准备出错后的处理。
子句4执行的前提条件是:
1、子句1头部匹配不成功,或者,“截断”! 之前的语句失败;
2、并且,子句2头部匹配不成功。
具体是哪个语句失败,以及失败的原因,待下面分析。
子句2和子句3,末尾的语句都是自身的递归调用。
这就是尾递归。
尾递归的好处,是可以无限递归下去,不会耗尽内存。
看看子句1中的语句吧。
concat("cond_",CondNumberStr,Name)
它是内建谓词,流模式是(i, i, o), (o, i, i), (i, o, i), (i, i, i)
用于连接2个字符串
本例的流模式是 (i, o, i),从变量名上看,CondNumberStr应该是数字
所以,若Name="cond_3" ,则CondNumberStr=3
这时,可以试试上一节的小伎俩:
在VDE中,菜单File|New,出现一空白文件noname.pro,写入:
goal concat("cond_",A,"cond_1").
菜单Project|Test Goal,目标编译执行,结果:
A=1 1 Solution
再看下一条语句
str_int(CondNumberStr,COND)
这是个内建谓词:str_int (STRING StringArg, INTEGER IntArg) 流模式是(i, o), (o, i), (i, i) 用于字符串与整数的相互转换 建议用上述小伎俩试一试
这一句之后,便是截断"!",由此可见,引起子句2回溯到子句3的条件是:
1、变量Name不是以“Cond_”开头的字符串,或者, 2、变量CondNumberStr不是数字字符串。
这一回溯引起子句3的执行,结果是忽略当前处理对象,开始新的递归,
处理列表中的后序对象。
继续往下看:
assert_cond(COND,Val)
assert_cond(CNO,"yes"):-!,assert(yes(CNO)). assert_cond(CNO,"no"):-!,assert(no(CNO)). assert_cond(_CNO,"why"):-!,assert(whymeet). assert_cond(_,_):-errorexit.
CNO是知识库里“条件”的标识号
yes(CNO),no(CNO),保存在事实库tmp里,
意思是:对第CNO号条件,回答了yes/no
whymeet,也是保存在事实库tmp里,
意思是:用户要求系统答复,为什么询问这一问题。
最后一句,assert_conditions(Rest).
递归处理列表的尾巴Rest。
今天的重点是尾递归,这也是Prolog的一大特色。