Any alternatives for these two ugly patterns?

148 views
Skip to first unread message

Steven Degutis

unread,
May 25, 2013, 7:24:29 AM5/25/13
to clo...@googlegroups.com
There are two patterns I find in my code that I'm still unhappy with but I don't know how to clean up.

The first is: (if (:attr obj) obj (assoc obj :attr something))

I'm basically saying, give this hash-map an attribute if it doesn't already have it. And just return the thing with an attribute, regardless if I had to add it or not.

This version is ugly because it repeats obj three times. I could write my own macro to de-duplicate it, but I avoid doing that when I can because there's usually a better built-in solution that I just don't know about yet.

The second is like it: (if (some-test obj) obj (some-transformation obj))

In this one, I just want to return the object, but maybe transform it first. But the reference to obj happens three times! Still feels like it could be cleaned up.

Any thoughts on how to clean these up?

Jim - FooBar();

unread,
May 25, 2013, 7:37:34 AM5/25/13
to clo...@googlegroups.com
On 25/05/13 12:24, Steven Degutis wrote:
> The first is: (if (:attr obj) obj (assoc obj :attr something))
>
> I'm basically saying, give this hash-map an attribute if it doesn't
> already have it. And just return the thing with an attribute,
> regardless if I had to add it or not.

(if (contains? obj :attr) obj
(assoc obj :attr something))

it's more evident now, but you still mention obj 3 times...

your second example seems just fine to me...if you have to test for
something, well, you have to test for something!!!

Jim

atkaaz

unread,
May 25, 2013, 7:44:25 AM5/25/13
to clo...@googlegroups.com
may I see the macro for the latter, if you decide to go that way ? thx


--
--
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+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/groups/opt_out.
 
 

Jim - FooBar();

unread,
May 25, 2013, 7:51:27 AM5/25/13
to clo...@googlegroups.com
no need for macros... :)

(definline safe-assoc [m k v]
`(if (contains? ~m ~k) ~m
  (assoc ~m ~k ~v)))
 
(definline pred-transform [obj pred tf]
`(if ~(pred obj) ~obj
    ~(tf obj)))

Jim

atkaaz

unread,
May 25, 2013, 7:55:01 AM5/25/13
to clo...@googlegroups.com
didn't know about definline, thanks Jim!

atkaaz

unread,
May 25, 2013, 7:55:51 AM5/25/13
to clo...@googlegroups.com
just wondering if obj is a form does it get evaluated twice?


On Sat, May 25, 2013 at 2:51 PM, Jim - FooBar(); <jimpi...@gmail.com> wrote:

atkaaz

unread,
May 25, 2013, 8:04:07 AM5/25/13
to clo...@googlegroups.com
Shouldn't it be like:

