如何减少Clojure中的可读性?

时间:2017-04-20 00:30:30

标签: clojure readability

reduce调用首先有f个参数。从视觉上讲,这通常是表格中最重要的部分。 e.g。

(reduce
 (fn [[longest current] x]
   (let [tail (last current)
         next-seq (if (or (not tail) (> x tail))
                    (conj current x)
                    [x])
         new-longest (if (> (count next-seq) (count longest))
                       next-seq
                       longest)]
     [new-longest next-seq]))
 [[][]]
 col))

问题是,val参数(在这种情况下为[[][]])和col参数在下面发生,并且它对你的眼睛来说是很长的路要走匹配参数f

如果它按此顺序显示,那对我来说会更具可读性:

(reduceb val col
  (fn [x y]
    ...))

我应该实现这个宏,还是我首先接近这个完全错误?

6 个答案:

答案 0 :(得分:5)

你当然不应该写那个宏,因为它很容易被写成函数。尽管如此,我并不是非常热衷于将其作为一种功能写作。如果你真的想将reduce与最后两个args配对,你可以写:

(-> (fn [x y]
      ...)
    (reduce init coll))

就个人而言,当我需要这样的大型函数时,我发现逗号实际上可以作为一个很好的视觉锚点,并且可以更容易地告诉两个表单在最后一行:

(reduce (fn [x y]
          ...)
        init, coll)

通常情况下,通常不会首先写出如此大的减少量。在这里,您将至少两个步骤组合成一个相当大且困难的步骤,尝试立即找到所有最长的递减子序列。相反,尝试将集合拆分为递减的子序列,然后取最大的一个。

(defn decreasing-subsequences [xs]
  (lazy-seq
    (cond (empty? xs) []
          (not (next xs)) (list xs)
          :else (let [[x & [y :as more]] xs
                      remainder (decreasing-subsequences more)]
                  (if (> y x) 
                    (cons [x] remainder)
                    (cons (cons x (first remainder)) (rest remainder)))))))

然后,您可以将reduce替换为:

(apply max-key count (decreasing-subsequences xs))

现在,懒惰函数并不比你的reduce特别短,但它只做一件事,这意味着它可以更容易理解;此外,它有一个名称(给你一个关于它应该做什么的提示),它可以在你正在寻找基于减少子序列的其他属性的环境中重复使用,而不仅仅是最长。如果使用函数参数替换>中的(> y x),您甚至可以更频繁地重复使用它,从而允许您根据任何谓词拆分为子序列。另外,如上所述它是懒惰的,所以你可以在任何类型的减少都不可能的情况下使用它。

说到易于理解,你可以看到我误解了你的功能在阅读时应该做什么。我将把这个转换为严格增加的子序列作为练习离开,在那里我看起来像是在计算递减的子序列。

答案 1 :(得分:1)

您不必使用@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Uri uri = data.getData(); try { Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri); // imageButton where you want to set image imageButton.setImageBitmap(bitmap); } catch(IOException e) { e.printStackTrace(); } } 或递归来获取降序(或升序)序列。在这里,我们按照从最长到最短的顺序返回所有降序序列:

reduce

