Replacing nested let statements with assignments

1,456 views
Skip to first unread message

JvJ

unread,
Oct 18, 2012, 12:01:33 PM10/18/12
to clo...@googlegroups.com
I'm not sure if anyone's done this before, but I'm fed up with writing code that looks like this:

(let [a 1]
   (println "this is a: " a)
   (let [b 2]
       (println "this is b: " b)
       (let [c 3]
            (println "this is c: " c)
            (+ a b c))))

I'd rather do something more like this:
(-:>
   (:= a 1) ;; Assign a to 1
   (println "this is a: " a)
   (:= b 2)
   (println "this is b: " b)
   (:= c 3)
   (println "this is c: " c)
   (+ a b c))


I wrote a macro to do this, but I feel like it's such a simple thing that someone must have done it before, and it's probably better than mine.
Does anyone know of anything like that? 

Here's the code for it in case anyone wants to use it:

(ns cljutils.core)

(defn- form-check
  "Ensures the form represents an assignment.
Such as (:= a 1)"
  [form]
  (and
   (= 3 (count form))
   (= := (first form))
   (symbol? (second form))))
   

(defn- iblk-fn
  "Simulates imperative variable-assignments through nested
let statements."
  [& forms]
  (let [form (first forms)
        rforms (rest forms)]
    (if-not form
      nil
      ;; Else
      `(do
         ~@(if (form-check form)
             `((let [~(second form) ~(nth form 2)]
                 ~(apply iblk-fn rforms)))
             (let [[f r] (split-with (comp not form-check) forms)]
                (concat
                 f
                 (apply iblk-fn r))))))))

(defmacro -:>
  [& forms]
  (apply iblk-fn forms))

David Nolen

unread,
Oct 18, 2012, 12:11:34 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 12:01 PM, JvJ <kfjwh...@gmail.com> wrote:
I'm not sure if anyone's done this before, but I'm fed up with writing code that looks like this:

What problem does this solve given you can do the following?

(let [a 1
      _ (println a)
      b 2
      _ (println b)
      c 3
      _ (println c)]
   ...) 

keeds

unread,
Oct 18, 2012, 12:12:14 PM10/18/12
to clo...@googlegroups.com
I'm confused. How does the following not work?

(let [a 1 b 2 c 3]
  (println a)
  (println b)
  (println c)
  (+ a b c))

Ben Wolfson

unread,
Oct 18, 2012, 12:17:07 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 9:12 AM, keeds <ake...@gmail.com> wrote:
> I'm confused. How does the following not work?
>
> (let [a 1 b 2 c 3]
> (println a)
> (println b)
> (println c)
> (+ a b c))

It works, but all of the expressions on the RHS of the let
expression's binding vector have to be applied before you start
printing any of the values.

--
Ben Wolfson
"Human kind has used its intelligence to vary the flavour of drinks,
which may be sweet, aromatic, fermented or spirit-based. ... Family
and social life also offer numerous other occasions to consume drinks
for pleasure." [Larousse, "Drink" entry]

JvJ

unread,
Oct 18, 2012, 12:23:32 PM10/18/12
to clo...@googlegroups.com
I didn't realize you could bind to empty identifiers like that.  Alright, that makes more sense.  I figured I was missing something.

JvJ

unread,
Oct 18, 2012, 12:24:35 PM10/18/12
to clo...@googlegroups.com
Exactly.  A big part of the reason was that I needed to do things between when other variables were initialized.

David Nolen

unread,
Oct 18, 2012, 12:26:25 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 12:23 PM, JvJ <kfjwh...@gmail.com> wrote:
I didn't realize you could bind to empty identifiers like that.  Alright, that makes more sense.  I figured I was missing something.

Just to be clear _ has not special meaning beyond convention. I could have used x but that doesn't convey that we don't care about the return value.

David  

Ambrose Bonnaire-Sergeant

unread,
Oct 18, 2012, 12:26:14 PM10/18/12
to clo...@googlegroups.com
There's nothing special going on, no "empty" identifiers. It's just a common convention to use _ when uninterested in the return value.

(let [_ 1]
  _)
;=> 1

Pretty evil to actually use bindings called _ though :)

Thanks,
Ambrose

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

