如何将函数式中的函数翻译成逻辑式中的关系

先看看例子:

在函数式编程中,我们可以用如下的方式定义「自然数」、「自然数加法」、「自然数乘法」:

(define (build-nat n)
  (cond
    ((= n 0) 'O)
    (else (cons 'S (build-nat (- n 1))))))

(define (succ-nat a)
  (cons 'S a))

(define (+-nat a b)
  (cond
    ((= a 0) b)
    (else (succ-nat (+-nat (- a 1) b)))))

(define (*-nat a b)
  (cond 
    ((= a 0) 0)
    (else
     (+-nat b (*-nat (- a 1) b)))))

模仿函数式编程编程的写法,我们可以在逻辑式编程中定义如下的关系:

;; using miniKanren

(defrel (succo a-1 a)
  (conso 'S a-1 a))

(defrel (zero?o n)
  (== n 'O))

(defrel (pos?o n)
  (fresh (n-1)
    (succo n-1 n)))

(defrel (+o a b c)
  (conde
   ((zero?o a) (== b c))
   ((pos?o a)
    (fresh (a-1 prev)
      (succo a-1 a)
      (succo prev c)
      (+o a-1 b prev)))))

(defrel (*o a b c)
  (conde
   ((zero?o a) (zero?o c))
   ((pos?o a)
    (fresh (a-1 prev)
      (succo a-1 a)
      (+o b prev c)
      (*o a-1 b prev)))))

得益于逻辑式编程的多方向性,我们不仅能用 +o *o 计算两数之和、两数之积,还能用它们来分解两数之和、两数之积。

但当我们使用上面定义的 *o 来分解两数之积时,程序会被卡住,这里以 (run * (x y) (*o x y 1)) 为例:

(*o x y 1) 

;; 因为 c != 0,进入 conde 的第二个分支
(pos?o x)
(succo a-1 x)
(+o b prev 1)
(*o a-1 b prev)

;; (+o b prev 1) 的一组合法分解为 (b = 0, prev = 1)
;; 于是我们转而查询
(*o a-1 0 1)

;; 又因为 c != 0,进入 conde 的第二个分支
(pos?o a-1)
(succo a-1-1 a-1)
(+o b prev 1)
(*o a-1-1 b prev)

;; 同样将 1 分解为 (b = 0, prev = 1),我们又得到了
(*o a-1-1 0 1)  ;; 重复,导致程序卡死

会出现这样的原因是,我们在函数式编程中定义的递归边界只适用于单向的计算,而在逻辑式编程中,我们可以沿着任意方向计算,因此我们需要在逻辑式编程中定义更多递归边界。

(defrel (*o a b c)
  (conde
   ((zero?o a) (zero?o c))
   ((pos?o a) (zero?o b) (zero?o c)) ;; 增加的递归边界
   ((pos?o a) (pos?o b)
    (fresh (a-1 prev)
      (succo a-1 a)
      (+o b prev c)
      (*o a-1 b prev)))))

完整的代码见这里


如何将函数式中的函数翻译成逻辑式中的关系
https://littlejianch.github.io/translate-a-function-to-a-relation-in-logic-programming/
Author
LittleJian
Posted on
September 27, 2022
Licensed under