-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conservative variadic functions #1921
Conversation
Attempts to add a proposal for variadic functions which require the minimal possible commitment, and avoids introducing new syntax.
IMO variadic generics (#376) seem like a cleaner solution, and they have the potential to solve other problems, too. |
This RFC has chosen to introduce a new type for this, rather than attempting to | ||
use tuples as has been proposed in the past (notably by [RFC #1582]). Ultimately | ||
any usage of tuples for this would require treating them as an hlist to be at | ||
all ergonomic. Rather than introducing additional magic around tuples, it makes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you seen @eddyb's implementation of tuple-based variadics? Personally, I find this approach less magic than the conversion of foo(arg1, arg2, arg3)
into foo(Cons(arg1, Cons(arg2, Cons(arg3))))
. It would also allow implementations of the Cons
and Split
traits for existing types, rather than requiring that everything variadic-like be converted to a the concrete struct Cons
and struct Nil
representation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have. I've also spent a lot of time working with code that has to work with tuples of various sizes. Ultimately these types are completely opaque to the caller, and for the callee tuples are harder to work with than an hlist is. Adding traits to make a tuple pretend to be an hlist just complicates things and is a big part of why I think previous proposals for this have fallen over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tuples are harder to work with than an hlist is
Compare the RFC example to the rough equivalent under the trait-based version:
impl<T> ToSql for T where
T: Split,
T::Head: ToSql,
T::Tail: ToSql,
{
fn to_sql(self, types: &mut [Type], out: &mut Vec<u8>) -> Result<(), Error> {
let (head, tail) = self.unpack();
head.to_sql(types, out)?;
tail.to_sql(types, out)?;
Ok(())
}
}
impl ToSql for () {
fn to_sql(self, _: &mut [Type], _: &mut Vec<u8>) -> Result<(), Error> {
Ok(())
}
}
The two seem fairly similar to me. In this case, the tuple being used is being consumed, but that can be fixed with a call to .ref_all()
by the caller. I don't think this is any more opaque to the caller than fn foo<Args: ArgBound>(args: Args)
where ArgBound
is implement for Const<...>
and Nil
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, I don't think we're 'pretending' that a tuple is an HList. A tuple is a finite, ordered, heterogeneous collection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two seem fairly similar to me.
They're not at all similar. impl<T> SomeTrait for T where T: SomeBounds
is significantly harder to work with than having a concrete type, as it causes major coherence issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sgrif That's true! When I said "fairly similar" I was referring to the ease/readability of the implementation. But you're correct, coherence does cause a number of problems in these cases (I've been stumbling on this one recently myself).
I'd prefer to resolve those coherence issues rather than designing new language features to avoid them. However, your design for variadics would provide a more immediate solution for those cases, which is a plus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, we could probably allow impl<H, ...T> Foo for (H, ...T)
, which should be nicer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IOW, while the tuple trait is an interesting design, the compiler would always need an internal representation that allows (A, ...B, C)
and it's just simpler to expose the sugary syntax to the user than simulate the correct trait behavior (when it matches and what associated types are).
The details of this proposal feel a lot like introducing new syntax, even if they technically aren't. I'm also not clear on how "variadic generics" is "out of scope", as the solution seems to involve and accept a lot of generics. Finally, the two case studies mentioned seem like they could both be solved using macros until we get real variadic generics, and in fact the diesel proposal you linked appears to be suggesting that diesel will do exactly that. So overall, I feel like I'm either misunderstanding something huge or this isn't really a minimally commital subset of what we eventually want to end up with. I do like the dedicated hlist type instead of tuples (tbh I never understood what the advantage of using tuples for variadics was supposed to be), but that seems like it might as well be part of a proper variadic generics proposal. |
Yes, that is 100% true. However, having a canonical representation in the language/standard library, and not forcing a macro invocation on our users is a huge win. (Forcing users to wrap multiple arguments in |
I could definitely get behind a push to standardize a single hlist implementation (assuming "everyone" agrees that hlists are better than tuples for doing variadics). The question then is whether a std hlist can be standardized without also settling on a design for the variadic generics feature that would eventually use it. That, I have no idea how to answer. |
I think if we're going to standardize on something, I'd rather it be something like first class variadic generics (like the |
This looks incredibly unergonomic, without some sort of generic lambdas. Maybe we get a crate that solves it, I don't know. Possible alternative: work out scoped macros, allow |
Why the constraint on the last type parameter? Won't you just get type mismatches if it is not a type parameter and/or used in other places. @camlorn What's the point of generic lambdas? |
So @cramertj has at some point written this gist which details an alternative approach to variadic generics: https://github.com/cramertj/rfcs/blob/variadic-generics/text/0000-variadic-generics.md @cramertj hope you don't mind me posting the link. =) I personally am inclined to close this particular RFC but I'd like to see @sgrif, @cramertj, @eddyb, and others (@nrc also expressed interest) pursue a revised RFC along these more generic lines. |
Yes, I'm happy to close this in favor of a more full featured RFC. I just figured I'd throw out a "minimalist" form and see what people thought, as it seemed like the variadic generics had been proposed and shot down a few times. |
@sgrif I'm growing more and more tired of the |
@arielb1 You can make everything else you want from this by implementing it as a state machine and iterating over the arguments in order. I suppose this is sort of possible now if you're willing to write everything as a separate function and give an API which allows passing a second parameter, but that's annoying. |
@camlorn In one of the demos (which I don't have handy right now), I used |
@eddyb |
@camlorn Oops, I got it wrong, it's without |
Sugar is fine, but I'd like to just have sugar too with an underlying (perhaps unstable for longer like Fn*) trait so intermediate users don't need to learn new semantics / doubt whether there is new expressive power at all. I know I, for one, immediately understood the current closures better than I had ever understood the purely-magic ones they replaced. |
@Ericson2314 One concern I have with implementing Edit: basically, I agree -- in the long run I'd like it to just be sugar. However, I don't want the feature to be blocked on type HTRBs/ATCs landing. |
Only outside of IOW, to get the same effect through desugaring you'd probably have to do this: impl<Head, Tail: Tuple> Foo for (Head, ...Tail) {...}
// could desugar to:
impl<T, Head, Tail> Foo for T
where T: Split<Head=Head, Tail=Tail>,
Tail: Cons<Head, Out=T>
{...} However, that encoding is suboptimal from an implementation point of view, and it only gets worse the more complex your tuple is, e.g. |
The one thing that I think most people can agree on is that ultimately we want hlists for this or an hlist like structure. Most proposals and examples given in the past have either been explicitly focused around hlists, or trying to treat a tuple as an hlist example example example. I do think it is important that the result be a concrete type. Having I have no issues with the idea of That isn't to say that I don't think we should have the The goal of this proposal was basically to come at the problem from a perspective of "if we know we need some kind of hlist, we could do this without affecting the type system or syntax". I figured giving that baseline (with the assumption that we may add syntax later, and affect the type system when we get to true variadic generics), but by skipping syntax and variadic generics the scope would be smaller. Diesel is looking at changing how we handle our workarounds for the lack of variadic functions, so having an idea of the direction the language will head with regards to that feature is important to us for being comfortable going 1.0. That said, there's clearly a lot of opposition to this proposal, so I'm fine with closing it if there's no further discussion to be had on the minimalist form. |
I don't understand why you assume tuples to be a bad fit. The compiler can reason about variadic tuples exactly as well as it could reason about nominal hlists in terms of coherence. |
@eddyb I thought I laid that out clearly, sorry if I didn't. The main problem with tuples lies around the 1 element and 0 element case, which are very important when writing code that works with "an arbitrary number of elements of differing types which conform to this trait". If we want to be able to say |
@sgrif |
@cramertj Ah, you're right. My mistake. |
Did you read my proposal that Niko linked above? The idea is to mark |
@cramertj Having a trait be |
@sgrif The trait can be implemented for all tuples (as it needs no associated items with the sugary syntax). |
Implementing for |
I've tried to avoid that syntax, it's the Is the worry that an impl for |
I wasn't referring to any specific example. Mostly trying to coordinate things across all examples.
No, writing those two impls should be equivalent to the hlist idea (assuming that I guess my main hesitation to tuples for this is that |
The last time variadics based on tuples was proposed, there was an enormous and iirc inconclusive tangent about tuple memory layout guarantees because in general an (a, b, c) value is not guaranteed to contain a value of type (b, c). This confused me at the time because I would've expected that the "parameter pack" (to borrow C++'s term) of a variadic generic function call is one of those things that exists only during monomorphization and vanishes entirely at codgen time/runtime, regardless of whether it's implemented as a tuple or something else. Are we now assuming that both the tuple and the hlist options would be types that "disappear during monomorphization", so we don't need to worry about their layouts? |
To clarify, it's the other way around. Other examples are
|
@Ixrec The problem was that people wanted to borrow tuple tails, i.e. get a |
No, this is part of why a separate type (which does have an explicit memory layout) comes to mind.
Do you think that there's value in simply disallowing repeats other than in the last position, at least initially? It drastically simplifies the feature to focus. |
Heh. Ironically I've wanted it the other way around in Diesel a few times. |
This isn't true IME, it only serves to make expanding the feature later tedious by having implicit limitations all over the compiler. I am even worried about a single repeat but less so because it's required sometimes. |
Even beginning to consider the cases that |
That explains a lot, thanks. I agree this sounds totally unnecessary and the Tuple::AsRef suggestion is a much better idea. |
Unfortunately, we have 28 columns, which requires Diesel's "huge-tables" support. This dramatically increases compile time of the Diesel dependency to ~3-4 minutes, up from negligible. Diesel's team says it's tracked by: - diesel-rs/diesel#747 - rust-lang/rfcs#1921 - rust-lang/rfcs#1935
Attempts to add a proposal for variadic functions which require the
minimal possible commitment, and avoids introducing new syntax.
Rendered