AtKaaZ

unread,
Oct 18, 2012, 12:27:11 PM10/18/12
to clo...@googlegroups.com
Thank you for this clarification!

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



--
I may be wrong or incomplete.
Please express any corrections / additions,
they are encouraged and appreciated.
At least one entity is bound to be transformed if you do ;)

Evan Gamble

unread,
Oct 18, 2012, 2:11:20 PM10/18/12
to clo...@googlegroups.com
For the situation where the lets are nested because you're checking the values in some way after each binding, I wrote a macro called let?. I find it very useful and use it in nearly all my code. https://github.com/egamble/let-else

Grant Rettke

unread,
Oct 18, 2012, 2:32:22 PM10/18/12
to clo...@googlegroups.com
-1 to using a binding form to do sequencing. That said, not sure what is better!

Alan Malloy

unread,
Oct 18, 2012, 2:55:12 PM10/18/12
to Clojure
It's rare to get tired of this, because nobody does it: it's not
common because your interleaved statements are side-effecting only,
which is not encouraged in Clojure, and rarely needed. Certainly
sometimes it's the best way to do something, but not so often that I'd
become frustrated; if anything, having to write such irritating code
can serve as a good reminder that I shouldn't have so many
unrestrained side effects scattered through my logic.

Grant Rettke

unread,
Oct 18, 2012, 3:01:58 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 1:55 PM, Alan Malloy <al...@malloys.org> wrote:
> It's rare to get tired of this, because nobody does it: it's not
> common because your interleaved statements are side-effecting only,
> which is not encouraged in Clojure, and rarely needed. Certainly
> sometimes it's the best way to do something, but not so often that I'd
> become frustrated; if anything, having to write such irritating code
> can serve as a good reminder that I shouldn't have so many
> unrestrained side effects scattered through my logic.

It isn't side effecting it is sequencing.

David Nolen

unread,
Oct 18, 2012, 3:02:33 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 2:55 PM, Alan Malloy <al...@malloys.org> wrote:
It's rare to get tired of this, because nobody does it: it's not
common because your interleaved statements are side-effecting only,
which is not encouraged in Clojure, and rarely needed. Certainly
sometimes it's the best way to do something, but not so often that I'd
become frustrated; if anything, having to write such irritating code
can serve as a good reminder that I shouldn't have so many
unrestrained side effects scattered through my logic.

I think from the examples debugging via print statements was the main (and reasonable) use case.

David 

Grant Rettke

unread,
Oct 18, 2012, 3:06:19 PM10/18/12
to clo...@googlegroups.com
David so as not to be a total loser here is an alternative that is
maybe the right way but also maybe the very wrong way!

(-> ((fn []
(let [a 1]
(println "this is a: " a)
a)))
((fn [a]
(let [b 2]
(println "this is b: " b)
(list a b))))
((fn [[a b]]
(let [c 3]
(println "this is c: " c)
(println "Sum: " (+ a b c))))))

JvJ what is your verdict?

David Nolen

unread,
Oct 18, 2012, 3:07:49 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 3:06 PM, Grant Rettke <gre...@acm.org> wrote:
(-> ((fn []
      (let [a 1]
        (println "this is a: " a)
        a)))
   ((fn [a]
      (let [b 2]
        (println "this is b: " b)
        (list a b))))
   ((fn [[a b]]
      (let [c 3]
        (println "this is c: " c)
        (println "Sum: " (+ a b c))))))

Why should you have to change the shape of your program to insert some debugging statements?

David 

Ben Wolfson

unread,
Oct 18, 2012, 3:09:08 PM10/18/12
to clo...@googlegroups.com
Clojure's let is already sequential, like Scheme's let*: "The bindings
are sequential, so each binding can see the prior bindings." R5RS
Scheme explicitly states that its let makes no guarantees about
sequences.

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



--

Grant Rettke

unread,
Oct 18, 2012, 3:13:28 PM10/18/12
to clo...@googlegroups.com
Writing a macro like JvJ did or writing all of your code inside of a
let statement like you did is also changing the shape of your program
just to print debug statements isn't it?

Sorry the way I understood the use case that you need to do something,
anything, maybe some expensive, before doing the *next thing*, and
same goes before the *next thing*. I didn't read it as inserting debug
statements at all.

