【问题标题】:Recursive macro clojure递归宏clojure
【发布时间】:2019-03-31 13:34:31
【问题描述】:

为了更好地学习宏,我一直在玩一些简单的例子,包括重新创建一个简化的后线程。我很难理解为什么下面的一个版本会导致堆栈溢出而另一个不会。

;; version one - blows up w/ stack overflow
(defmacro ->>> [e1 & exprs]
  `(if ~exprs
       (->>> ~(concat (first exprs) (list e1)) ~@(rest exprs))
       ~e1))

;; version two, works just fine
(defmacro ->>> [e1 & exprs]
  (if exprs
       `(->>> ~(concat (first exprs) (list e1)) ~@(rest exprs))
       e1))

我最初的反应是,这一定是因为在第一个示例中,虽然生成的扩展看起来如果它是普通代码就可以运行得很好,因为它是一个宏,递归调用不断扩展并且如果测试永远不会发生。在第二个版本中,if 测试发生在返回任何列表以进行运行时评估之前,从而有机会突破。

但是,我不确定这种心智模型是否正确,因为以下示例 (Clojure Brave & True) 看起来与上面的第一个版本非常相似,并且工作正常:

(defmacro our-and
  ([] true)
  ([x] x)
  ([x & next]
 `(if ~x (our-and ~@next) ~x)))

编辑:澄清一下,我的意思是上面的our-and 在结构上(不是语义上)相似,因为它返回一个包含对宏的递归调用的列表,类似于我的版本一上面的线程最后一个副本。

【问题讨论】:

    标签: clojure macros


    【解决方案1】:

    你的心智模型是正确的。将宏视为接受代码并返回代码并在编译时运行的函数可能会有所帮助。这应该可以清除第一个示例和our-and 之间的差异。

    在第一个示例中,我们有一个函数,它接受代码并始终返回使用 ->>> 宏的代码,从而导致无限的宏扩展。请记住,引用代码中的if 表达式将在运行时进行评估,但是当发生宏评估时,您会在编译时获得堆栈溢出。

    our-and 中,我们有一个包含三个子句的函数。在首先评估的两个子句中,它返回不包含自身的代码。在第三个子句中,它返回包含自身的代码。这使它类似于示例 2,而不是示例 1。

    【讨论】:

    • 你的解释很清楚,谢谢。我掩饰了多个 arities 有效突破的事实,因为它们都不包含宏本身,从而导致进一步扩展。
    【解决方案2】:

    有时从简单的函数和数据向量开始会更容易。这是一个例子:

    (ns tst.demo.core
      (:use tupelo.core demo.core tupelo.test))
    
    (defn ->>>
      [val & exprs]
      (spyx val)
      (spyx exprs)
      (if (empty? exprs)
        val
        (let [[expr & others] exprs
              >>     (spyx expr)
              >>     (spyx others)
              [fn & args] expr
              >>     (spyx fn)
              >>     (spyx args)
              fncall (concat [fn val] args)
          >> (spyx fncall)
          result (concat ['->>> fncall] others)]
          (spyx result) )))
    

    带输出:

    val => :val
    exprs => ([:form1 1 2 3] [:form2 4 5])
    
    expr => [:form1 1 2 3]
    others => ([:form2 4 5])
    
    fn => :form1
    args => (1 2 3)
    
    fncall => (:form1 :val 1 2 3)
    result => (->>> (:form1 :val 1 2 3) [:form2 4 5])
    
    (->>> :val [:form1 1 2 3] [:form2 4 5]) 
    
         => (->>> (:form1 :val 1 2 3) [:form2 4 5])
    

    因此,您可以看到它将:val 线程化到正确的位置(线程优先样式)并为递归调用设置。更接近宏,我们制作了一个辅助函数 fn:

    (defn my-thread-first-impl
      [val & exprs]
      (spyx val)
      (spyx exprs)
      (if (empty? exprs)
        val
        (let [[expr & others] exprs
              >>     (spyx expr)
              >>     (spyx others)
              [fn & args] expr
              >>     (spyx fn)
              >>     (spyx args)
              fncall (concat [fn val] args)
              >>     (spyx fncall)
              result `(my-thread-first-impl ~fncall ~@others)]
          result)))
    
    ; (defmacro my-> [forms] )
    
    (dotest
      (spyx (my-thread-first-impl :val
              '(fn-1 1 2 3)
              '(fn-2 4 5) ))
    
    val => :val
    exprs => ((fn-1 1 2 3) (fn-2 4 5))
    expr => (fn-1 1 2 3)
    others => ((fn-2 4 5))
    fn => fn-1
    args => (1 2 3)
    fncall => (fn-1 :val 1 2 3)
    
      => (tst.demo.core/my-thread-first-impl (fn-1 :val 1 2 3) (fn-2 4 5))
    

    带有宏和虚拟 fn 的最终版本

    (defn fn-1 [& args]
      (vec (cons :fn-1 args)))
    (defn fn-2 [& args]
      (vec (cons :fn-2 args)))
    
    (defn my-thread-first-impl
      [val & exprs]
      (spyx val)
      (spyx exprs)
      (if (empty? exprs)
        val
        (let [[expr & others] exprs
              >>     (spyx expr)
              >>     (spyx others)
              [fn & args] expr
              >>     (spyx fn)
              >>     (spyx args)
              fncall (concat [fn val] args)
              >>     (spyx fncall)
              result `(my-> ~fncall ~@others)]
          result)))
    
    (defmacro my->
      [& forms]
      (apply my-thread-first-impl forms))
    

    &结果:

    (my-> :val
      (fn-1 1 2 3)
      (fn-2 4 5))
    
     => [:fn-2 [:fn-1 :val 1 2 3] 4 5]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-15
      • 1970-01-01
      相关资源
      最近更新 更多