(definline pred-transform [obj pred tf]
   `(if (~pred ~obj) ~obj
       (~tf ~obj)))
=> (pred-transform 1 #(not (nil? %)) println)
1
=> (pred-transform 1 nil? println)
1
nil


atkaaz

unread,
May 25, 2013, 8:07:03 AM5/25/13
to clo...@googlegroups.com
in which case it does get evaluated twice if form:
=> (pred-transform (println 1) #(not (nil? %)) #(println % "."))
1
1
nil .
nil

=> (pred-transform (println 1) nil? #(println % "."))
1
1
nil

so maybe a let + gensym would be in order?

atkaaz

unread,
May 25, 2013, 8:08:51 AM5/25/13
to clo...@googlegroups.com
like:
=> (definline pred-transform [obj pred tf]
   `(let [o# ~obj]
      (if (~pred o#) o#
       (~tf o#))))
#'cgws.notcore/pred-transform

=> (pred-transform (println 1) nil? #(println % "."))
1
nil
=> (pred-transform (println 1) #(not (nil? %)) #(println % "."))
1
nil .
nil

atkaaz

unread,
May 25, 2013, 8:11:15 AM5/25/13
to clo...@googlegroups.com
I wonder why the definline didn't act like a function?
=> (defn pred-transform [obj pred tf]
     (if (pred obj) obj
       (tf obj)))
#'cgws.notcore/pred-transform

=> (pred-transform (println 1) #(not (nil? %)) #(println % "."))
1
nil .
nil
=> (pred-transform (println 1) nil? #(println % "."))
1
nil

=> (fn? pred-transform)
true

=> (definline pred-transform [obj pred tf]
   `(let [o# ~obj]
      (if (~pred o#) o#
       (~tf o#))))
#'cgws.notcore/pred-transform
=> (fn? pred-transform)
true

Cedric Greevey

unread,
May 25, 2013, 8:13:22 AM5/25/13
to clo...@googlegroups.com
Seems to me that (merge {:attr something} obj) answers the OP's question, mentions obj only once, and is short and pithy. OTOH it computes the "something" every time, whether it's needed or not, so in cases where "something" is expensive to compute (or has side effects that should only happen if it winds up in the output!) then another method needs to be used.

Steven Degutis

unread,
May 25, 2013, 8:16:07 AM5/25/13
to clo...@googlegroups.com
Thanks, that solution's pretty great, although you're right about the side-effects problem, so I can't always use this solution.

Also I just remembered, sometimes to solve the second one, I would do ((if condition transformer identity) obj) but that feels ugly.


You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/1GFesvqspwk/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

atkaaz

unread,
May 25, 2013, 8:19:20 AM5/25/13
to clo...@googlegroups.com
On Sat, May 25, 2013 at 3:16 PM, Steven Degutis <sbde...@gmail.com> wrote:

Also I just remembered, sometimes to solve the second one, I would do ((if condition transformer identity) obj) but that feels ugly.
but the condition has to contain obj, so obj is referred twice ? otherwise i kinda like it

Chris Ford

unread,
May 25, 2013, 8:35:49 AM5/25/13
to Clojure
Here's how I did it, which is similar to what atkaaz and Jim were suggesting:

(defn whenever [x applies-to? f] (if (applies-to? x) (f x) x))
(whenever obj :attr #(assoc % :attr something))
(whenever obj some-test some-transformation)

Cedric Greevey

unread,
May 25, 2013, 8:41:36 AM5/25/13
to clo...@googlegroups.com
If the condition is true of the transformed object (e.g., it's "if this condition isn't met, use this method to massage it so it is"), then there's this:

(first (filter condition? (map #(%1 obj) [identity transformation])))

It can even use laziness to avoid the transformation, but you'll need (list identity transformation) instead of [identity transformation] to avoid chunking the seq.

And it can then be extended with successively more-expensive transformations that it will try until one makes condition? true of its output.

Matching Socks

unread,
May 25, 2013, 8:52:33 AM5/25/13
to clo...@googlegroups.com

(update-in obj [:attr] #(or %1 %2) something)

(cond-> obj (not (:attr obj)) (assoc :attr something))



Steven Degutis

unread,
May 25, 2013, 9:18:13 AM5/25/13
to clo...@googlegroups.com
Seems like I almost want arbitrary or-like behavior, like only-sometimes-evaluation of a conditional form.

I feel like this could have something to do with lazy sequences, since technically has the option of never getting evaluated. We could use map.

Will test some things out in nrepl.el and return with interesting results if any.


On Sat, May 25, 2013 at 7:52 AM, Matching Socks <phill...@gmail.com> wrote:

(update-in obj [:attr] #(or %1 %2) something)

(cond-> obj (not (:attr obj)) (assoc :attr something))

Jim - FooBar();

unread,
May 25, 2013, 9:26:05 AM5/25/13
to clo...@googlegroups.com
> so maybe a let + gensym would be in order?


yes that is what you do to avoid double-evaluation...:) I was making a
different point though, the fact that definline produces a first class
fn which still expands like a macro.

Jim


atkaaz

unread,
May 25, 2013, 9:29:28 AM5/25/13
to clo...@googlegroups.com
yep that was interesting thanks btw; it was a function that was acting like a macro, how odd


--
--
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

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.

Kelker Ryan

unread,
May 25, 2013, 9:47:14 AM5/25/13
to clo...@googlegroups.com
Here's my solution to your problem. Let me know if it helps.
```
user> (defn if-attr [-key obj missing]
(if-let [ret (get obj -key)] ret (assoc obj -key missing)))
#'user/if-attr
user> (if-attr :abc {} "missing-value")
{:abc "missing-value"}
user> (if-attr :abc {:abc 123} "missing-value")
123
user>
```



25.05.2013, 22:29, "atkaaz" <atk...@gmail.com>:

> О©╫yep that was interesting thanks btw; it was a function that was acting like a macro, how odd
>
> О©╫On Sat, May 25, 2013 at 4:26 PM, Jim - FooBar(); <jimpi...@gmail.com> wrote:
>>> О©╫so maybe a let + gensym would be in order?
>> О©╫yes that is what you do to avoid double-evaluation...:) I was making a different point though, the fact that definline produces a first class fn which still expands like a macro.
>>
>> О©╫Jim
>>
>> О©╫--
>> О©╫--
>> О©╫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+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/groups/opt_out.
> О©╫--
> О©╫--
> О©╫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+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/groups/opt_out.

Jim - FooBar();

unread,
May 25, 2013, 9:50:56 AM5/25/13
to clo...@googlegroups.com
I just realised you can also do this to get the same effect of pred-transform...This is the first time I'm using cond->! I'm starting to realise its usefuleness... :)

 (let [rpred (complement pred)]
    (cond-> obj
        (pred obj)  identity ;;if true return it untouched
        (rpred obj) tf))       ;;if false do the transformation

Jim


On 25/05/13 14:29, atkaaz wrote:
yep that was interesting thanks btw; it was a function that was acting like a macro, how odd
On Sat, May 25, 2013 at 4:26 PM, Jim - FooBar(); <jimpi...@gmail.com> wrote:
so maybe a let + gensym would be in order?


yes that is what you do to avoid double-evaluation...:) I was making a different point though, the fact that definline produces a first class fn which still expands like a macro.

Jim



--
--
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

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/groups/opt_out.


--
--
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

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.

Steven Degutis

unread,
May 25, 2013, 11:05:29 AM5/25/13
to clo...@googlegroups.com
Another solution, repeats itself slightly less:

(assoc obj
  :key (or (:key obj)
           (something-else-with-side-effects-maybe?)))

Not sure I like it much better than the first one. But I usually prefer not writing my own macros/functions when I can avoid it, so it has that plus.


You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/1GFesvqspwk/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

dmirylenka

unread,
May 26, 2013, 6:05:49 AM5/26/13
to clo...@googlegroups.com
(merge {:attr something} obj)

dmirylenka

unread,
May 26, 2013, 6:19:11 AM5/26/13
to clo...@googlegroups.com
Sorry, that's just another suggestion for the first pattern.
For the second, I would use:

(cond-> obj (some-test obj) some-transformation)

On Sunday, May 26, 2013 12:05:49 PM UTC+2, dmirylenka wrote:
(merge {:attr something} obj)
Reply all
Reply to author
Forward
0 new messages