Grant Rettke

unread,
Oct 18, 2012, 3:15:29 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 2:09 PM, Ben Wolfson <wol...@gmail.com> wrote:
> On Thu, Oct 18, 2012 at 12:01 PM, Grant Rettke <gre...@acm.org> wrote:
>> It isn't side effecting it is sequencing.
>
> Clojure's let is already sequential, like Scheme's let*: "The bindings
> are sequential, so each binding can see the prior bindings." R5RS
> Scheme explicitly states that its let makes no guarantees about
> sequences.

Understood but you don't write entire function definitions inside of a
let block :).

Then again I guess I misunderstood the use case, which is to just
print debug information once in a while.

Alan Malloy

unread,
Oct 18, 2012, 3:15:46 PM10/18/12
to Clojure
On Oct 18, 12:02 pm, David Nolen <dnolen.li...@gmail.com> wrote:
Indeed, but it's easy to add those in any number of ways other than
the code the OP was originally unhappy with. For example, by adding _
bindings for side effects, or by introducing a macro like:

(defmacro ? [form]
`(let [x# ~form]
(printf "%s is %s" '~form x#)
x#))

(let [a (? 1)
b (? (+ 3 a))]
...)

Mark Engelberg

unread,
Oct 18, 2012, 3:50:31 PM10/18/12
to clo...@googlegroups.com
When I want to add print commands for debugging, I usually either do it the way David Nolen described, i.e., binding _ to a printf statement, or I use a little utility macro like this (picked up from stackoverflow):
(defmacro dbg[x] `(let [x# ~x] (println "dbg:" '~x "=" x#) x#))

I agree with Evan Gamble that I frequently end up with code that is a mixture of conditional branching and binding.  This results in highly indented and less readable code and I think Clojure would really benefit from some additions to ameliorate this.

Evan's solution (https://github.com/egamble/let-else) allows you to put conditions in your let.
Cgrand's solution (https://github.com/cgrand/utils) allows you to put lets in your cond.

Either way works well.  I think Evan's way results in somewhat more compact code for the common case, whereas Cgrand's way feels a little more versatile (and his "flatter cond" is what I use).  I strongly urge you to pick one of these two techniques and include it in your project.  Once you try it and see how much cleaner the code is, you'll be hooked.

--Mark

Grant Rettke

unread,
Oct 18, 2012, 4:15:54 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 2:50 PM, Mark Engelberg
<mark.en...@gmail.com> wrote:
> Either way works well. I think Evan's way results in somewhat more compact
> code for the common case, whereas Cgrand's way feels a little more versatile
> (and his "flatter cond" is what I use). I strongly urge you to pick one of
> these two techniques and include it in your project. Once you try it and
> see how much cleaner the code is, you'll be hooked.

Is it poor form to do something like this inside a defn?

(def a 1)
(println "this is a: " a)
(def b 2)
(println "this is b: " b)
(def c 3)
(println "this is c: " c)

David Nolen

unread,
Oct 18, 2012, 4:17:15 PM10/18/12
to clo...@googlegroups.com
very poor form. def is always top level.

David 

Grant Rettke

unread,
Oct 18, 2012, 4:22:20 PM10/18/12
to clo...@googlegroups.com
It is poor form in that it will break things in confusing and horrible
ways or poor form from a style perspective?

When you use def inside a defn is it equivalent to a let binding like this?

(defn foo []
(def a 1)
(println a))

(defn foo []
((fn [a]
(println a)) 1))

David Nolen

unread,
Oct 18, 2012, 4:32:56 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 4:22 PM, Grant Rettke <gre...@acm.org> wrote:

When you use def inside a defn is it equivalent to a let binding like this?

(defn foo []
  (def a 1)
  (println a))

(defn foo []
  ((fn [a]
     (println a)) 1))

Not equivalent. 

Grant Rettke

unread,
Oct 18, 2012, 4:41:05 PM10/18/12
to clo...@googlegroups.com
I see. To what is it equivalent?

Mark Engelberg

unread,
Oct 18, 2012, 4:42:56 PM10/18/12
to clo...@googlegroups.com
A def, even inside defn, creates and binds a global variable.

Grant Rettke

unread,
Oct 18, 2012, 4:45:08 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 3:42 PM, Mark Engelberg
<mark.en...@gmail.com> wrote:
> A def, even inside defn, creates and binds a global variable.

Woa, I see, thanks!

Grant Rettke

unread,
Oct 18, 2012, 4:45:32 PM10/18/12
to clo...@googlegroups.com
Anyone voted for internal define lately?

David Nolen

unread,
Oct 18, 2012, 5:05:58 PM10/18/12
to clo...@googlegroups.com
At this point I think it's highly unlikely to change - the behavior is pretty well documented:

user=> (doc def)
-------------------------
def
  (def symbol doc-string? init?)
Special Form
  Creates and interns a global var with the name
  of symbol in the current namespace (*ns*) or locates such a var if
  it already exists.  If init is supplied, it is evaluated, and the
  root binding of the var is set to the resulting value.  If init is
  not supplied, the root binding of the var is unaffected.

JvJ

unread,
Oct 18, 2012, 5:09:52 PM10/18/12
to clo...@googlegroups.com
Exactly.  Not only debugging, but java interop that involved calling methods with side effects.

Grant Rettke

unread,
Oct 18, 2012, 5:10:59 PM10/18/12
to clo...@googlegroups.com
I figured you would use doto for that.
> --
> 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



--
Grant Rettke | ACM, AMA, COG, IEEE
gre...@acm.org | http://www.wisdomandwonder.com/
Wisdom begins in wonder.
((λ (x) (x x)) (λ (x) (x x)))

Grant Rettke

unread,
Oct 18, 2012, 5:11:54 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 4:05 PM, David Nolen <dnolen...@gmail.com> wrote:
> On Thu, Oct 18, 2012 at 4:45 PM, Grant Rettke <gre...@acm.org> wrote:
>> Anyone voted for internal define lately?
> At this point I think it's highly unlikely to change - the behavior is
> pretty well documented:

I see. Just a thought that an internal define not necessarily 'def'
might be a nice idea for a lot of these cases, but then again hey it
is lisp we can tweak it to our liking.
Message has been deleted

JvJ

unread,
Oct 18, 2012, 5:16:46 PM10/18/12
to clo...@googlegroups.com

On a side note, I was partially inspired by Haskell's do notation, which is imperative-looking syntactic sugar for monadic bind operators.

JvJ

unread,
Oct 18, 2012, 5:23:37 PM10/18/12
to clo...@googlegroups.com, gre...@acm.org
Most of what could be accomplished by an internal define could be done with a let statement.  But if you don't want to add the brackets, you can create your own function definition macro that converts defs to lets.

JvJ

unread,
Oct 18, 2012, 5:25:03 PM10/18/12
to clo...@googlegroups.com, gre...@acm.org
The doto form is great, but as far as I know, it only lets you thread a single object.  I'm looking at creating several objects consecutively.

Mark Engelberg

unread,
Oct 18, 2012, 5:33:29 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 1:45 PM, Grant Rettke <gre...@acm.org> wrote:

Anyone voted for internal define lately?

On the one hand, internal define would be nice because it would help alleviate the nested let problem and possibly be more intuitive for newcomers.

On the other hand, sometimes (rarely) you actually want to have a function create global variables.  If def didn't work the way it did, it would be extremely difficult to achieve that effect.

Since there are other ways to create local variables (albeit with the unfortunate consequence of increasing the indenting level) but no other convenient way to create global variables, the decision to have def always affect the top-level environment seems to me like it creates the most semantically expressive power.  So overall, I like it the way it is.

That said, in my own code I find it essential to work with one of the macros that provide a way to intermingle locals and conditionals without indenting the code halfway across the screen.  I'd like to see some standard way to do this eventually work its way into Clojure core.

AtKaaZ

unread,
Oct 18, 2012, 5:17:52 PM10/18/12
to clo...@googlegroups.com
deja-vu :)

On Thu, Oct 18, 2012 at 11:16 PM, JvJ <kfjwh...@gmail.com> wrote:

On a side note, I was partially inspired by Haskell's do notation, which is imperative-looking syntactic sugar for monadic bind operators.

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



--
I may be wrong or incomplete.
Please express any corrections / additions,
they are encouraged and appreciated.
At least one entity is bound to be transformed if you do ;)

Stuart Sierra

unread,
Oct 18, 2012, 5:52:12 PM10/18/12
to clo...@googlegroups.com
It's slightly different, but libraries such as Flow or Prismatic's Graph can be used to achieve a similar effect.

Flow: https://github.com/stuartsierra/flow
Graph: http://blog.getprismatic.com/blog/2012/10/1/prismatics-graph-at-strange-loop.html

Example using Flow:

(def the-flow
  (flow b ([a] (println "This is a:" a) 2)
        c ([b] (println "This is b:" b) 3)
        out ([a b c]
               (println "This is c:" c)
               (+ a b c))))

(def the-fn (flow-fn the-flow [a] out))

(the-fn 1)
;; This is a: 1
;; This is b: 2
;; This is c: 3
;;=> 6

Herwig Hochleitner

unread,
Oct 18, 2012, 7:11:06 PM10/18/12
to clo...@googlegroups.com
2012/10/18 Mark Engelberg <mark.en...@gmail.com>
When I want to add print commands for debugging, I usually either do it the way David Nolen described, i.e., binding _ to a printf statement, or I use a little utility macro like this (picked up from stackoverflow):

(defmacro dbg[x] `(let [x# ~x] (println "dbg:" '~x "=" x#) x#))

FWIW, when just wanting to print out debug values, I use a custom reader tag similar to the above macro:

(let [x #log/spy (+ a b)]
  (usage-of x))

Even though it's probably an abuse of the idea of reader tags, it has the advantage that its quickly added and removed, even without paredit.
Of course, that doesn't solve the mixed-let-cond problem.

Mark Engelberg

unread,
Oct 18, 2012, 8:25:53 PM10/18/12
to clo...@googlegroups.com
On Thu, Oct 18, 2012 at 4:11 PM, Herwig Hochleitner <hhochl...@gmail.com> wrote:

FWIW, when just wanting to print out debug values, I use a custom reader tag similar to the above macro:

(let [x #log/spy (+ a b)]
  (usage-of x))



That's nice!  I haven't done anything with reader macros.  Can you post the implementation?

Mark Engelberg

unread,
Oct 18, 2012, 8:28:52 PM10/18/12
to clo...@googlegroups.com
OK, just looked it up and realized that it's just how # works, and not a special kind of macro.

Herwig Hochleitner

unread,
Oct 18, 2012, 11:26:46 PM10/18/12
to clo...@googlegroups.com
2012/10/19 Mark Engelberg <mark.en...@gmail.com>

>
> OK, just looked it up and realized that it's just how # works, and not a special kind of macro.

 
That's the nice thing with reader tags.

They just generate new expressions, so their implementing fn can use syntax quote, just like a macro.

For completeness sake:

(defn emit-dbg [form]
  ;; `(clojure.tools.logging/spy ~form)
  `(let [x# ~form]
     (println "dbg:" '~x "=" x#) x#))

(defmacro dbg [form]
  (emit-dbg form))

;; in data_readers.clj

{log/dbg com.example.devutil/emit-dbg}

Thus the forms (dbg ...) and #log/dbg ... can both be used.

Herwig Hochleitner

unread,
Oct 18, 2012, 11:30:38 PM10/18/12
to clo...@googlegroups.com
This def

(defn emit-dbg [form] ...))

should have read:

(defn emit-dbg [form]
  ;; `(clojure.tools.logging/spy ~form)
  `(let [x# ~form] 
     (println "dbg:" '~form "=" x#) x#))

Sorry for the noise.

Bob Hutchison

unread,
Oct 19, 2012, 6:49:38 AM10/19/12
to clo...@googlegroups.com

On 2012-10-18, at 2:11 PM, Evan Gamble <solar...@gmail.com> wrote:

For the situation where the lets are nested because you're checking the values in some way after each binding, I wrote a macro called let?. I find it very useful and use it in nearly all my code. https://github.com/egamble/let-else

Hmmm, I can see how that might be very handy… like having guards on each binding. Thanks.

Cheers,
Bob
Reply all
Reply to author
Forward
0 new messages