define fails at runtime, where let fails at compile time

102 views
Skip to first unread message

Brian Adkins

unread,
Mar 11, 2019, 11:21:39 AM3/11/19
to Racket Users
I just discovered that define will fail at runtime, where let would fail at compile time. Besides helping to keep the indentation level from marching to the right "too much", what are the benefits of define over let?

--- snip ---
#lang racket

(define (f n) (+ n 1))

(define (foo)
  (define b (f a))
  (define a 7)
  
  b)

(define (bar)
  (let ([b (f a)]
        [a 7])

    b))
--- snip ---

Matthias Felleisen

unread,
Mar 11, 2019, 12:29:40 PM3/11/19
to Brian Adkins, Racket Users
I think your characterization is a bit misleading here.

In ‘bar’ ‘a’ is not bound, something that Racket (and DrRacket) properly signal at compile time.

In ‘foo’ ‘a’ *is* bound, because you’ve set up a mutually recursive scope. But, when Racket evaluates (foo) it notices that ‘a’ is bound but uninitialized, which is two different things.

If you want to compare apples to apples, use a ‘letrec' instead of a ‘let' in ‘bar'. Then you have (1) the same semantics and (2) the same error.

— Matthias

Brian Adkins

unread,
Mar 11, 2019, 1:13:30 PM3/11/19
to Racket Users
I want let semantics, but I've been using define more because it's preferred in the Racket style guide. I don't want the behavior of define above, so using letrec to get a runtime error instead of compile time error doesn't make sense.

Greg Hendershott

unread,
Mar 11, 2019, 1:17:47 PM3/11/19
to Matthias Felleisen, Brian Adkins, Racket Users
To be fair:

As a new user, it's possible to have the intuition that `define` is
just a way to avoid indentation -- that it "writes a `let` for you,
from the point of the define to 'the end of the enclosing scope'".

And it's possible for that intuition to seem correct for a very long
time -- until you hit an example like Brian did. And then you need to
learn about `letrec`.

(As a non-new user, 99.9% of the time that I use a local `define` I
actually wish it were "like `let`" not `letrec`, but I use it anyway
and try to be careful.)
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Brian Adkins

unread,
Mar 11, 2019, 1:18:52 PM3/11/19
to Racket Users
Oops - I should've used let* in my example. 

Matthias Felleisen

unread,
Mar 11, 2019, 1:22:48 PM3/11/19
to Brian Adkins, Racket Users


> On Mar 11, 2019, at 1:18 PM, Brian Adkins <lojic...@gmail.com> wrote:
>
> I want let semantics, but I've been using define more because it's preferred in the Racket style guide. I don't want the behavior of define above, so using letrec to get a runtime error instead of compile time error doesn't make sense.
>
> Oops - I should've used let* in my example.


That wouldn’t change a thing in your example.

If you meant you want a let* semantics for sequences of define, I think that’s a good idea. And as the author of the Style Guide, I wholeheartedly agree with this desire. When I replace let-s with define-s, I have gotten used to checking for identifier sequencing and such. But perhaps a newbie shouldn’t have to think that way.

— Matthias




Brian Adkins

unread,
Mar 11, 2019, 1:23:34 PM3/11/19
to Racket Users
Yes, I hadn't really thought through the semantics of define (i.e. whether it had let or letrec semantics). So, in my case, since I want let semantics, I will use let. I'm happy to follow the Racket style guide when I get to the point of contributing code that is covered by it, but I think I will use let, when I want let semantics, for my own code.

Hendrik Boom

unread,
Mar 11, 2019, 1:42:02 PM3/11/19
to Racket Users
On Mon, Mar 11, 2019 at 01:17:08PM -0400, Greg Hendershott wrote:
> To be fair:
>
> As a new user, it's possible to have the intuition that `define` is
> just a way to avoid indentation -- that it "writes a `let` for you,
> from the point of the define to 'the end of the enclosing scope'".

Racket needs a way to avoid trouble with tail-indentation, just as it
avoids trouble with tail-recursion.

Years ago I used a lisp that used / to indicate a final sublist:
(a a a
/bbb bbb bbb
/def
)
meant the same as
(a a a
(bbb bbb bbb
(def
)))
It was very convenient and allowed let and if to nest gracefully.

Are there any spcial characters or the like left in Racket that could
be used for this purpose?

-- hendrik

Brian Adkins

unread,
Mar 11, 2019, 1:48:27 PM3/11/19
to Racket Users
On Monday, March 11, 2019 at 1:22:48 PM UTC-4, Matthias Felleisen wrote:


