Lisa/Logical Introduction

Logical Programming in Lisa

Feb 12, 2020

见习魔法师

Logical Programming

Lisa现在支持逻辑式编程了,具体是通过一个内置的可插拔DSL来实现的。也就是说,Lisa的逻辑式编程模块是可插拔的,可以和宿主语言交互。

原子

为了实现逻辑式编程语言中的原子的概念,本次更新中,Lisa引入了原子。原子以冒号:开头,紧接着一个合法的标识符,如果原子中有空白字符,可以接一个字符串,例如: :a, :"atom with spaces", 或者 :"b":a:"a" 被视为相同的原子。

Logical Module

若想要使用逻辑模块,你需要先引入这个模块,使用 (import-env! logical) 来引入该模块。 正如定义变量需要环境模型一样,逻辑的事实会被定义在LogicalContext里面,因此,你可以处理多个不同的上下文。而且上下文可以被储存在Lisa变量中,一旦该上下文被储存,该上下文就是不可变的,因此,可以保证引用透明性。 你可以用(current-context)来获得当前上下文, (pop-context!)来移除当前上下文, (logical/push-context context)来设定context为当前上下文。 你可以使用 (logical/new-context)来创建一个上下文并将其设为当前的上下文。

当一个上下文被设定为当前上下文的时候,你就可以使用上下文中定义的宏,例如fact, define-rule 以及 query

当你想要了解一个语言的时候,你需要着重了解这个语言的基本要素,抽象方法以及组合方法1。从这个方面上讲,Lisa的logical module作为一个DSL,其基本要素都和Lisa有一些差别。

Facts

除了Lisa支持的基本要素外,事实是Lisa/Logical的基本表达式。 事实可以使用 fact 宏来定义。比如:

(fact (path a b))
(fact (path b c))
(fact (path d e))
(fact (path e f))
如果你对prolog有所了解,你就会发现prolog区分变量和原子的方法是大小写和下划线。这点和Erlang相似,然而我个人不是十分喜欢这一点,因此Lisa是和Elixir类似的,用原子类型来代表原子,用符号(Symbols)来代替变量。事实上,你可以以任何非符号基本变量,比如字符串和数字来代表事实。

注意到当我们定义事实当时候,我们往往不怎么用到变量,因此在fact宏中,符号会被自动转换为同名的原子。如果你硬是要用符号,你可以使用'sym,即一个符号的引用(quoted symbol)来指代。以上规则只在定义事实的时候有效。 下划线 _ 匹配任何表达式,并且忽略该值。

(fact (path a b)) ; <=> 
(fact (path :a :b))

Rules

规则是逻辑式编程的用来抽象的方法。 规则可以用 define-rule 宏来定义。其语法和定义一个函数很像。

(define-rule (link a c)
    (or (path a c)
        (and (path a b)
            (link b c))))

规则中的符号被视为变量,原子被视为原子。 规则的规则体是一个查询。特别地,当一个规则的规则体是空的时候,该规则默认匹配到了就永远为真。

Queries

查询是逻辑式编程的组合方法。 一个查询可以是事实和规则的组合,组合方式是使用一些特殊的组合子。 使用query宏来执行一段查询。

(query (link from to)) ; => ({'from :a 'to :b} {'from :b 'to :c} {'from :d 'to :e} {'from :e 'to :f} {'from :a 'to :c} {'from :d 'to :f})
(query (link :a to)) ; => ({'to :b} {'to :c})

查询的结果是一个记录的列表,如果该查询匹配不到任何规则,则生成一个空列表。 如果你仅仅想要知道一个事实只不是存在,你可以使用is-true?宏。

注意,他们都是宏,意味着后面的表达式将不进行任何转换直接被转入。

Combinator

一些特殊的组合子提供了强大的抽象能力。

  • and 接受任意查询,如果所有的查询都为真,则结果查询为真。
  • or 接受任意查询,如果有其中一个查询为真,则结果查询为真。
  • but 接受一个查询,如果该查询为真,则结果查询为假。
  • ? 接受一个Lisa表达式,该表达式必须返回真或者假,如果为真,则总查询为真。
  • execute-lisa 接受一个Lisa表达式,执行该表达式,结果查询是一个永真查询。
  • = 可以对传入的两个表达式做归一化。
  • <- 接受一个变量和一个表达式,对表达式求值之后对变量和表达式做归一化。
(fact (salary a 114))
(fact (salary b 514))

(query (and (salary x y) (? (> y 200)))) ; => ({'x :b 'y 514})
(query (and (salary x y) (execute-lisa (println! y)))) ; Prints 114 514 and the result should be ({'x :a 'y 114} {'x :b 'y 514})

(query (= (x x x) ((1 b c) (a 2 c) (a b 3)))); => ({'a 1 'b 2 'c 3 'x (1 2 3)})

(define-rule (minus-one x y)
    (<- y (- x 1)))
(is-true? (minus-one 3 2)) ; => true

逻辑模块提供了一个子语言,能和Lisa交互。但是目前看啦逻辑式编程仍然无法找到合适的用处,甚至教科书上能见到函数式编程,却很难见到逻辑式编程的身影。

本篇文章只是一个介绍,具体实现可以看下一篇文章。 r{{ changeExtraDisplaySettings({blurMainImage: true}) }}


  1. Structure and Interpretation of Computer Programs Chapter 1 The Elements of Programming