is PG's "imperative outside-in" advice any good?

462 views
Skip to first unread message

Daniel Higginbotham

unread,
Oct 15, 2013, 8:29:29 AM10/15/13
to clo...@googlegroups.com
I've been going through On Lisp by Paul Graham and on page 33 he recommends against performing "intermediate" bindings. Does this advice hold for Clojure? Here are a couple examples:

;; Common Lisp (from the book)
(defun bad (x)
(let (y sqr)
(setq y (car x))
(setq sqr (expt y 2))
(list 'a sqr)))

(defun good (x)
(list 'a (expt (car x) 2)))

;; Clojure
(defn bad [x]
(let [y (first x)
sqr (expt y 2)]
(list 'a sqr)))

(defn good [x]
(list 'a (expt (first x) 2)))

Paul Graham explains:

"The final result is shorter than what we began with, and easier to understand. In the original code, we’re faced with the final expression (list 'a sqr), and it’s not immediately clear where the value of sqr comes from. Now the source of the return value is laid out for us like a road map.

The example in this section was a short one, but the technique scales up. Indeed, it becomes more valuable as it is applied to larger functions."

In clojure you can't do setq of course but I find myself going against this advice all the time, and I find that it's more important to do so when working with larger functions. I think introducing names makes code clearer. Here's an example from my own code:

(defn create-topic
[params]
(let [params (merge params (db/tempids :topic-id :post-id :watch-id))
topic (remove-nils-from-map (c/mapify params mr/topic->txdata))
watch (c/mapify params mr/watch->txdata)
post (c/mapify params mr/post->txdata)]
{:result (db/t [topic post watch])
:tempid (:topic-id params)}))

To my mind, creating bindings for "topic", "watch", and "post" makes the code easier to understand. When you get to "(db/t [topic post watch])" you don't have to deal with as much visual noise to understand exactly what's going into the transaction.

So, is PG's advice any good?

Thanks!
Daniel

Mikera

unread,
Oct 15, 2013, 9:56:11 AM10/15/13
to clo...@googlegroups.com
I certainly prefer giving names to intermediate results with a "let" block: having good names and breaking the computation up into logical chunks makes the code much easier to understand and maintain when you come back to it later.

PG's example though is bad for different reasons - this is actually mutating variables in an imperative style, which is definitely "bad style" - both in Lisp and Clojure I think. The Clojure equivalent would be to use atoms (or vars) and mutating them.

"let" on its own is purely functional, and doesn't have this problem.

Brian Hurt

unread,
Oct 15, 2013, 10:13:58 AM10/15/13
to clo...@googlegroups.com
Lifting subexpressions up into lets is actually something I do a lot- for one very important reason: it lets me insert print statements (or logging statements) showing the value of the subexpression.  So I'll do;
    (let [ x (subexpression) ]
        (main-expression))

because it lets me do:
    (let [ x (subexpression) ]
        (println "The value of x is" x)
        (main-expression))

If fact, a lot of times I'll do;
    (let [ x (subexpression)
            res (main-expression) ]
        res)

because it lets me do:
    (let [ x (subexpression)
            _ (println "The value of x is" x)
            res (main-expression) ]
        (println "The value of the whole expression is" res)
        res)

This is of great value in debugging.

Brian



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

Raoul Duke

unread,
Oct 15, 2013, 11:18:04 AM10/15/13
to clo...@googlegroups.com
if a programming language doesn't have something like 'where', then i
am sad. http://stackoverflow.com/questions/4362328/haskell-where-vs-let

Alex Baranosky

unread,
Oct 15, 2013, 12:00:10 PM10/15/13
to clo...@googlegroups.com
I and some of my coworkers do tend to avoid `let` unless in this particular case you especially want to emphasize the name of something unobvious. OFten I'd prefer to pull out a new function over using let, or inline the binding for readability *improvement*.


On Tue, Oct 15, 2013 at 8:18 AM, Raoul Duke <rao...@gmail.com> wrote:
if a programming language doesn't have something like 'where', then i
am sad. http://stackoverflow.com/questions/4362328/haskell-where-vs-let

Sean Corfield

unread,
Oct 15, 2013, 1:02:43 PM10/15/13
to clo...@googlegroups.com
Yeah, I found when I first got started with Clojure I tended to use
let for intermediate named results but over time I've moved to using
small, private top-level functions instead because I want to focus on
the names of the _functionality_ rather than the names of intermediate
_data_. I still use let for some things but nowhere near as much as I
used to.

One construct using let that I see in my code quite a bit that I
haven't figured out a cleaner way to express:

(let [x (some-expression)]
(if (p x)
(f x)
(g x)))

I get tempted to write a utility function for it but I haven't come up
with a good name for it :)

Sean
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Marcus Lindner

unread,
Oct 15, 2013, 1:13:01 PM10/15/13
to clo...@googlegroups.com
How looks this?

(defn conditional [x condition consequent alternative]
(if (condition x)
(consequent x)
(alternative x))))

(conditional (some-expression) p f g)

Guru Devanla

unread,
Oct 15, 2013, 1:15:04 PM10/15/13
to clo...@googlegroups.com
Sean,

The case you listed is where let binding becomes important so as to not perform duplicate evaluation. That is one place the utility of let stands out along with its scope.

For example 

Without let bindings:

  (if (p (some-expression))
    (f (some-expression))
    (g (some-expression))

introduces duplicate evaluation.  I see the utility of let more in such scenarios.  Whereas in the original PGs example that is not the case, and debate on that stands.

Sean Corfield

unread,
Oct 15, 2013, 1:16:59 PM10/15/13
to clo...@googlegroups.com
I know how to write it but I just don't like that name (or any other
I've come up with yet). And I'd probably put the `x` as the last
argument and provide a curried version.

Sean Corfield

unread,
Oct 15, 2013, 1:18:07 PM10/15/13
to clo...@googlegroups.com
On Tue, Oct 15, 2013 at 10:15 AM, Guru Devanla <grd...@gmail.com> wrote:
> The case you listed is where let binding becomes important so as to not
> perform duplicate evaluation. That is one place the utility of let stands
> out along with its scope.

Yup, exactly. I just see it often enough that I feel that it should be
codified somehow...

Jim - FooBar();

unread,
Oct 15, 2013, 1:19:51 PM10/15/13
to clo...@googlegroups.com
On 15/10/13 18:02, Sean Corfield wrote:
> One construct using let that I see in my code quite a bit that I
> haven't figured out a cleaner way to express:
>
> (let [x (some-expression)]
> (if (p x)
> (f x)
> (g x)))


wasn't cond-> designed exactly for that?

(let [x (some-expression)]
(cond-> x
(p x) f
((comlpement p) x) g)))

Jim

Sean Corfield

unread,
Oct 15, 2013, 1:48:28 PM10/15/13
to clo...@googlegroups.com
On Tue, Oct 15, 2013 at 10:19 AM, Jim - FooBar(); <jimpi...@gmail.com> wrote:
> wasn't cond-> designed exactly for that?
>
> (let [x (some-expression)]
> (cond-> x
> (p x) f
> ((comlpement p) x) g)))

That's uglier than the if :)

Sometimes I wish there was a variant of cond-> that took functions
instead of boolean expressions and threaded the expression thru those
(just to wander further off-topic)...

Phillip Lord

unread,
Oct 15, 2013, 1:49:02 PM10/15/13
to clo...@googlegroups.com

What about "let-some-expression-if-then-else"?


________________________________________
From: clo...@googlegroups.com [clo...@googlegroups.com] on behalf of Sean Corfield [seanco...@gmail.com]
Sent: 15 October 2013 18:02
To: clo...@googlegroups.com
Subject: Re: is PG's "imperative outside-in" advice any good?

Ben Wolfson

unread,
Oct 15, 2013, 1:51:01 PM10/15/13
to clo...@googlegroups.com
On Tue, Oct 15, 2013 at 10:48 AM, Sean Corfield <seanco...@gmail.com> wrote:
On Tue, Oct 15, 2013 at 10:19 AM, Jim - FooBar(); <jimpi...@gmail.com> wrote:
> wasn't cond-> designed exactly for that?
>
> (let [x (some-expression)]
> (cond-> x
>     (p x) f
>     ((comlpement p) x) g)))

That's uglier than the if :)

Also less efficient, since it may require evaluating (p x) twice.

If it were me, I'd use something like this:

(defn either
   "Return a function that takes one argument, x, and returns (f x) if (p x) is truthy and (g x) otherwise."
   [p f g]
   (fn [x] (if (p x) (f x) (g x))))

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

Bob Hutchison

unread,
Oct 15, 2013, 3:43:01 PM10/15/13
to clo...@googlegroups.com

On 2013-10-15, at 8:29 AM, Daniel Higginbotham <nonrec...@gmail.com> wrote:

> I've been going through On Lisp by Paul Graham and on page 33 he recommends against performing "intermediate" bindings. Does this advice hold for Clojure? Here are a couple examples:
>
> ;; Common Lisp (from the book)
> (defun bad (x)
> (let (y sqr)
> (setq y (car x))
> (setq sqr (expt y 2))
> (list 'a sqr)))
>
> (defun good (x)
> (list 'a (expt (car x) 2)))
>
> ;; Clojure
> (defn bad [x]
> (let [y (first x)
> sqr (expt y 2)]
> (list 'a sqr)))
>
> (defn good [x]
> (list 'a (expt (first x) 2)))

The closest I can think of to the bad CL code in Clojure would be something like:

(defn bad-clojure [x]
(let [v (first x)
v (Math/pow v 2)
v (list 'a v)]
v))

I don't think anyone at all would find either the bad CL or Clojure code okay. This is more of an admonition to not be an idiot :-)

Your Clojure code is equivalent to the following CL code:

(defun good2 (x)
(let* ((y (car x))
(sqr (expt y 2)))
(list 'a sqr)))

There's nothing wrong with writing code that way, it's functional and clear. What you're doing with this style of writing is introducing intermediate named values. Sometimes those just clutter up the code, sometimes they clarify. Use your own good taste. You'd do the same thing in Haskell, but I'd use a where clause.

>
> Paul Graham explains:
>
> "The final result is shorter than what we began with, and easier to understand. In the original code, we’re faced with the final expression (list 'a sqr), and it’s not immediately clear where the value of sqr comes from. Now the source of the return value is laid out for us like a road map.
>
> The example in this section was a short one, but the technique scales up. Indeed, it becomes more valuable as it is applied to larger functions."

Yes, in the bad CL code you don't know where the value of sqr comes from and the problem gets much worse as the code gets longer. This is the problem with imperative code in general.

You don't have that problem in either Clojure or the 'good2' CL program.

>
> In clojure you can't do setq of course but I find myself going against this advice all the time

He's not talking about intermediate names for values, he's talking about imperative assignments using setq, setf, and friends. Clojure's nearest equivalent, that I can think of, is shadowing in a let statement, and I think you'll find this is frowned upon.

> So, is PG's advice any good?

Yup.

Cheers,
Bob

>
> Thanks!
> Daniel

Daniel Higginbotham

unread,
Oct 15, 2013, 7:49:18 PM10/15/13
to clo...@googlegroups.com
Thanks for all the responses! 

This discussion will definitely help me write better code. This was especially helpful:

over time I've moved to using 
small, private top-level functions instead because I want to focus on 
the names of the _functionality_ rather than the names of intermediate 
_data_

As was this:

because it lets me do:
    (let [ x (subexpression)
            _ (println "The value of x is" x)
            res (main-expression) ]
        (println "The value of the whole expression is" res)
        res)

Thanks!
Daniel

dgrnbrg

unread,
Oct 15, 2013, 9:41:38 PM10/15/13
to clo...@googlegroups.com
If this is something you do often, spyscope is a library I wrote to simplify this sort of investigation. You can print an expression by writing #spy/d in front of it. For more information, you can read at https://github.com/dgrnbrg/spyscope

Benny Tsai

unread,
Oct 17, 2013, 2:06:43 AM10/17/13
to clo...@googlegroups.com
For those who use clojure.tools.logging, there's also the handy "spy" function.

Mars0i

unread,
Oct 17, 2013, 7:04:18 PM10/17/13
to clo...@googlegroups.com
In CL, `let*` works like Clojure's `let`, in that both allow you to bind later variables to valued calculated from earlier ones.  (CL's `let` only allows references to things defined before entering the `let`.)  A couple of years ago I was hacking on some CL code originally written by someone else, with a lot of `let*`s in it.  I started adding bindings, and them more, and after a while I just could not make it work.  The order of bindings was crucial, and the right hand sides were referring to multiple variables bound elsewhere in the `let*`.  It was driving me crazy.  I realized that the whole thing I'd created was an instance of bad coding style.    I just pulled things apart and put the code into separate functions that called each other.   And I replaced all of my `do*`s (loops with bindings that can refer to each other) with `mapcar` or `mapc` (like Clojure's `map`).  Much clearer. 

I now try to avoid `let*` as much as possible in CL, and when I use it, I make sure that I keep things simple.  I'm just learning Clojure.  I'm not going to avoid `let`, but I will try to make sure that I use it carefully.  I agree with other posters here that sometimes code is clearer and easier to understand if it's broken into sequential bindings, but it depends.  I think that often it's better to use a series of separate function calls instead of a big `let`.  I would say that for me, a good rule of thumb is that a `let` should bind no more than four or five variables, maximum, and that if there are more variables, their rhs's should usually refer only to the variable defined on the previous line.  Otherwise it's too hard to keep track of the dependencies.

Maybe the right thing to say is: Follow PG's rule, except when it's better to break it.  And then keep it simple.

Those aren't rules that anyone else has to follow, of course.  Do what works for you.  This is how I think about it.
Reply all
Reply to author
Forward
0 new messages