[racket] Combining iteration and match

From: J. Ian Johnson (ianj at ccs.neu.edu)
Date: Tue Sep 3 10:54:50 EDT 2013

Asumu's suggestion is what I would use, since generic-bindings are currently unstable and not as performant as the idiomatic manual solutions.
A difficult idiom to encode, however, is when you want to skip elements of the sequence that don't match, since you end up needing define forms between for-clauses, as well as some "doesn't match" value that you give to all would-be defined identifiers, and add #:unless (eq? first-match-id doesnt-match). Not only can you not even do that, getting it to work in the current for-forms would require extra allocation (say, putting all matching identifiers in a list, or #f if no match) and additional nesting of for forms, which ruins the convenience of the for/X macros. We can't express [(list a b) (in-list list-of-2lists)] as a special form that uses :do-in, since match shouldn't have to know how in-list decomposes its sequence.

Instead, I would propose an extension of the for macro collection itself, such that the built looping structure is generalized to something like

(let-outer-form ([outer-pattern outer-expr] ...)
  (let-pre-loop-form ([pre-loop-pattern pre-loop-expr] ...)
    (let-loop-form loop ([loop-pattern loop-expr] ...)
      (if pos-guard
        (let-inner-form ([inner-pattern inner-expr] ...)
          (if pre-guard
              (let body-bindings
                 (if post-guard
                     (let-reloop-form ([reloop-pattern reloop-expr] ...)
                       (loop loop-arg ...))

Notice the new pre-loop and reloop binding opportunities. I've found myself wanting to compute some values that would otherwise have to be recomputed or hackily stored somewhere in order to use in loop-expr ... and loop-arg ...

for has the following instantiation
[let-outer-form let-values]
[(outer-pattern ...) ((outer-id ...) ...)]
[let-pre-loop-form let]
[(pre-loop-pattern ...) ()]
[(pre-loop-expr ...) ()]
[let-inner-form let-values]
[(inner-pattern ...) ((inner-id ...) ...)]
[let-reloop-form let]
[(reloop-pattern ...) ()]
[(reloop-expr ...) ()]

Now I say "something like" since this looping structure is built for the entire collection of for-clauses, and not just one. Thus, there needs to be a sensible protocol for special clause forms to be combined in the different generalized forms. We could have our own outer-form/pre-loop-form/let-loop-form/let-inner-form/let-body-form/let-reloop-form implementations such that this protocol is implementable in the generic binding library. The protocol I have in mind is that clauses with pattern (id ...) get collected into one let-values, whereas adjacent forms defined in a generic-binding way can choose to either be cascaded in a chosen form, or collected into a chosen form. Additionally, since we want the ability to say that non-matching isn't a failure, but a signal to continue, there is more we need to be able to do with the for forms; indeed, the above template lies a bit, since it does not mention the possibilities of #:when, #:unless, #:break or #:final, where the first two actually call loop with all the same accumulators, but with the sequence "stepped forward one." This changes the format of the let-X-form setup, since you would instead have a successful branch and a failing branch (consider a match fall-through clause either raising an error or calling the loop on the same accumulators with the stepped sequence(s)).

This kind of overhaul is not something we can do externally to one of the most used, non-trivial collection of macros in the Racket code base. We also have to use a more conservative language to implement it, since it's so low on the language tower. Perhaps later this year I'll have time to submit a pull-request that opens the for macros up a bit more in this fashion, but I anticipate a heavy push-back from core developers due to its supreme importance to Racket's foundation.


----- Original Message -----
From: "Asumu Takikawa" <asumu at ccs.neu.edu>
To: "Konrad Hinsen" <konrad.hinsen at fastmail.net>
Cc: users at racket-lang.org
Sent: Tuesday, September 3, 2013 9:12:54 AM GMT -05:00 US/Canada Eastern
Subject: Re: [racket] Combining iteration and match

On 2013-09-03 11:32:23 +0200, Konrad Hinsen wrote:
>   (for/list ([(list a b) some-sequence])
>     a)

I usually use `match-define`:

  (for/list ([a+b some-sequence])
    (match-define (list a b) a+b)

Can be slightly longer than just `match` for simple cases, but doesn't
cause rightward drift.

  Racket Users list:

Posted on the users mailing list.