【问题标题】:Prolog translation of Lisp's tail-recursionLisp 尾递归的 Prolog 翻译
【发布时间】:2017-10-09 19:16:33
【问题描述】:

我有一个问题是上一个主题的后续问题, Should I avoid tail recursion in Prolog and in general?

在上面链接的文章中,用户false 提供了这个代码示例和这个解释......

早在 1970 年代,主要的 AI 语言是 LISP。和 相应的定义应该是...

  (defun addone (xs)
    (cond ((null xs) nil)
      (t (cons (+ 1 (car xs))
           (addone (cdr xs))))))

...不是直接尾递归的:原因是cons: 在那个时候的实现中,它的论点首先被评估, 只有这样,cons 才能被执行。所以像你一样重写这个 指示(并反转结果列表)是可能的 优化技术。

然而,在 Prolog 中,您可以在知道之前创建 cons 实际值,这要归功于 逻辑变量。这么多节目 在 LISP 中不是尾递归的,在 LISP 中被翻译成尾递归程序 序言。

这种影响仍然可以在许多 Prolog 中找到 教科书。

我的问题是:上面的 Prolog 翻译什么是好的 LISP 代码?

编辑: 添加了 lisp 代码示例和 lisp 文档描述了各种 lisp 函数。

插件实例

1 > (addone '(1 2 3))

(2 3 4)

2 > (addone '('()))

> Error: The value 'NIL is not of the expected type NUMBER.
> While executing: CCL::+-2, in process listener(1).
> Type :POP to abort, :R for a list of available restarts.
> Type :? for other options.

3 > (addone '(a b c))

> Error: The value A is not of the expected type NUMBER.
> While executing: CCL::+-2, in process listener(1).
> Type :POP to abort, :R for a list of available restarts.
> Type :? for other options.

3 > ^C

lisp 功能文档

cons object-1 object-2 => 缺点

创造一个新的缺点, 其汽车是 object-1 , 其 cdr 为 object-2 。

例子
  (cons 1 2) =>  (1 . 2)
  (cons 1 nil) =>  (1)
  (cons nil 2) =>  (NIL . 2)
  (cons nil nil) =>  (NIL)
  (cons 1 (cons 2 (cons 3 (cons 4 nil)))) =>  (1 2 3 4)
  (cons 'a 'b) =>  (A . B)
  (cons 'a (cons 'b (cons 'c '()))) =>  (A B C)
  (cons 'a '(b c d)) =>  (A B C D)

(汽车 x) => 对象

如果 x 是一个缺点, 车返回那个缺点的车。 如果 x 为 nil , 汽车返回零。

(cdr x) => 对象

如果 x 是一个缺点, cdr 返回该 cons 的 cdr。 如果 x 为 nil , cdr 返回零 .

cond {clause}* => 结果*

子句::= (测试形式*)

测试表单按照它们的顺序一次评估一个 在参数列表中给出,直到找到一个测试形式 计算结果为真。

如果该子句中没有形式,则 test-form [ed: test-form 的第一个值,如果有,则为 nil are no values] 由 cond 形式返回。否则,表格 与此测试表格相关联的按顺序评估,留给 对,作为隐式预测,以及最后一个返回的值 表单由 cond 表单返回。

一旦一个测试表单产生真值,就不会再有其他测试表单 评估。如果没有测试表单返回 true,则返回 nil

http://www.lispworks.com/documentation/HyperSpec/Body/m_cond.htm#cond 了解更多信息。

defun 函数名 lambda-list 形式* => 函数名

http://www.lispworks.com/documentation/HyperSpec/Body/m_defun.htm#defun 了解更多信息。

t => T

