
Racket is really missing a form of cond
that reduces rightward shift and parenthesis. I know several people have designed some variants of cond over the years to do that. Here’s a very simple one that fits my needs almost perfectly: (define-syntax (begin/cond stx)
(syntax-parse stx
[(_ body:expr ...
(~seq #:cond [test test-body ...] ...)
(~seq #:else else-body ...))
#'(begin
body ...
(cond [test test-body ...]
...
[else
(begin/cond else-body ...)]))]
[(_ body:expr ...)
#'(begin body ...)]))
Example with default racket: (displayln "start")
(define y 2)
(cond
[(> y 3) 'y>3]
[(not 5) 'well_nope]
[else
(displayln "do")
(define x (+ y 2))
(cond
[(> x 3) 'x>3]
[else
(define z (+ x y))
(cond
[(< z 10) 'z<10]
[else 'other])])])
Same with begin/cond: (begin/cond
(displayln "start")
(define y 2)
#:cond
[(> y 3) 'y>3]
[(not 5) 'well_nope]
#:else
(displayln "do")
(define x (+ y 2))
#:cond
[(> x 3) 'x>3]
#:else
(define z (+ x y))
#:cond
[(< z 10) => values]
#:else
'other)
I also find it to be more readable.
What’s your favorite variant of cond? Do you like this one (why/why not)?

For that example, my favorite flavor or cond
would be if
, thanks to Parendown:
; `w-` is `let` with fewer parens
(require (only-in lathe-comforts w-))
(begin (displayln "start")
#/w- y 2
#/if (> y 3) 'y>3
#/if (not 5) 'well_nope
#/begin (displayln "do")
#/w- x (+ y 2)
#/if (> x 3) 'x>3
#/w- z (+ x y)
#/if (< z 10) 'z<10
'other)
More broadly, I also tend to find I use pattern matching a lot more than I use if
.

While the rightward shift is clearly a problem, I think I prefer the standard cond
because it’s syntax is simpler. Also, I think that rightward shift can often be contained by factoring stuff into smaller functions. Now, you’ll tell me that the “simpler syntax” argument is a matter of habit, and you’ll be right :shrug:

And indeed, pattern matching often cuts it perfectly for me, both in expressivity and in reducing the rightward shift

For a variant of cond I think we could take inspiration from Alexis King’s do
notation from data/monad
. It allows a similar mixing of “do clauses” with normal definitions and expressions, just like we want here with cond
. However being based on Haskell do
notation it uses an infix <-
symbol in every “do clause”: > (do [x <- (just 7)]
(define (calls-b)
(add1 (b)))
(define (b)
(- x))
[y <- (just (calls-b))]
(pure (* 2 y)))
(just -12)
For the cond version, an extra infix symbol could be =>
or ->
or then
or :
: (cond
(displayln "start")
(define y 2)
[(> y 3) => 'y>3]
[(not 5) => 'well_nope]
(displayln "do")
(define x (+ y 2))
[(> x 3) => 'x>3]
(define z (+ x y))
[(< z 10) => => values]
[else => 'other])
Or perhaps since Racket isn’t Haskell a prefix-style distinguishing symbol would work better, like if
: (cond
(displayln "start")
(define y 2)
[if (> y 3) 'y>3]
[if (not 5) 'well_nope]
(displayln "do")
(define x (+ y 2))
[if (> x 3) 'x>3]
(define z (+ x y))
[if (< z 10) => values]
[else 'other])

Would be nice to get rid of the last else
and just write 'other
.

For my personal project, I use this macro: https://github.com/sorawee/my-utils/blob/master/cond.rkt#L7

Usage:
(cond
[x 1]
[#:let y 2]
[(foo? y) 3]
[(bar? y) 4]
[#:let z 5]
[else (+ y z)])

It wouldn’t directly support that (displayln "do")

Though it’s technically possible: [#:let _ (displayln "do")]

Another possibility is taking inspiration from syntax-parse
, which allows #:do [def-or-expr ...]
in between pattern-directives. With cond
that would look like: (cond
#:do [(displayln "start")
(define y 2)]
[(> y 3) 'y>3]
[(not 5) 'well_nope]
#:do [(displayln "do")
(define x (+ y 2))]
[(> x 3) 'x>3]
#:do [(define z (+ x y))]
[(< z 10) => values]
[else 'other])

And… I discovered that my macro also supports #:do
lol

I just forgot that it exists.

Though the syntax is slightly different

One advantage of let
is that it’s like let*

You can keep shadowing the same id over and over again

That’s not possible with #:do [(define ...)]

Personally I prefer shadowing to have an parenthesized scope

I dunno. I found myself using the “update” idiom a lot. Like:
(define (foo x)
(define normalized-x (normalize x))
(define enhanced-x (enhance normalized-x)
(define updated-x (update enhanced-x))
(f updated-x)))
or equivalently:
(define (foo x)
(let* ([x (normalize x)]
[x (enhance x)]
[x (update x)])
(f x)))
It would be really nice to be able to write this instead:
(define (foo x)
(define* x (normalize x))
(define* x (enhance x))
(define* x (update x)
(f x)))

For this I use (with [it x] (normalize it) (enhance it) (update it) (f it))

(just do it)

Or (~> x normalize engance update f)
using the threading
package

yeah, but often the argument is not in the first place and then it becomes less clean

Then the _
hole marker in threading becomes useful. (~> x (expt 2 _) add1 (range _ 0 -1) (map log _))

That’s what I mean by ‘less clean’. Not hygienic, and there are some parenthesized expressions mixed with procedures. Also the notation is too hacky for my taste.

@hoshom has joined the channel

Oh boy, do I have Opinions here

Just to get this out of the way first: the hole marker should be required when threading, always.


This is great!

Drive by comment: the parser-entry-input/c
contract is a little confusing to me. Can it be simplified? For instance, can it be just (sequence/c char?)
?

I see what you mean. Given the spec’s requirements it might need to be (sequence/c (or/c token? char?))
.

I’m having second thoughts about the package and collection names. css3
as a collection name is a misnomer since the numerical levels don’t refer to CSS as a whole, but as the “level” of a specification module defined by the W3C. Since the css
package name is already taken, I have to think more about what else to call it. css-syntax3
might be appropriate for a collection name, though.