Question about style

180 views
Skip to first unread message

Bob Heffernan

unread,
Aug 11, 2018, 10:11:19 AM8/11/18
to racket...@googlegroups.com
Dear all,

I am new to Racket and only slightly less new to scheme & scheme-like
languages.

I have noticed myself often doing something like the following:

(define (foo x)
(let* ([y (f x)]
[z (g y)]
[p (h z)])
(bar p)))

Which could, of course, be written as

(define (foo x)
(bar (h (g (f x)))))

Here's an example from something I was just working on:

(define (get-data input)
(let* ([url-string (construct-url input)]
[url (string->url url-string)]
[port (get-pure-port url)])
(read-json port)))

which, again, could be written as:
(define (get-data input)
(read-json (get-pure-port (string->url (construct-url input)))))

My question is: is the way I'm writing things considered to be bad
style? It feels like a hangover from more imperative-style programming
& the inclination to do one thing "per line". On the other hand, it
often helps readability.

It might be, of course, that both versions amount to the same thing
after the interpreter has been at them.

Thanks and regards,
Bob Heffernan

Wolfgang Hukriede

unread,
Aug 11, 2018, 10:30:45 AM8/11/18
to Racket Users
My advice would be to follow your own taste. But drop the brackets.

Shu-Hung You

unread,
Aug 11, 2018, 11:29:26 AM8/11/18
to Racket Users
For small expressions it probably does not matter, but suitably naming
intermediate expressions is definitely a good approach as it
`explains' the code. Also, replacing let* by define can reduce nesting
level. These 2 points are suggested in the style guide 4.2 and 4.4:

https://docs.racket-lang.org/style/Choosing_the_Right_Construct.html
> --
> 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.

Deren Dohoda

unread,
Aug 11, 2018, 1:39:58 PM8/11/18
to Bob Heffernan, Racket Users


My question is: is the way I'm writing things considered to be bad
style?  It feels like a hangover from more imperative-style programming
& the inclination to do one thing "per line".  On the other hand, it
often helps readability.

I invariably write my code like this. I just think it's easier to understand two years later. I have a few let-like macros for this reason. 1) print-let /print-let*, for printf-debugging of let forms. 2) andlet / andlet*, for stopping binding evaluation in a let when the value to bind is #f (then the whole 'let' expression is #f). Sometimes I use internal 'define's instead, based on feel for heavily-indented code. I rarely try to write a complicated expression directly without some kind of explanatory setup. I have tinkered with some other 'let'-like forms but these two in particular are really useful. 

Deren

Andrew J

unread,
Aug 11, 2018, 8:09:49 PM8/11/18
to Racket Users
I typically use either threading or composition...

(require threading)
(define (foo x)
  (~> x 
        f g h bar))

or

(define (foo x)
  ((compose bar h g f) x)


A.

Stefan Schmiedl

unread,
Aug 12, 2018, 5:29:39 AM8/12/18
to Racket Users
"Andrew J" <and...@alphajuliet.com>, 12.08.2018, 02:09:

> I typically use either threading or composition...

> (require threading)
> (define (foo x)
> (~> x
> f g h bar))

"threading" is not included in the default racket installer, so
how do I get it?

The package manager tells me that there is a "threading" package,
but I'm not sure if it is the correct one.

Browsing over to https://pkgs.racket-lang.org and searching for
"threading" offers me a few more options, the three "threading"
packages amongst them. But now there is a link to "Documentation"
(yay, well-written documentation) and I see that the "threading"
package indeed provides the threding macro quoted above.


Wouldn't it be nice to have a link to https://pkgs.racket-lang.org
in the "Related Web Sites" submenu of DrRacket's "Help" menu?

Reasons:
1. I started looking there instead of under File, Package Manager,
and expected to find the "official" package site listed there.
2. The web site allows me to browse the documentation before
installing anything, which I can't do from the PM window

Regards,
s.

Greg Hendershott

unread,
Aug 12, 2018, 3:56:48 PM8/12/18
to Racket-Users List
As others said, naming intermediate values can make things clearer.
(Taken too far, maybe it's hard to see the forest for the trees? I
think it depends on the audience and the domain.)

You happened to choose an example that illustrates a reason maybe not
to do this with let* or define -- cleaning up resources like input
ports. I would write this particular thing using call/input-url.

https://docs.racket-lang.org/net/url.html#(def._((lib._net%2Furl..rkt)._call%2Finput-url))

For instance, I'd probably change construct-url to return a url struct
like its name suggests, then write something like:

(define (get-data input)
(define url (construct-url input))
(call/input-url url get-pure-port read-json))

or perhaps just:

(define (get-data input)
(call/input-url (construct-url input)
get-pure-port
read-json))

This takes care of closing the port, even if read-json errors.

Also, I like the way it reads: "From this URL, connect this way, and
read this way." So, this is another way to name pieces, taking
advantage of well-chosen names for the functions.

Daniel Prager

unread,
Aug 14, 2018, 10:27:49 PM8/14/18
to Stefan Schmiedl, Racket Users
The "threading" package is an example of something I'd like to see promoted to the main Racket distribution. 

Reason: it adds clarity for an attractive style, and makes it available out-of-the-box*, on a par with e.g. Clojure,  F#, and Elm.

Just my opinion.

But it does raise a question for the core group, "How do packages get promoted?"

Dan

* Or almost out-of-the-box, if an additional (require racket/threading) is still needed.

Paulo Matos

unread,
Aug 16, 2018, 5:44:09 AM8/16/18
to racket...@googlegroups.com


On 11/08/18 16:11, Bob Heffernan wrote:
> Dear all,
>
> I am new to Racket and only slightly less new to scheme & scheme-like
> languages.
>
> I have noticed myself often doing something like the following:
>
> (define (foo x)
> (let* ([y (f x)]
> [z (g y)]
> [p (h z)])
> (bar p)))

I really, really don't like nesting and a few years ago adopted the
style of internal defines. It makes for much more readable code:

(define (foo x)
(define y (f x))
(define z (g y))
(define p (h z))

(bar p))

I avoid nesting, long lines and short names. So I would try to name the
variables appropriatelly. If intermediate variables don't have a
'meaning' such that they can be given proper names, maybe they don't
deserve to be a variable and instead I will compose.

Just my 2 cents.

Paulo Matos

>
> Which could, of course, be written as
>
> (define (foo x)
> (bar (h (g (f x)))))
>
> Here's an example from something I was just working on:
>
> (define (get-data input)
> (let* ([url-string (construct-url input)]
> [url (string->url url-string)]
> [port (get-pure-port url)])
> (read-json port)))
>
> which, again, could be written as:
> (define (get-data input)
> (read-json (get-pure-port (string->url (construct-url input)))))
>
> My question is: is the way I'm writing things considered to be bad
> style? It feels like a hangover from more imperative-style programming
> & the inclination to do one thing "per line". On the other hand, it
> often helps readability.
>
> It might be, of course, that both versions amount to the same thing
> after the interpreter has been at them.
>
> Thanks and regards,
> Bob Heffernan
>

--
Paulo Matos

Robert Girault

unread,
Aug 16, 2018, 8:22:48 AM8/16/18
to Racket
On Thu, Aug 16, 2018 at 6:44 AM 'Paulo Matos' via Racket Users
<racket...@googlegroups.com> wrote:
> On 11/08/18 16:11, Bob Heffernan wrote:
> > Dear all,
> >
> > I am new to Racket and only slightly less new to scheme & scheme-like
> > languages.
> >
> > I have noticed myself often doing something like the following:
> >
> > (define (foo x)
> > (let* ([y (f x)]
> > [z (g y)]
> > [p (h z)])
> > (bar p)))
>
> I really, really don't like nesting and a few years ago adopted the
> style of internal defines. It makes for much more readable code:
>
> (define (foo x)
> (define y (f x))
> (define z (g y))
> (define p (h z))
>
> (bar p))
>
> I avoid nesting, long lines and short names. So I would try to name the
> variables appropriatelly. If intermediate variables don't have a
> 'meaning' such that they can be given proper names, maybe they don't
> deserve to be a variable and instead I will compose.

I think I'd write your example like this.

(define (foo x)
(local ((define y (f x))
(define z (g y))
(define p (h z)))
(bar p)))

(If I knew what f, g, h do, I might write it differently. If they're
all only for computing p, local expresses the fact.)

I was educated by HtDP 1st edition:
https://htdp.org/2018-01-06/Book/part_three.html#%28part._sec~3alocal-definitions%29

Matthias Felleisen

unread,
Aug 16, 2018, 9:25:46 AM8/16/18
to Robert Girault, Racket

> On Aug 16, 2018, at 8:22 AM, Robert Girault <rfrancoi...@gmail.com> wrote:
>
> I think I'd write your example like this.
>
> (define (foo x)
> (local ((define y (f x))
> (define z (g y))
> (define p (h z)))
> (bar p)))
>
> (If I knew what f, g, h do, I might write it differently. If they're
> all only for computing p, local expresses the fact.)
>
> I was educated by HtDP 1st edition:
> https://htdp.org/2018-01-06/Book/part_three.html#%28part._sec~3alocal-definitions%29


Thanks for the up-vote but let me explain the “local” rationale here and vote for the ‘inner define’ variant.

I added ‘local’ to the TEACHING languages for semantic consistency. This decision was based on my inner semanticist rather than mass psychology of students but with psychology in mind.

1. A series of global (in the sense of 'module level') defines have a certain semantics (both static and dynamic). If you experiment using global defines and then move them into the innards of a function, you want the _exact_ _same_ semantics. So

(define a (A 0))
(define b (B 1))
(define c (C 2))

would become

(define (f x)
(define a (A 0))
(define b (B 1))
(define c (C 2))
. . . )

This works well and fine for many but _not all_ sequences. For some you got a different error message .. and I consider rare but feasible failure in a teaching setting _insidious_. So I decided to come up with a construct that preserves the exact same semantics.

2. There is a secondary but minor reason. Internal defines introduce a new syntactic category into the language while ‘local’ defines simply extend the syntactic category of expressions. I like keeping the number of syntactic categories small. And I was afraid that once I opened the door, we’d get many things.

3. Since I made this decision, we also changed Racket’s behavior so that it signals undefined defines (during evaluation) uniformly. For all I know the semantic rationale (pt. 1) no longer applies, only the syntactic one (pt. 2).

4. While the teaching languages share syntax with Racket (as in #lang racket), they are not Racket.

;; - - -

In Racket programs for the world, possibly real, you want to avoid rightward drift. Indenting deeper and deeper makes code appear ‘ugly’ to many eyes, especially those used to other languages. But I will say this is also the one point about ‘ugly’-syntax languages that I have learned to appreciate (plus some concision in names).

Internal defines are thus much more preferable than local, let, letrec, and similar constructs. See the Style Guide, where I spelled this out in a bit more detail.

— Matthias


Style Guide https://docs.racket-lang.org/style/index.html



Bob Heffernan

unread,
Aug 16, 2018, 12:44:02 PM8/16/18
to racket...@googlegroups.com
On 18-08-16 11:43, 'Paulo Matos' via Racket Users wrote:
> I really, really don't like nesting and a few years ago adopted the
> style of internal defines. It makes for much more readable code:
>
> (define (foo x)
> (define y (f x))
> (define z (g y))
> (define p (h z))
>
> (bar p))

Thank you to Paulo and everybody else for your very helpful feedback.

When it comes to nesting (which, I guess, means the use of let) vs
internal defines: is this purely an aesthetic thing?

Regards,
Bob

Robert Girault

unread,
Aug 16, 2018, 1:29:46 PM8/16/18
to Matthias Felleisen, Racket
On Thu, Aug 16, 2018 at 10:25 AM Matthias Felleisen
<matt...@felleisen.org> wrote:
> > On Aug 16, 2018, at 8:22 AM, Robert Girault <rfrancoi...@gmail.com> wrote:
> >
> > I think I'd write your example like this.
> >
> > (define (foo x)
> > (local ((define y (f x))
> > (define z (g y))
> > (define p (h z)))
> > (bar p)))
> >
> > (If I knew what f, g, h do, I might write it differently. If they're
> > all only for computing p, local expresses the fact.)
> >
> > I was educated by HtDP 1st edition:
> > https://htdp.org/2018-01-06/Book/part_three.html#%28part._sec~3alocal-definitions%29
>
> Thanks for the up-vote but let me explain the “local” rationale here and vote for the ‘inner define’ variant.

[...]

> Style Guide https://docs.racket-lang.org/style/index.html

Nice to know. Thank you. (I'll keep it ``in my backpack.'')

Deren Dohoda

unread,
Aug 16, 2018, 7:25:46 PM8/16/18
to Matthias Felleisen, Robert Girault, Racket

Thanks for the up-vote but let me explain the “local” rationale here and vote for the ‘inner define’ variant.
[snip]...


In Racket programs for the world, possibly real, you want to avoid rightward drift. Indenting deeper and deeper makes code appear ‘ugly’ to many eyes, especially those used to other languages. But I will say this is also the one point about ‘ugly’-syntax languages that I have learned to appreciate (plus some concision in names).

Internal defines are thus much more preferable than local, let, letrec, and similar constructs. See the Style Guide, where I spelled this out in a bit more detail. 

In general cases I agree with this and view heavy indenting as at a minimum code smell but 'let' is so conceptually simple to work with syntactically. If I want to extend/alter/make an analogous construction of 'let' it's three to twenty lines depending on what I'm doing. I have no clue how to start messing with internal definitions in a similar way.

Deren

Greg Hendershott

unread,
Aug 16, 2018, 11:22:30 PM8/16/18
to Matthias Felleisen, Racket-Users List
Have you ever considered extending the grammar of define from this:

(define id expr)
(define (head args) body ...+)

To this:

(define id expr ... ...) ; <-- like e.g. `hash`
(define (head args) body ...+)

So we could write things like:

(define x 0
y 1
z 2)

Sometimes so much writing/reading of "define" feels like too low a
signal:noise ratio.

Or would this cause problems?


p.s. I guess it's a slippery slope. For instance what I'd like even
more is for `match-define` extended that way, and given a name like
`define` or `def`:

(define pat expr ... ...)

Philip McGrath

unread,
Aug 16, 2018, 11:30:33 PM8/16/18
to Greg Hendershott, Matthias Felleisen, Racket-Users List
With just a few more parentheses, I have a macro that lets you write:
(def
  [x 0]
  [y 1]
  [z 2]
  ;; or even
  [(f x) (/ (+ x y) z)])


I like explicit delimiters, but I agree that a bunch of one-line `define`s feels pretty painful.

-Philip

--
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+unsubscribe@googlegroups.com.

Neil Van Dyke

unread,
Aug 17, 2018, 1:07:26 AM8/17/18
to Bob Heffernan, racket...@googlegroups.com
Bob Heffernan wrote on 08/16/2018 12:43 PM:
>
> When it comes to nesting (which, I guess, means the use of let) vs
> internal defines: is this purely an aesthetic thing?

I think, practically, you could call it only aesthetic, since the PLT
developers are invested in making internal `define` work as well as `let`.

Early in the evolution of Racket, it was PLT Scheme.  Early on, one
could often see instantly that a bit of code was written by a PLT
person, because only Matthias and his students (many now professors)
would do things like use square brackets.

One way to look at things like square brackets and internal `define` are
like a charming accent on a language that you also speak.  And maybe
yours is a charming accent, to them.

An accent doesn't have to be a big barrier to communication; your ear
quickly learns to adapt.  And, most importantly, an accent tends to come
along with a very valuable diversity of ideas.

In the case of seeing PLT Scheme from the outside, some of us could see
some other value separate from the accent, and we decided to go live in
PortLanTia.  (I can hear the groans even before I click Send.)

Many of us in PortLanTia have our own unique and evolving accents. I
think that's a good thing, helping to keep the collective intellectually
fresh and open, and not get too insular.  You can speak however you like
in PortLanTia. "https://docs.racket-lang.org/style/" is only the style
guide for official government (i.e., Git commits to code of core Racket
itself).  The newspapers, novelists, and businesses have their own
styles, and the government doesn't dictate how they speak.

Now, if you're new to the language of PortLanTia, and you don't yet know
what your accent will be, you might start by trying to speak according
to the style of the official government documents.  It's conveniently
written up, you don't yet know how the important subtleties differ from
things you already know from elsewhere, and this style was prescribed by
those who knew some of the subtleties well.  Over time, as you know
more, your own style will emerge, influenced by your background,
linguistic style that you happened to think about, experiment with, etc.

PortLanTia also happens to be extremely supportive of linguistic
extension and research/experimenting.  This is not irreconcilable with
more prescription for a given purpose, such as style conventions for the
code of core Racket.  In engineering and elsewhere, we often make
practical decisions with complex tradeoffs that we know we can't fully
measure or understand, and one thing we tend to do is to limit the
scope.  Limiting scope lets us make a decision about fuzzy balances for
one class of situations, without the difficulty, costs, and risks of
making it for a much larger set of situations.  And we very often get
spreading influence behavior outside the scope for that decision,
including a decision for a different scope explicitly referencing or
delegating to that other decision, with or without modification.  But
there's no overwhelming pressure to defer to precedent, and PortLanTia's
effort into `#lang` and other features are practically begging you to
also try numerous very different things than the founders could foresee.

