[racket] An example of let-vs-define: SICP 2.64

247 views
Skip to first unread message

Danny Yoo

unread,
Nov 2, 2012, 5:05:08 PM11/2/12
to Racket mailing list
I was just looking at exercise 2.64 of SICP, one that creates a BST out of an ordered list of elements:


However, because of all the nested lets, I have to admit that I was having a hard time reading it!



The rewritten code is:


with the following changes:

  1. Rewrote the packing/unpacking of 2-tuple lists in partial-tree with multiple values.

  2. Replaced nested lets with defines.

  3. Reordered some of the variables definitions to highlight relationships.


I thought the function itself was neat, and just wanted to share.

Matthias Felleisen

unread,
Nov 2, 2012, 5:17:34 PM11/2/12
to Danny Yoo, Racket mailing list

I guess we should take this also as a confirmation of the style guide :-)
> ____________________
> Racket Users list:
> http://lists.racket-lang.org/users

Grant Rettke

unread,
Nov 2, 2012, 5:29:09 PM11/2/12
to Matthias Felleisen, Racket mailing list
Internal define is awesome.
--
Grant Rettke | ACM, AMA, COG, IEEE
gre...@acm.org | http://www.wisdomandwonder.com/
Wisdom begins in wonder.
((λ (x) (x x)) (λ (x) (x x)))

Sam Tobin-Hochstadt

unread,
Nov 2, 2012, 5:33:41 PM11/2/12
to Grant Rettke, Racket mailing list, Matthias Felleisen
On Fri, Nov 2, 2012 at 5:29 PM, Grant Rettke <gre...@acm.org> wrote:
> Internal define is awesome.

I agree in general, but I did recently fix a bug in Typed Racket that
was made possible entirely by use of internal `define`:
https://github.com/plt/racket/commit/0e71f2d5dc#L1R16

The original code had a `let`, which I changed to define, and then
renamed a variable, leading to a bug that would have been caught with
let, but instead produced an infinite loop in some cases with internal
define.

Just a cautionary tale,
Sam

Grant Rettke

unread,
Nov 2, 2012, 5:51:07 PM11/2/12
to Sam Tobin-Hochstadt, Racket mailing list, Matthias Felleisen
On Fri, Nov 2, 2012 at 4:33 PM, Sam Tobin-Hochstadt <sa...@ccs.neu.edu> wrote:
Just a cautionary tale,+++


Good point. Thanks for sharing. 

Neil Van Dyke

unread,
Nov 2, 2012, 6:19:32 PM11/2/12
to Danny Yoo, Racket mailing list
Here's a different comparison of let-vs-internal-define, using your code.

Your internal define example (hopefully whitespace doesn't get mangled
by however you're reading this message):

(define (partial-tree elts n)
(cond [(= n 0)
(values EMPTY-TREE elts)]
[else
(define left-size (quotient (- n 1) 2))
(define-values (left-tree non-left-elts)
(partial-tree elts left-size))

(define this-entry (first non-left-elts))

(define right-size (- n (+ left-size 1)))
(define-values (right-tree remaining-elts)
(partial-tree (rest non-left-elts) right-size))

(values (tree this-entry left-tree right-tree)
remaining-elts)]))

Simply transliterated to a let syntax, and with a more readable zero
test (which you'd need a "begin" to do with internal define, incidentally):

(define (partial-tree elts n)
(if (zero? n)
(values empty-tree elts)
(let* ((left-size (quotient (- n 1) 2))
((left-tree non-left-elts) (partial-tree elts left-size))
(this-entry (first non-left-elts))
(right-size (- n (+ left-size 1)))
((right-tree remaining-elts) (partial-tree (rest
non-left-elts)
right-size)))
(values (tree this-entry left-tree right-tree)
remaining-elts))))

Which is better is subjective, but in the "let" example, I can instantly
see that there is an ordered sequence of bindings, and what the names
are, in a visually regular format.

In the define example, I have to first see that there's an extent
consisting solely of a bunch of define forms in it with no non-define
code hidden, then look for the names, then examine the code for ordering
(e.g., are there recursive/forward references?), and tread carefully so
that I don't get scrod by a toplevel-like idiosyncrasy.

This is a simple example. I think let can sometimes win slightly more
when the examples get more complicated. (And there are other examples,
such as with huge nested definitions, in which I sometimes think
internal define might be more readable, but overall I usually find let
to be more readable.)

In summary: You can make "let*-values" be unnecessarily awkward, and you
can cherry-pick throwaway problem set code from SICP, and you can tell
people they have to type ugly square brackets when they don't, but the
people of Let will not be intimidated! Long live Let! :)

Neil V.

Greg Hendershott

unread,
Nov 8, 2012, 10:06:57 AM11/8/12
to us...@racket-lang.org
In general I love internal define and have been using it heavily.

After the initial infatuation, I still love it. I've also come to
appreciate that sometimes let can be clearer. I like having the
choice.

Another way internal define can bite you is if you're not real crisp
on your understanding of certain forms with optional parts. For
instance syntax-case clauses. Let's say I have a friend (ahem) who
didn't really grok the optional guard/fender part, but that hadn't
mattered when writing stuff like:

(syntax-case stx ()
[(_ my pattern)
(let ([id rhs])
#'(my template))])

One day tries:

(syntax-case stx ()
[(_ my pattern)
(define id rhs)
#'(my template)])

This friend of mine (cough) was confused for awhile before figuring it out.

Of course the primary problem here is me^H^H my friend didn't know
this aspect of syntax-case clauses. I'm just saying that switching
from let to define can flush out some misunderstandings. Shrug.

Matthias Felleisen

unread,
Nov 8, 2012, 12:36:08 PM11/8/12
to Greg Hendershott, us...@racket-lang.org

That happens to the best and worst of us. I switch among
syntax-rule, syntax-rules, syntax-case, and syntax-parse.
I have made the same mistake as your friend.
Reply all
Reply to author
Forward
0 new messages