(def in [3 2 1 0 -1 2 7 6 7 6 5 4 3 2]) (defn descending-sequences [xs] (->> xs (partition 2 1) (map (juxt (fn [[x y]] (> x y)) identity)) (partition-by first) (filter ffirst) (map #(let [xs' (mapcat second %)] (take-nth 2 (cons (first xs') xs')))) (sort-by (comp - count)))) (descending-sequences in) ;;=> ((7 6 5 4 3 2) (3 2 1 0 -1) (7 6)) 给出了所有可能的比较,(partition 2 1)允许您标记连续减少的运行。此时您已经可以看到答案,其余代码正在删除不再需要的行李

如果您想要提升序列,则只需将partition-by更改为<

>

如果在问题中,您只想要最长的序列,那么将;;=> ((-1 2 7) (6 7)) 作为最后一个宏中的最后一个函数调用。或者将first替换为:

sort-by

为了最大限度地提高可读性,您可以命名操作:

(apply max-key count)

答案 2 :(得分:0)

我感到你的痛苦......他们很难读懂。

我看到了2项可能的改进。最简单的是编写一个类似to the Plumatic Plumbing defnk样式的包装器:

(fnk-reduce { :fn    (fn [state val] ... <new state value>)
              :init  []
              :coll  some-collection } )

因此函数调用具有单个映射arg,其中3个部分中的每一个都标记为&amp;可以在地图文字中以任何顺序出现。

另一种可能性是只提取还原fn并给它起一个名字。这可以是包含reduce的代码表达式的内部或外部:

(let [glommer (fn [state value] (into state value)) ]
   (reduce glommer #{} some-coll))

或可能

(defn glommer [state value] (into state value)) 
(reduce glommer #{} some-coll))

与往常一样,任何提高清晰度的东西都是首选。如果您还没有注意到,我非常喜欢Martin Fowler关于Introduce Explaining Variable重构的想法。 :)

答案 3 :(得分:0)

我会提前道歉,将更长的解决方案发布到您想要更简洁/清晰的地方。

我们正处于clojure transducers的新时代,你的解决方案似乎有点过了最长的&#34;和&#34;当前&#34;转发记录保存。有条不紊的传感器可以做到这一点,而不是传递那个状态。

(def longest-decreasing
   (fn [rf]
     (let [longest (volatile! [])
           current (volatile! [])
           tail (volatile! nil)]
       (fn
         ([] (rf))
         ([result] (transduce identity rf result))
         ([result x] (do (if (or (nil? @tail) (< x @tail))
                           (if (> (count (vswap! current conj (vreset! tail x)))
                                  (count @longest))
                             (vreset! longest @current))
                           (vreset! current [(vreset! tail x)]))
                         @longest)))))))

在你放弃这种方法之前,要意识到它只是给你正确的答案,你可以用它做一些不同的事情:

(def coll [2 1 10 9 8 40])
(transduce  longest-decreasing conj  coll) ;; => [10 9 8]
(transduce  longest-decreasing +     coll) ;; => 27
(reductions (longest-decreasing conj) [] coll) ;; => ([] [2] [2 1] [2 1] [2 1] [10 9 8] [10 9 8])

同样,我知道这可能会显得更长,但是用其他换能器组成这个可能是值得的(不确定我的空气1是否打破了?)

答案 4 :(得分:0)

我相信iterate可以替代reduce。例如,iterate将用于解决此问题的 iteratee 函数:

(defn step-state-hof [op]
  (fn [{:keys [unprocessed current answer]}]
    (let [[x y & more] unprocessed]
      (let [next-current (if (op x y)
                          (conj current y)
                          [y])
            next-answer (if (> (count next-current) (count answer))
                         next-current
                         answer)]
        {:unprocessed (cons y more)
         :current     next-current
         :answer      next-answer}))))

current已建立,直到它变得超过answer,在这种情况下会创建一个新的answer。每当条件op不满足时,我们就会重新开始构建新的current

iterate本身返回一个无限序列,因此当 iteratee 被调用正确的次数时需要停止:

(def in [3 2 1 0 -1 2 7 6 7 6 5 4 3 2])
(->> (iterate (step-state-hof >) {:unprocessed (rest in)
                                  :current     (vec (take 1 in))})
     (drop (- (count in) 2))
     first
     :answer)
;;=> [7 6 5 4 3 2]

通常,只要获得答案,您就会使用drop-whiletake-while进行短路。我们可以这样在这里不需要短路,因为我们事先知道step-state-hof的内部函数需要调用(- (count in) 1)次。这比计数少一个,因为它一次处理两个元素。请注意,first正在强制进行最终通话。

答案 5 :(得分:0)

我想要这个表格的订单:

  1. reduce
  2. valcol
  3. f
  4. 我能够弄清楚这在技术上满足了我的要求:

    > (apply reduce
        (->>
         [0 [1 2 3 4]]
         (cons
          (fn [acc x]
            (+ acc x)))))
    10
    

    但这不是最容易阅读的内容。

    这看起来更简单:

    > (defn reduce< [val col f]
        (reduce f val col))
    nil
    
    > (reduce< 0 [1 2 3 4]
        (fn [acc x]
          (+ acc x)))
    10
    

    <是&#34的简写;参数向左旋转&#34;)。使用reduce<,我可以看到在我的眼睛到达f参数时传递给f的内容,因此我可以专注于阅读f实现(可能会很长)。此外,如果f确实变长,我不再需要在视觉上检查valcol参数的缩进,以确定它们属于reduce符号的方式更远起来。我个人认为这比在调用reduce之前将f绑定到符号更具可读性,特别是因为fn仍然可以接受名称以便清楚。

    这是一个通用的解决方案,但这里的其他答案提供了很多很好的替代方法来解决我给出的具体问题。