Clojure 中的嵌套映射语句无法正确评估,似乎与 LazySeq 有关

Nested map statements in Clojure don't evaluate properly, seems related to LazySeq

我在 Clojure 中编写了一个函数,它应该采用逻辑表达式和 return 等效表达式,其中所有 not 语句直接作用于变量,如下所示:

(not (and p q r))

变成

(or (not p) (not q) (not r))

它使用德摩根定律将 not 向内推,如果 not 直接作用于另一个 not 语句,它们将抵消。代码如下所示:

(defn transform [expr]
    (if
        (list? expr)
        (if
            (=
                'not
                (first expr)
            )
            (if
                (list? (nth expr 1))
                (if
                    (=
                        'not
                        (first (nth expr 1))
                    )
                    (transform (first (rest (first (rest expr)))))
                    (if
                        (=
                            'and
                            (first (nth expr 1))
                        )
                        (cons 
                            'or 
                            (map
                                transform
                                (map
                                    not-ify
                                    (rest (first (rest expr)))
                                )
                            )
                        )
                        (if
                            (=
                                'or
                                (first (nth expr 1))
                            )
                            (cons
                                'and 
                                (map
                                    transform
                                    (map
                                        not-ify
                                        (rest (first (rest expr)))
                                    )
                                )
                            )
                            expr
                        )
                    )
                )
                expr
            )
            expr
        )
        expr
    )
)

问题出在这部分:

(map
    transform
    (map
        not-ify
        (rest (first (rest expr)))
    )
)

第一个 map 语句使用函数 not-ify(请原谅双关语)基本上在每个语句之前放置一个 not。那部分有效。但是,输出不适用于 map transform,尽管 map transform 部分本身有效。让我告诉你:

如果我在 REPL 中写入以下内容:

(def expr '(not (and q (not (or p (and q (not r)))))))


(map
    not-ify
    (rest (first (rest expr)))
)

我得到输出((not q) (not (not (or p (and q (not r))))))

如果我然后获取该输出和 运行 (map transform '((not q) (not (not (or p (and q (not r))))))),我将得到输出 ((not q) (or p (and q (not r))))。到目前为止一切顺利。

然而,如果我 运行 一次全部完成,就像这样:

(map
    transform
    (map
        not-ify
        (rest (first (rest expr)))
    )
)

我得到的是这个输出:((not q) (not (not (or p (and q (not r)))))).

如果运行

(def test1
    (map
        not-ify
        (rest (first (rest expr)))
    )
)
(map transform test1)

我也得到((not q) (not (not (or p (and q (not r)))))).

但是如果我运行

(def test2 '((not q) (not (not (or p (and q (not r)))))))
(map transform test2)

我再次得到正确的结果:((not q) (or p (and q (not r))))

我的猜测是,这与具有类型 LazySeqmap not-ify 输出 (test1) 有某种关系,而如果我手动输入 (test2 ) 它变成了 PersistentList。我已经尝试在 test1 上 运行ning (into (list)) 将其转换为 PersistentList,以及 doRundoAll,但没有结果.我能以某种方式停止我的 map not-ify 语句从 returning a LazySeq?

简短的回答是使用 seq? 而不是 list?

以下是我的实现方式:

(defn push-not-down [expr]
  (if (and (seq? expr) (seq? (second expr)))
    (let [[outer-op & [[inner-op & inner-args] :as outer-args] :as expr] expr]
      (if (= 'not outer-op)
        (condp = inner-op
          'and (cons 'or (map #(push-not-down (list 'not %)) inner-args))
          'or (cons 'and (map #(push-not-down (list 'not %)) inner-args))
          'not (first inner-args)
          expr)
        (if (#{'or 'and} outer-op)
          (cons outer-op (map push-not-down outer-args))
          expr)))
    expr))

(deftest push-not-down-test
  (testing "Not or and not and are transformed to and not and or not"
    (is (= '(or (not :a) (not :b))
           (push-not-down
             '(not (and :a :b)))))
    (is (= '(and (not :a) (not :b))
           (push-not-down
             '(not (or :a :b))))))
  (testing "Double nots cancel"
    (is (= :a
           (push-not-down
             '(not (not :a))))))
  (testing "The rules work together in complex combinations"
    (is (= '(and :a (and :b (not :c)))
           (push-not-down
             '(and (not (not :a)) (not (or (not :b) :c))))))
    (is (= '(or (or (and (not :a))))
           (push-not-down
             '(or (or (and (not :a))))))))
  (testing "Nested expressions that don't fit the rules are preserved"
    (is (= '(not (inc 1))
           (push-not-down
             '(not (inc 1)))))
    (is (= '(inc (or 2 1))
           (push-not-down
             '(inc (or 2 1)))))))

对于形式和表达式,列表和序列之间没有显着差异。或者,如果你确实想保持简洁,你只需要更彻底地将序列转换为列表:)

物有所值,...

首先,让我们定义逻辑 inverse 变成什么:

(def opposite {'and 'or, 'or 'and})

(defn inverse [expr]
  (let [default (list 'not expr)]
    (if (seq? expr)
      (let [[op & args] expr]
        (if (= op 'not)
          (first args)
          (cons (opposite op) (map inverse args))))
      default)))

让我们测试一下:

(map inverse ['p '(not p) '(and a b) '(and (not a) b)])
;((not p) p (or (not a) (not b)) (or a (not b)))

它短路双重否定以及做 DeMorgan 事情。

现在我们可以表达转换:

(defn transform [expr]
  (if (seq? expr)
    (let [[op & args] expr]
      (if (= op 'not)
        (inverse (first args))
        (cons op (map transform args))))
    expr))

例如,

(transform '(not (and p q r)))
;(or (not p) (not q) (not r))
  • 在找到 not 的地方,它 returns 参数的 inverse
  • 否则,它重构表达式,变换 尽可能使用子表达式,就像 inverse 一样。

如果我们不急于转换子表达式,我们可以简化为

(defn transform [expr]
  (or (and (seq? expr)
           (let [[op & args] expr]
             (if (= op 'not) (inverse (first args)))))
      expr))

  • 我发现拉出 inverse 操作更容易理解。
  • 在简单的 transform 中,我玩过 andor 以及 单臂 if 以避免重复无所事事的情况 下OP的文字。

这与 有什么关系?

  • 我无耻地窃取了他关于使用 seq? 而不是 list?.
  • 他对无法识别的操作抛出异常。我的只是让 他们 nil.
  • 我已将 andor 分解为一个案例。