请在2024 年 Clojure 州长调查!中分享您的想法。

欢迎!请查看关于页面获取更多关于这个工作方式的信息。

+2
变换器

我之前见过这个错误报告:https://clojure.atlassian.net/browse/CLJ-1569
现在,在尝试用另一种语言实现 Clojure 类型的变换器以进行教学目的时,我意识到这件事件影响了很多东西。所以先谈一下...
使用当前实现initializing lazily通常需要在步骤函数和完成函数中执行额外的检查,以及空输入的情况。所以并不是没有解决方案,但这非常不直观,特别是所有标准变换器仍然有nulary版本,它们永远不会被调用(所以从实现的角度讲,它在过程中跳过了一步,但从逻辑的角度讲,它添加了一个隐式的步骤,即删除一个转换后的初始值,并用与之无关的值替换它)。
在给出更多示例之前,我应该注意的是,在链接的issus中,alt-transduce没有检查初始化过程后初始值是否已经reduced,我认为它应该这样做,因为reduce本身不会这样做。所以我的更新版的alt-reduce是:

(defn alt-transduce
  ([xform f coll]
     (let [rf (xform f)
           result (rf)]
       (rf (if (reduced? result)
             (unreduced result)
             (reduce rf (rf) coll)))))
  ([xform f init coll]
     (let [rf (xform
               (fn
                 ([] init)
                 ([result] (f result))
                 ([result input] (f result input))))
           result (rf)]
       (rf (if (reduced? result)
             (unreduced result)
             (reduce rf (rf) coll))))))

说到受影响的lazy和额外的检查,示例已经在标准库中。take被定义为:

(defn take
...
  ([n]
     (fn [rf]
       (let [nv (volatile! n)]
         (fn
           ([] (rf))
           ([result] (rf result))
           ([result input]
              (let [n @nv
                    nn (vswap! nv dec)
                    result (if (pos? n)
                             (rf result input)
                             result)]
                (if (not (pos? nn))
                  (ensure-reduced result)
                  result)))))))
...

首先,take 0仍然需要消耗一个项,因为它第一次获得了控制权。其次,尽管我不知道为什么它是这样实现的,只有一个值需要跟踪,但显然有两次比较,这显然是因为它需要在接收到额外的输入时处理n = 0。有一个功能性的初始化阶段,它可以这样实现

    (defn alt-take
  ([n]
     (fn [rf]
       (let [nv (volatile! n)]
         (fn
           ([]
              (let [result (rf)]
                (if (not (pos? n))
                  (ensure-reduced result)
                  result)))
           ([result] (rf result))
           ([result input]
              (let [nn (vswap! nv dec)
                    result (rf result input)]
                (if (not (pos? nn))
                  (ensure-reduced result)
                  result))))))))

(近乎原始,如果有必要,可以重新排序,关键是两个比较在应该放的位置上。)
如果这个问题还没有被解决,那一定有强大的理由吧?看起来这个疑问从未得到回答,我想我的问题在这一点上也是相同的:https://clojure.atlassian.net/browse/CLJ-1569?focusedCommentId=18296

感谢您能进一步提供关于为什么做出这种设计选择的信息。

1 答案

0

我不确定你是否在Slack上,但几个月前有一个很好的回答在这篇关于此问题的帖子中:https://clojurians.slack.com/archives/C053AK3F9/p1657570994323509?thread_ts=1657568229.677049&cid=C053AK3F9

总结来说,“你可以使用转换器构建一个归约函数,这就是为什么转换器必须支持init参数”,以下是一个示例。

(def useless-xform
  (fn [rf]
    (fn
      ([] (rf))
      ([result] (rf result))
      ([result input]
       (rf result input)))))

(transduce identity
           (useless-xform +)
           [1 2])
;; 3

这是迄今为止我读到的关于转换器中未使用的init参数谜团的最合理的解释。


编辑了
我不在Slack上,我在那里注册时遇到了错误。
然而,该答案似乎回答了一个截然不同的问题。再次,问题本质上是要问:为什么transduce故意忽略转换arity 0?虽然已经展示了应该这么做的原因,但还没有展示为什么不应该这么做。
我已经展示了一个具有零arity的有用转换器示例(take),并提到了另一个可以创建的示例(向序列中添加项)。构建归约函数是转换器的全部目的,而且随着累加器的意义甚至类型的改变,这个过程需要两个额外的函数(arity)来启动和完成过程。初始化(nullary)和最终化(unary)函数不仅在这里很有用,而且是必需的。但当前的transduce实现未能处理这两个中的一个。我也可以用一个+来展示示例,虽然算术示例可能不太清楚。
你看到这两者之间意义的大差异吗?
(transduce identity (xform +) 10 [1 2])

(transduce xform + 10 [1 2])
提供初始值应该以与“完成”以相同的方式替换arity 0。但目前在转换(transformed)累加器中被替换。这可以通过以下方式证明:
(defn reinit-xform [init]
  (fn [rf]
    (fn
      ([] (rf) init) ; <-- !!!
      ([result] (rf result))
      ([result input]
       (rf result input)))))
(transduce identity ((reinit-xform 100) +) 10 [1 2])
(transduce (reinit-xform 100) + 10 [1 2])
具有正确身份的版本将其初始值替换为10后返回13。而对于在未转换(由transduce看来)的缩减函数外部重初始化的版本,本应返回103,因为初始值应由xform转换,这正是原问题报告或我的帖子中建议的alt-reduce(在此上下文中无差异)所返回的,但不是当前版本的transduce。
by
你问“为什么transduce不调用初始化器?”,我给你举了一个它确实调用的例子,所以我不确定如何回应...
by
标题中的问题可能本身具有歧义,但整个说明是问题的一部分,否则它就不会在那里。如果它太长,请见谅,但之前的尝试似乎并不有效,因此我认为需要更多的例子。
当然,如果转换只是身份函数,transduce就变成了reduce。一个反例足以驳斥某事,但一个有效例子除了在这个特定情况下刚好能工作外,并不能证明任何事情。
...