t =>  T 
(eq t 't) =>  T
(case 'b (a 1) (t 2)) =>  2

【问题讨论】:

  • 你知道 Lisp 程序是做什么的吗?
  • 为什么要在问题中记录 Lisp 功能?任何希望回答问题的人都应该知道基本功能或知道在哪里可以找到任何详细信息。你不应该用大量基本的 Lisp 语言文档来混淆你的问题。
  • 我完全不同意。你能不能把提供这样的参考视为一项很好的服务?特别是当问题是关于从外语语法(lisp)到“本地”观众的语法(序言)的翻译时。如果是“请帮我把这个法语翻译这个法语成英语”的问题——提供 5 个最重要单词的定义作为起点是否不合适?
  • 问题确实要求翻译 - 翻译可以解释,但肯定不能(仅)解释“提供一些序言提供此功能,请忽略原始语法、语义、单词选择和方法,随你喜欢”。使用这种方法的答案没有错,但还有其他可能性。
  • “翻译”在解释上可能会有一些差异,但主要的是:功能相同,结构相同或相似,这是我原来的答案提供的.事实上,我故意避免使用 CLP(FD) 方法(这是我通常会选择的方法),因为您正在寻找与 Lisp 代码等效的东西。我确实用 CLP(FD) 更新了答案。

标签: prolog common-lisp tail-recursion code-translation tailrecursion-modulo-cons


【解决方案1】:

这是给定 Lisp 算法在 Prolog 中的再现。请注意,Lisp 是函数式的,Lisp 函数可以返回值。 Prolog 中不是这种情况,因此您需要两个参数。

非关系的直接实现是:

addone([], []).
addone([H|T], [H1|T1]) :-
    H1 is H + 1,
    addone(T, T1).

请注意,第二个谓词子句头部的 [H1|T1] 参数对应于 Lisp 中的 (cons H1 T1)

这也可以使用maplist 来完成,这与最初的 Lisp 实现稍有不同,但 Lisp 确实具有列表映射函数,可用于创建看起来更像这样的 Lisp 实现:

addone_element(X, X1) :- X1 is X + 1.
addone(List, List1) :- maplist(addone_element, List, List1).

在 Prolog 中,这可以使用 CLP(FD) 来提高相关性,这对于对整数进行推理很有用:

:- use_module(library(clpfd)).

addone([], []).
addone([H|T], [H1|T1]) :-
    H1 #= H + 1,
    addone(T, T1).

还有maplist 版本:

addone_element(X, X1) :- X1 #= X + 1.
addone(List, List1) :- maplist(addone_element, List, List1).

【讨论】:

  • 在我看来,这两种方法都没有提供 logic 解决方案,因为不是 isomorphic 。从步骤 a 到步骤 b 再到步骤 c 的过程实现,但不能从步骤 c 到步骤 b 再到步骤 a ,是 imperativ 不是 logical 的。

  •  ?- (addone(V,[2,3,4])) 。 user:addone/2 的第 2 条错误!!实例错误 - X 是 A+B:预期的绑定值 
     ?- (addone_maplist(V,[2,3,4])) 。 user:addone_element/2 的第 1 条错误! INSTANTIATION ERROR - X is A+B: expected bound value 
  • 你在你的问题中说你正在寻找一个 Prolog 谓词来做 Lisp 程序所做的事情。所以我提供的程序只能在一个方向上工作:addone([2,3,4], Result)。 Lisp 程序不会“反向工作”,这是您尝试使用addone(V, [2,3,4]) 的方式。也就是说,给定返回值,Lisp 程序不会为您提供导致该返回值的函数参数。如果正确定义了关系,Prolog 会更独特地这样做。我更新了答案以启用该功能。
  • @Kintalken 是的,你澄清了你的要求,我相应地更新了我的答案。有什么问题吗?
  • @Kintalken 我会重新添加它,但也要保留 CLP(FD) 版本。
【解决方案2】:

直接翻译:

(defun addone (xs)
    (cond ((null xs) nil)
          (t (cons (+ 1 (car xs))
                   (addone (cdr xs))))))

addone( XS, RESULT) :-
   (   XS = [],              % null XS ? then:
       RESULT = []           % 
   ;
       XS = [CAR | CDR],     % else:
       R is 1 + CAR,         % calculate the two
       addone( CDR, S)       %          fields         % almost TR,
       RESULT = [R | S],     % and cons them up        % save for this cons
   ).

但是,变了,

(defun addone (xs)
    (let ((result))
      (cond ((null xs) (setf result nil))
            (t         (setf result (cons (+ 1 (car xs))
                                          (addone (cdr xs))))))
      result))
=
(defun addone (xs)
    (let ((result))
      (cond ((null xs) (setf result nil))
            (t         (setf result (list nil))
                       (setf (car result) (+ 1 (car xs)))
                       (setf (cdr result) (addone (cdr xs)))))
      result))
=
(defun addone (xs &optional (result (list nil)))        ; head sentinel
      (cond ((null xs))
            (t         (setf (cdr  result) (list nil))
                       (setf (cadr result) (+ 1 (car xs)))
                       (addone (cdr xs) (cdr result))))    ; almost TR
      (cdr result))                                    ; returned but not used
=
(defun addone (xs &aux (result (list nil)))
    (labels ((addone (xs result)
              (cond ((null xs))
                    (t (setf (cdr  result) (list nil))
                       (setf (cadr result) (+ 1 (car xs)))
                       (addone (cdr xs) (cdr result))))))   ; fully TR
        (addone xs result))
    (cdr result))

它是完全尾递归的,

addone( XS, RESULT) :-
   (   XS = [], 
       RESULT = []
   ;
       XS = [CAR | CDR],
       RESULT = [R | S],     % cons two empty places, and
       R is 1 + CAR,         %   fill'em 
       addone( CDR, S)       %   up                          % fully TR
   ).

使用了 Boxing / head sentinel,因此我们可以在 Common Lisp 中设置可设置的指针,但在 Prolog 中这不是必需的——Prolog 的逻辑变量是可直接设置的(一次),已命名 指针。

这也是为什么 Prolog 的转换比 Lisp 的要小得多和容易得多的原因。所需要的只是将一行代码提升一两个档次(而且可能是一个档次)。

【讨论】:

  • 一个非常好的答案!非常感谢您 。您梳理出 lisp 示例的方式非常好,因此可以展示如何实现最后调用尾部优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多