Bizzarre cond-> behavior

169 views
Skip to first unread message

Milt Reder

unread,
Jul 20, 2017, 6:36:42 PM7/20/17
to Clojure
Hey Clojurians,
I'm experiencing some odd behavior with cond-> and was wondering if anyone had any ideas. I was troubleshooting a very slow load time in one of our projects, and traced it to a web templating library we maintain. One of the functions in this lib, `cell` takes a *very* long time (about a minute) to define in Clojure:

(def valid-align #{:top :middle :bottom})

(def align-mdl-class
  {:top    "mdl-cell--top"
   :middle "mdl-cell--middle"
   :bottom "mdl-cell--bottom"})

(defn cell [& {:keys [align offset order col stretch?
                      desktop tablet phone
                      children
                      id class attr]
               :as   args}]
  (when align (assert (valid-align align)))
  (into
   [:div
    (merge
     {:id    id
      :class (cond-> "mdl-cell"
               class    (str " " class)
               stretch? (str " mdl-cell--stretch")
               align    (str " " (align-mdl-class align))
               offset   (str " mdl-cell--" offset "-offset")
               order    (str " mdl-cell--" order "-order")
               col      (str " mdl-cell--" col "-col")
               desktop  (cond->
                            (:col desktop)    (str " mdl-cell--" (:col desktop) "-col-desktop")
                            (:offset desktop) (str " mdl-cell--" (:offset desktop) "-offset-desktop")
                            (:order desktop)  (str " mdl-cell--" (:order desktop) "-order-desktop")
                            (:hide? desktop)  (str " mdl-cell--hide-desktop"))
               tablet   (cond->
                            (:col tablet)    (str " mdl-cell--" (:col tablet) "-col-tablet")
                            (:offset tablet) (str " mdl-cell--" (:offset tablet) "-offset-tablet")
                            (:order tablet)  (str " mdl-cell--" (:order tablet) "-order-tablet")
                            (:hide? tablet)  (str " mdl-cell--hide-tablet"))
               phone    (cond->
                            (:col phone)    (str " mdl-cell--" (:col phone) "-col-phone")
                            (:offset phone) (str " mdl-cell--" (:offset phone) "-offset-phone")
                            (:order phone)  (str " mdl-cell--" (:order phone) "-order-phone")
                            (:hide? phone)  (str " mdl-cell--hide-phone")))}
     attr)]
   children))

It works fine at runtime, but loading it in Clojure (1.8 or 1.9-alpha17) takes far longer than I'd expect. Try loading the above code in a repl and see for yourself. The templating lib is cljc, and both loading and running it are fine (a couple ms)  in cljs. The time taken seems to increase exponentially with more clauses added to the nested cond->'s near the bottom in clojure.

I've rewritten it without cond-> and it works fine, but I was wondering if anyone could shed light on why this is, and why it isn't an issue in cljs.

Cheers,
Milt

Milt Reder

unread,
Jul 20, 2017, 7:35:00 PM7/20/17
to Clojure
Here's a simplified example:

(defn wat [& {:keys [a b c d e f
                     map-0 map-1 map-2]}]
  (cond-> "foo"
    a (str a)
    b (str b)
    c (str c)
    d (str d)
    e (str e)
    f (str f)
    map-0 (cond->
              (:a map-0) (str (:a map-0))
              (:b map-0) (str (:b map-0))
              (:c map-0) (str (:c map-0))
              (:d map-0) (str (:d map-0)))
    map-1 (cond->
              (:a map-1) (str (:a map-1))
              (:b map-1) (str (:b map-1))
              (:c map-1) (str (:c map-1))
              (:d map-1) (str (:d map-1)))
    map-2 (cond->
              (:a map-2) (str (:a map-2))
              (:b map-2) (str (:b map-2))
              (:c map-2) (str (:c map-2))
              (:d map-2) (str (:d map-2)))))

Peter Hull

unread,
Jul 21, 2017, 4:56:01 AM7/21/17
to Clojure
On Friday, 21 July 2017 00:35:00 UTC+1, Milt Reder wrote:
Here's a simplified example:

I can confirm that pasting that defn into a CIDER repl does take a long time before the prompt comes back. I tried doing a "macro expand all" and it was pretty much instantaneous. The resulting expansion is quite long but not unreasonable (see below). Beyond that, I'm not sure how to analyse what Clojure's doing or time it. I'm using 1.9 alpha by the way.
Pete

(def wat
 
(fn*
   
([& p__9335]
     
(let*
       
[map__9336
        p__9335
        map__9336
       
(if (seq? map__9336)
         
(. clojure.lang.PersistentHashMap create (seq map__9336))
          map__9336
)
        a
       
(get map__9336 :a)
        b
       
(get map__9336 :b)
        c
       
(get map__9336 :c)
        d
       
(get map__9336 :d)
        e
       
(get map__9336 :e)
        f
       
(get map__9336 :f)
        map
-0
       
(get map__9336 :map-0)
        map
-1
       
(get map__9336 :map-1)
        map
-2
       
(get map__9336 :map-2)]
       
(let*
         
[G__9337
         
"foo"
          G__9337
         
(if a (str G__9337 a) G__9337)
          G__9337
         
(if b (str G__9337 b) G__9337)
          G__9337
         
(if c (str G__9337 c) G__9337)
          G__9337
         
(if d (str G__9337 d) G__9337)
          G__9337
         
(if e (str G__9337 e) G__9337)
          G__9337
         
(if f (str G__9337 f) G__9337)
          G__9337
         
(if map-0
           
(let*
             
[G__9338
               G__9337
               G__9338
               
(if (:a map-0) (str G__9338 (:a map-0)) G__9338)
               G__9338
               
(if (:b map-0) (str G__9338 (:b map-0)) G__9338)
               G__9338
               
(if (:c map-0) (str G__9338 (:c map-0)) G__9338)]
             
(if (:d map-0) (str G__9338 (:d map-0)) G__9338))
            G__9337
)
          G__9337
         
(if map-1
           
(let*
             
[G__9339
               G__9337
               G__9339
               
(if (:a map-1) (str G__9339 (:a map-1)) G__9339)
               G__9339
               
(if (:b map-1) (str G__9339 (:b map-1)) G__9339)
               G__9339
               
(if (:c map-1) (str G__9339 (:c map-1)) G__9339)]
             
(if (:d map-1) (str G__9339 (:d map-1)) G__9339))
            G__9337
)]
         
(if map-2
           
(let*
             
[G__9340
              G__9337
              G__9340
             
(if (:a map-2) (str G__9340 (:a map-2)) G__9340)
              G__9340
             
(if (:b map-2) (str G__9340 (:b map-2)) G__9340)
              G__9340
             
(if (:c map-2) (str G__9340 (:c map-2)) G__9340)]
             
(if (:d map-2) (str G__9340 (:d map-2)) G__9340))
           G__9337
))))))


James Reeves

unread,
Jul 21, 2017, 5:08:47 AM7/21/17
to clo...@googlegroups.com
I can also confirm, tested on Clojure 1.8.0. The macro doesn't seem to take long at all; it's the expanded code that takes a significant time to compile.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
James Reeves

Nicola Mometto

unread,
Jul 21, 2017, 6:06:34 AM7/21/17
to clo...@googlegroups.com
I have a patch that fixes this, will open a ticket later today.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
signature.asc

Nicola Mometto

unread,
Jul 21, 2017, 6:32:08 AM7/21/17
to clo...@googlegroups.com
Ticket with patch + explanation: https://dev.clojure.org/jira/browse/CLJ-2210
signature.asc

Peter Hull

unread,
Jul 21, 2017, 7:11:29 AM7/21/17
to Clojure
That was quick! So, if I understand it, the problem is not with cond->, it's the chain of let-bindings it expands to?
Pete
 

Nicola Mometto

unread,
Jul 21, 2017, 7:12:47 AM7/21/17
to clo...@googlegroups.com
correct

On 21 Jul 2017, at 12:11, Peter Hull <peter...@gmail.com> wrote:

That was quick! So, if I understand it, the problem is not with cond->, it's the chain of let-bindings it expands to?
Pete
 

signature.asc

Milt Reder

unread,
Jul 21, 2017, 10:53:41 AM7/21/17
to Clojure
Nicola,
Wow, I certainly didn't expect to wake up to a patch, nice work. I think I get the underlying problem, seems like there might be other cases where this comes up.

Thanks also to others who dared to drop my code into their REPLs to confirm, I am constantly impressed by the Clojure community.

Cheers,
Milt
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
James Reeves
Reply all
Reply to author
Forward
0 new messages