> On Mar 11, 2019, at 1:18 PM, Brian Adkins <lojic...@gmail.com> wrote:
>
> I want let semantics, but I've been using define more because it's preferred in the Racket style guide. I don't want the behavior of define above, so using letrec to get a runtime error instead of compile time error doesn't make sense.
>
> Oops - I should've used let* in my example.


That wouldn’t change a thing in your example.

My only point was that when using let, it fails even when ordered correctly, but with let* it succeeds when ordered correctly.
 
If you meant you want a let* semantics for sequences of define, I think that’s a good idea. And as the author of the Style Guide, I wholeheartedly agree with this desire. When I replace let-s with define-s, I have gotten used to checking for identifier sequencing and such. But perhaps a newbie shouldn’t have to think that way.

I would argue that *nobody* should have to think that way when we can have the compiler do it for us :) Obviously, I'm happy with a dynamically typed language, as I've chosen Racket over OCaml & Haskell, but I'm still happy to delegate some things to the compiler. 


Philip McGrath

unread,
Mar 11, 2019, 1:49:49 PM3/11/19
to Brian Adkins, Racket Users
I think it's fair to say that the Racket style guide is not as rigid as some other style guides for some other languages: as it says itself in the introduction, it "isn’t complete and it isn’t perfect" and is more a set of "guidelines and best practices" than binding universal rules. I think it is usually right (even about some things I didn't agree with the first time I read it), but I have a number of personal quirks in my style, as do many others whose code I've read. (For example, I think several of us experiment with brackets or braces instead of parentheses to distinguish `define-values` left-hand-sides from the form of `define` with a function header.)

In terms of `define` specifically, I think using `let` or `let*` instead when that is your intended semantics is justifiable under the style guide, and I sometimes do so myself. Even the guide itself gives a `let*` that "is not easily replaced with a series of defines." The example it calls out as "bad" uses `let` for a single, simple local definition, which I think is a much more clear-cut case of rightward drift with no corresponding benefit.\

On Mon, Mar 11, 2019 at 1:17 PM Greg Hendershott <greghen...@gmail.com> wrote:
As a new user, it's possible to have the intuition that `define` is
just a way to avoid indentation -- that it "writes a `let` for you,
from the point of the define to 'the end of the enclosing scope'".

That would be a reasonable intuition and arguably a nice semantics. I'm curious about what new users actually do think—I expect it depends on what language they come from. Off hand, I think `letrec` semantics is fairly common for local definitions, and I think a lot of mainstream languages don't make `let` or `let*` semantics available at all. (For a particularly egregious example, in Python, IIUC, it isn't always possible to even determine whether an identifier is bound or not until runtime.)

-Philip

rocketnia

unread,
Mar 17, 2019, 3:24:08 PM3/17/19
to Racket Users
On Monday, March 11, 2019 at 10:42:02 AM UTC-7, Hendrik Boom wrote:
Years ago I used a lisp that used / to indicate a final sublist

Whoa, that's exactly the same thing as the Parendown library I've implemented for Racket, seemingly right down to the punctuation and indentation style. (Well, since Racket's already using / for division, Parendown uses #/ for now, but there's still room to change this if anyone has better ideas.)

Do you have a link to the lisp you were using that had this feature? I'd like to add it to the "related work" section of the Parendown readme.

- rocketnia

Hendrik Boom

unread,
Mar 17, 2019, 8:35:56 PM3/17/19
to Racket Users
On Sun, Mar 17, 2019 at 12:24:07PM -0700, rocketnia wrote:
> On Monday, March 11, 2019 at 10:42:02 AM UTC-7, Hendrik Boom wrote:
> >
> > Years ago I used a lisp that used / to indicate a final sublist
> >
>
> Whoa, that's exactly the same thing as the Parendown
> <https://docs.racket-lang.org/parendown/> library I've implemented for
> Racket, seemingly right down to the punctuation and indentation style.
> (Well, since Racket's already using / for division, Parendown uses #/ for
> now, but there's still room to change this if anyone has better ideas.)
>
> Do you have a link to the lisp you were using that had this feature? I'd
> like to add it to the "related work" section of the Parendown readme
> <https://github.com/lathe/parendown-for-racket#related-work>.

No. I don't have a link. It was developed before I had internet
access, bootstrapped using Bill Waite's STAGE2 macro processor. It has
the lovely feature that its macro arguments *always* had matched
parentheses, no matter how long they were and so I used it in effect to
parse Lisp and generate VAX assembler.

I used the bootstrap version to build the version I actually used.

Much of the source code seems to have disappeared over the years.

-- hendrik
Reply all
Reply to author
Forward
0 new messages