深入解析norvig/paip-lisp中的简单Lisp程序实现

深入解析norvig/paip-lisp中的简单Lisp程序实现

paip-lisp Lisp code for the textbook "Paradigms of Artificial Intelligence Programming" paip-lisp 项目地址: https://gitcode.com/gh_mirrors/pa/paip-lisp

前言:学习编程语言的正确方式

在《Paradigms of Artificial Intelligence Programming》这本经典著作中,Peter Norvig通过第二章向我们展示了一个简单但完整的Lisp程序实现。这个程序能够根据语法规则生成随机的英语句子。本章的核心思想是:学习编程语言最好的方式不是死记硬背语法规则,而是通过实际构建程序来掌握语言

语法规则的基础概念

上下文无关文法

程序的核心是一个上下文无关短语结构文法(context-free phrase-structure grammar),这种文法采用生成语法(generative syntax)范式。基本规则如下:

句子 => 名词短语 + 动词短语
名词短语 => 冠词 + 名词
动词短语 => 动词 + 名词短语
冠词 => the, a, ...
名词 => man, ball, woman, table...
动词 => hit, took, saw, liked...

这种文法之所以称为"上下文无关",是因为规则的应用不依赖于周围的单词;而"生成"则意味着这些规则共同定义了语言中所有可能的句子集合。

句子生成示例

让我们看一个句子生成的分解过程:

  1. 生成句子需要先生成名词短语和动词短语
  2. 生成名词短语需要生成冠词和名词
    • 选择"the"作为冠词
    • 选择"man"作为名词
    • 结果名词短语是"the man"
  3. 生成动词短语需要生成动词和名词短语
    • 选择"hit"作为动词
    • 生成名词短语需要生成冠词和名词
      • 选择"the"作为冠词
      • 选择"ball"作为名词
      • 结果名词短语是"the ball"
    • 结果动词短语是"hit the ball"
  4. 最终句子是"The man hit the ball"

Lisp实现方案

直接映射方案

最直接的实现方式是将每个语法规则映射为一个独立的Lisp函数:

(defun sentence ()    (append (noun-phrase) (verb-phrase)))
(defun noun-phrase () (append (Article) (Noun)))
(defun verb-phrase () (append (Verb) (noun-phrase)))
(defun Article ()     (one-of '(the a)))
(defun Noun ()        (one-of '(man ball woman table)))
(defun Verb ()        (one-of '(hit took saw liked)))

这些函数都使用append来组合结果,并依赖one-of函数从选项列表中随机选择:

(defun one-of (set)
  "从集合中随机选择一个元素并返回单元素列表"
  (list (random-elt set)))

(defun random-elt (choices)
  "从列表中随机选择一个元素"
  (elt choices (random (length choices))))

程序运行示例

运行这个程序可以生成各种随机句子:

> (sentence) => (THE WOMAN HIT THE BALL)
> (sentence) => (THE BALL SAW THE TABLE)
> (noun-phrase) => (THE MAN)
> (verb-phrase) => (LIKED THE WOMAN)

扩展语法复杂性

当我们需要扩展语法规则,比如允许名词短语包含不定数量的形容词和介词短语时,直接映射方案会变得复杂:

(defun Adj* ()
  (if (= (random 2) 0)
      nil
      (append (Adj) (Adj*))))

(defun PP* ()
  (if (random-elt '(t nil))
      (append (PP) (PP*))
      nil))

这种实现虽然可行,但代码已经变得难以阅读和维护,特别是对于不熟悉Lisp的人来说。

基于规则的解决方案

更优雅的实现方式

更优雅的解决方案是将语法规则表示为数据,然后编写一个通用的生成器:

(defparameter *simple-grammar*
  '((sentence -> (noun-phrase verb-phrase))
    (noun-phrase -> (Article Noun))
    (verb-phrase -> (Verb noun-phrase))
    (Article -> the a)
    (Noun -> man ball woman table)
    (Verb -> hit took saw liked)))

这种表示方式几乎与原始语法规则一一对应,保持了高度的可读性。

核心生成函数

generate函数是这个方案的核心,它处理三种情况:

  1. 输入是列表时,递归生成每个元素并拼接结果
  2. 输入是符号且有重写规则时,随机选择一个规则继续生成
  3. 输入是符号但没有重写规则时,直接返回该符号(作为列表)
(defun generate (phrase)
  "生成随机句子或短语"
  (cond ((listp phrase)
         (mappend #'generate phrase))
        ((rewrites phrase)
         (generate (random-elt (rewrites phrase))))
        (t (list phrase))))

辅助函数

为了操作这些规则,我们定义了几个辅助函数:

(defun rule-lhs (rule) (first rule))  ; 获取规则左侧
(defun rule-rhs (rule) (rest (rest rule)))  ; 获取规则右侧
(defun rewrites (category) (rule-rhs (assoc category *grammar*)))  ; 获取类别的所有重写选项

语法扩展的便捷性

基于规则的解决方案最大的优势是扩展语法时不需要修改生成函数。例如,我们可以轻松定义更复杂的语法:

(defparameter *bigger-grammar*
  '((sentence -> (noun-phrase verb-phrase))
    (noun-phrase -> (Article Adj* Noun PP*) (Name) (Pronoun))
    (verb-phrase -> (Verb noun-phrase PP*))
    (PP* -> () (PP PP*))
    (Adj* -> () (Adj Adj*))
    (PP -> (Prep noun-phrase))
    (Prep -> to in by with on)
    (Adj -> big little blue green adiabatic)
    (Article -> the a)
    (Name -> Pat Kim Lee Terry Robin)
    (Noun -> man ball woman table)
    (Verb -> hit took saw liked)
    (Pronoun -> he she it these those that)))

设置新语法后,原来的generate函数可以直接使用:

> (generate 'sentence)
(A TABLE ON A TABLE IN THE BLUE ADIABATIC MAN SAW ROBIN WITH A LITTLE WOMAN)

两种实现路径的比较

  1. 直接映射方案

    • 优点:实现简单直接
    • 缺点:语法规则与代码耦合,扩展性差
  2. 基于规则的方案

    • 优点:语法与处理逻辑分离,扩展性强
    • 缺点:需要额外编写规则解释器

对于AI编程这类复杂领域,第二种方案通常是更好的选择,因为它允许我们在问题本身的术语中工作,最小化直接使用Lisp实现的部分。

编程技巧与最佳实践

  1. 使用let引入局部变量:避免使用未声明的全局变量
  2. 数据驱动编程:将规则表示为数据,使程序更灵活
  3. 抽象层:定义操作规则的工具函数,提高代码可读性
  4. 参数与变量:使用defparameter定义常量,defvar定义变量

结语

通过这个简单的句子生成程序,我们不仅学习了Lisp的基本编程技巧,更重要的是理解了如何设计灵活、可扩展的程序架构。这种基于规则的、数据驱动的编程风格是AI编程中的核心范式,也是Lisp语言强大表达能力的体现。

在后续章节中,我们将继续探索更复杂的AI编程技术,但这个简单的例子已经展示了Lisp在处理符号计算和规则系统方面的独特优势。

paip-lisp Lisp code for the textbook "Paradigms of Artificial Intelligence Programming" paip-lisp 项目地址: https://gitcode.com/gh_mirrors/pa/paip-lisp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程季令

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值