【问题标题】:convert to clojure threading operator转换为 clojure 线程运算符
【发布时间】:2020-02-29 13:30:34
【问题描述】:

我在 let 中有以下代码

; clause in a let
lessons-full (into []
                   (map #(dissoc (merge % (first (lesson-detail %)))
                                 :id :series_id :lesson_id :num))
                   lessons)

奇怪的是,我意外地使用了 into 的转换器形式,因为在错误的地方得到了一个括号。就是上面显示的版本。为了比较,没有传感器的版本是:

; clause in a let
lessons-full (into []
                   (map #(dissoc (merge % (first (lesson-detail %)))
                                 :id :series_id :lesson_id :num)
                        lessons))

这也有效。

在此转换中我还有几件事要做,包括将名为:type 的键的值转换为keyword,该键当前是一个字符串。然而,这正在成为高认知负荷。 还不熟悉线程操作符。任何人都可以协助第一步/思考过程吗?

lessons 是来自 jdbc 查询的映射列表。


更新:草稿答案 - 转换为线程最后运算符的思考过程

第 1 步

准备杂耍。 1. 从线程最后一个->> 开始, 2. 将参数lessons 放在前面, 3. 将最终转换into 放在最后。此版本有效,但我们还没有完成:

; clause in a let
lessons-full (->> lessons
                  (map #(dissoc (merge % (first (lesson-detail %)))
                                         :id :series_id :lesson_id :num) ,,,)
                  (into [] ,,,))

请注意,triple commas ,,, 被 clojure 忽略,我们添加它们以帮助可视化最后线程 ->> 宏注入参数的位置。

第 2 步

我们提取了dissoc,但由于我们在对map 的调用中使用了它,所以当我们将其提取出来时,我们需要将它包装在对map 的另一个调用中。

; clause in a let
lessons-full (->> lessons
                  (map #(merge % (first (lesson-detail %))) ,,,)
                  (map #(dissoc % :id :series_id :lesson_id :num) ,,,)
                  (into [] ,,,))

所以这也有效。让它沉入水中。


更新 2

最后,这是实现我最初目标的代码:

; clause in a let
lessons-full (->> lessons
                  (map #(merge % (lesson-detail %)) ,,,)
                  (map #(dissoc % :id :series_id :lesson_id :num) ,,,)
                  (map #(assoc % :type (keyword (:type %))) ,,,)
                  (into [] ,,,))

看来list comps 在线程最后一个宏内部并不容易使用,除非我弄错了。另外,我在这里映射了三遍地图。在我当前的用例中,这可能无关紧要,但这里有什么关于性能或任何其他可能的改进的内容吗?

【问题讨论】:

  • 如果您已经在使用传感器,而不是对线程“满意”,为什么不继续使用传感器和comp 到您的第一个版本?
  • @cfrick 我尝试了comp,但没有成功。如果您想发布一个示例,那就太好了:)
  • 您能举例说明您的输入和期望的输出吗?

标签: clojure


【解决方案1】:

如果您建议的代码的认知负荷是这里的问题,您可以将您在每节课上执行的操作分解为它自己的功能,例如这个:

(defn preprocess-lesson [lesson]
  (dissoc (merge lesson (first (lesson-detail lesson)))
          :id :series_id :lesson_id :num))

这有几个好处:

  • 你可以为preprocess-lesson写一个测试用例
  • 很明显,这种预处理课程的操作只需要 lesson 作为参数,并且不依赖于周围范围的某些值。
  • 课程顺序的转换看起来更清晰

如果你想使用线程操作符,你会写

(->> lessons
     (map preprocess-lesson))

如果你想使用传感器,你可以写

(into []  (map preprocess-lesson) lessons)

假设你想在一节课上做更多的操作,你甚至可以使用preprocess-lesson里面的线程操作符:

(defn preprocess-lesson [lesson common-lesson-data]
  (-> lesson
      (merge (first (lesson-detail lesson)))
      (dissoc :id :series_id :lesson_id :num)
      (assoc :type (get lesson "type"))
      (dissoc "type")
      (merge common-lesson-data)))

然后像这样称呼它

(->> lessons
     (map #(preprocess-lesson % {:teacher "Rudolf"})))

还不熟悉线程操作符。任何人都可以协助 第一步/思考过程

线程宏可通过阐明计算中的数据流来使您的代码更具可读性。而不是写

(filter a? (map b (filter c? X)))

你可以写

(->> X
     (filter c?)
     (map b)
     (filter a?))

这可能更具可读性,并且可能更容易理解意图。但除此之外,它们并没有增加任何额外的表达能力,而不仅仅是像(filter a? (map b (filter c? X))) 这样的嵌套表达式。简而言之,使用线程宏使您的代码更具可读性,而不是为了使用它们。如果在单独的函数中分解出一些代码使您的代码更具可读性,那么就这样做。

【讨论】:

    【解决方案2】:

    我会这样写,使用传感器

    理论上,无论数据结构如何,您都可以更灵活地定义和使用计算

    (let [xf (comp (map #(merge % (first (lesson-detail %))))
                   (map #(dissoc % :id :series_id :lession_id :num))
                   (map #(update % :type keyword)))]
      ;; into array, eager
      (into [] xf lessons)
      ;; lazy sequence
      (sequence xf lessons))
    

    【讨论】:

      猜你喜欢
      • 2023-03-31
      • 2019-03-24
      • 1970-01-01
      • 2011-09-02
      • 2018-12-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多