Hendrik Boom

unread,
Aug 17, 2018, 4:42:48 AM8/17/18
to Deren Dohoda, Racket
On Thu, Aug 16, 2018 at 07:25:42PM -0400, Deren Dohoda wrote:
> >
> > Thanks for the up-vote but let me explain the “local” rationale here and
> > vote for the ‘inner define’ variant.
> > [snip]...
> >
> >
> > In Racket programs for the world, possibly real, you want to avoid
> > rightward drift. Indenting deeper and deeper makes code appear ‘ugly’ to
> > many eyes, especially those used to other languages. But I will say this is
> > also the one point about ‘ugly’-syntax languages that I have learned to
> > appreciate (plus some concision in names).
> >
> > Internal defines are thus much more preferable than local, let, letrec,
> > and similar constructs. See the Style Guide, where I spelled this out in a
> > bit more detail.
> >
>
> In general cases I agree with this and view heavy indenting as at a minimum
> code smell but 'let' is so conceptually simple to work with syntactically.

In a Lisp-like language I once introduced a syntactic extension:
( a b c / d e f / g h i)
would always be equivalent to
(a b c ( d e f ( g h i)))

In cases where I would normally tail-nest, this reduced everything to
syntactically non-nested parentheses. / worked a lot like a semicolon
in conventional languages, but I tended to place it near the beginning of the line:
( a b c
/ d e f
/ g h i
)

This was very readable, and did a lot to make Lisp pleasant.

Unfortuntely, there aren't many leftover special characters in Scheme
that can be used for this. I had not defined / to mean division.

> If I want to extend/alter/make an analogous construction of 'let' it's
> three to twenty lines depending on what I'm doing. I have no clue how to
> start messing with internal definitions in a similar way.

My 'let' construction was simple a three-argument
( let a b c)
Iterated for a nonrecursive sequence of lets
( let a b
/ let c d
/ if c foo
/ if d bar whateverelse
)

And this fit nicely with an if-then-else chain as well

-- hendrik

>
> Deren
>
> --
> 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.
Reply all
Reply to author
Forward
0 new messages