Pre-RFC: Variadic Generics

(small note, member of wg-grammar, words are mine, not the group's, etc)

There is some intention of merging type ascription into the pattern syntax. Even if we semantically require type ascription at the top level of the pattern, I expect this grammar simplification will likely eventually be accepted/implemented.

The current function argument grammar as defined by wg-grammar is

FnArg =
  | SelfValue:{ mutable:"mut"? "self" }
  | SelfRef:{ "&" lt:LIFETIME? mutable:"mut"? "self" }
  | Regular:FnSigInput
  ;
FnSigInput = { pat:Pat ":" }? ty:Type;

This sounds like parameter packs are substituted with the concrete elements every time the containing function, type, trait or implementation is used, like macros that are expanded every time they are used. However, I don't think that this can work, because functions have to be type-checked even if they aren't called anywhere. So the concrete semantics of parameter packs must be specified for each and every possible context where they can appear, and the parser has to be taught how to parse them in each context.

Take this example from the RFC:

fn zip<(A @ ..), (B @ ..)>((a: A) @ .., (b: B) @ ..)
    -> ((A, B) @ ..)
{
    ((a, b) @ ..)
}

To parse this, the grammar of type and lifetime parameters must be extended to allow parameter packs and parentheses around parameters; the grammar of function parameters, tuple types and tuple expressions must be extended as well. And so on.

I read this, but I was confused by the zip example which violates this rule. The reference-level explanation should explain how many parameter pack expansions are allowed in each context.

2 Likes

I now uploaded an updated version of my proposal where I changed the following:

  • I moved fold expressions to the future possibilities sections. (It's a to-do right now but the idea would be macros foldr! and foldl!)
  • I've added the SameArityAs trait and added the HasArity trait to future possibilities.
  • I've fixed the mistake in the zip example mentioned by @Aloso
  • I worked on writing a more complete grammer. Most notably, I've changed the proposed syntax for function arguments from (a: A)@.. to a: A@... This way, the grammer for function arguments does not have to be changed.
  • I've added an initial idea of how this could be type-checked.
  • I've removed the notion of parameter packs in favor of "collecting" arguments into tuples. Most notably this simplifies
fn zip<(A@..), (B@..)>((a@..): (A@..), (b@..): (B@..)) -> ((A, B)@..)
    where (A@..): SameArityAs<(B@..)>

to

fn zip<(A@..), (B@..)>(a: A, b: B) -> repeat!{(A, B)}
    where A: SameArityAs<B>

This also includes the aforementioned repeat! macro for which I'd propose a short-hand syntax repeat!{...} and a more explicit syntax repeat!(for a, b in A, B {...}).

There are still a lot of to-dos in the document but I think it's best at this point to first have your opinion on the general direction before I continue working on them. So again, any feedback is very much appreciated!

1 Like

toying out of my league...

  • can this all be done via unstable macros?

  • for example tuple!, for!, zip!

    fn zip<tuple!(A), tuple!(B)>(at: A, bt: B)  -> zip!(A, B) {
        (
            for! (a, b) in zip!(at, bt) {
                (a, b),
            }
        )
    }
    

Here it's assumed zip!(A, B) implicitly adds some sort of where to ensure A and B are of same length

Well this could all be done via macros, but they would need to be built-in macros anyways, because they have to be type-checked. Therefore, I don't see any reason not to provide a proper syntax. Variadic tuples should be a special kind of tuple. So a tuple-like syntax seems appropriate.

Also, I'm still not sure if something like fn foo<(A@..), (B@..)>((a, b): repeat!{(A, B)}@..) should really be allowed. Macros usually generate code, but in this instance the repeat macro matches the function parameters when the function is called.

1 Like
  • the idea was to have marcos expand to some syntax but not specify which one initially
  • to me for! + zip! (if workable at all) seem more flexible and easier to read than repeat!

So if I understand correctly you would eventually also want to replace this macro by an actual syntax anyways?

That's kind of a matter of taste. I do think that repeat! is easier and probably less confusing to read than for!, especially because for already has a meaning and implies that some kind of iteration is happening. We do not iterate a tuple, we repeat a given expression for each element in a tuple, with lead to the syntax repeat!(for a, b in A, B {(a, b)}) for which you can naturally define the short-hand repeat!({A, B}). As this also contains a for-like construct and allows to zip you would have to give me an example of how your idea is more flexible.

yes

it is more flexible but perhaps difficult to implement: these macros got to be expanded after monomorphisation but type-checked before

re flexibilty: (for! a in A {for! b in B { (a, b), }})

Then the goal should be to directly find a proper syntax. As @scottmcm mentioned this won't be implemented in the near future anyways. So there's plenty of time to work on this.

This roughly translates to repeat!(for a in A {repeat!(for b in B {(a, b)})@..})

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.