-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
First draft of tuples design doc #111
Conversation
var (||): e = (||); | ||
``` | ||
|
||
**Oddity:** The 0-tuple type should also be able to be written `struct {}`, but |
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.
should or can?
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 don't know yet, I've added the sentence:
We will need to resolve this contradiction somehow.
docs/design/tuples.md
Outdated
``` | ||
|
||
The basic question here is tuple enough like an array to use the same operator | ||
to access its elements? |
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 like [| |]
as it has a nice property of reminding readers what is going on
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.
Ultimately I decided this syntax is too unfamiliar, too visually noisy, and not a common enough use case to justify a separate operator.
``` | ||
// Define a function that takes keyword arguments. | ||
// Keyword arguments must be defined after positional arguments. | ||
fn f(Int: p1, Int: p2, .key1 = Int: key1, .key2 = Int: key2) { ... } |
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 kinda hate the repetition of .key1 = Int: key1
... not sure what to do about it
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 agree. I think this is one argument in favor of name: type
instead of type: name
. In the name: type
convention, you could write something like .name: type
to say "name
is keyword argument". Might have the opposite problem of being too subtle though.
|
||
We are currently making order always matter for [consistency](#order-matters), | ||
even though the implementation concerns for function calling may not require | ||
that particular constraint. |
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.
Hmm, this doesn't seem great for argument forwarding. Say you want to forward all your arguments to another function, along with an extra keyword argument .foo = bar
. If keyword arguments can be passed in any order, it just works:
SomeFunction(args..., .foo = bar);
But if we enforce ordering, that natural-looking code will break any time the user passes a keyword argument that happens to come after .foo
in SomeFunction
's declaration order.
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 agree that is a strong argument, but I'm having trouble balancing it against the consistency argument. In particular, it seems like the restrictions should be the same when constructing a struct vs. calling a function, since the struct name acts just like a function returning a value of its type, except that we will likely have less control of destruction order for the struct case.
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.
For now, I've just added your concern to the document.
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.
Sounds good!
When you added the sentence "We may need to allow keyword arguments in any order to allow use cases with unpacking", was it intended to replace the following sentence "Hmm, this doesn't seem great for argument forwarding"? The way it reads right now is kind of confusing.
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.
Sorry, I was interrupted in the middle of putting this change in and I lost track of what I was trying to do. Hopefully better now.
|
||
## Equality | ||
|
||
Tuple types don't have names, so they are compared structurally. That means two |
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.
Does this mean that two user-defined types may be comparable with one another even if neither author intended this?
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.
To get a non-structural type, you give it a name. Tuples don't have names, but they can be converted to structs that match structurally. So as long as your user-defined types are named, then they won't be comparable, but you could have a non-transitive situation like:
struct A {
var Int: x;
}
struct B {
var Int: x;
}
StaticAssert(A != B);
var A: a = (| .x = 1 |);
var B: b = (| .x = 1 |);
Assert(a == (| .x = 1 |));
Assert(b == (| .x = 1 |));
// Compile-time type error: Assert(a == b);
docs/design/tuples.md
Outdated
One thing we have considered is using a type constructor to name tuple types, so | ||
the type of `(|1, 2.0, true|)` would be `Tuple(Int, Float64, Bool)` instead of | ||
`(|Int, Float64, Bool|)`. Another way we could spell these types is by equating | ||
tuples and unnamed structs, but this would require a syntax for defining unnamed | ||
fields in structs accessed positionally. |
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've been thinking of the tuple literal (|stuff|)
syntax as a shorthand for Tuple(stuff)
. I find that way of thinking helpful, because then tuples are just like any other type, but we decided that they are common enough that they need a simpler syntax. This also gives us a benchmark for what our alternate syntax needs to be better than.
I also like the idea of unifying tuples and structs, because it means I define a struct with useful names for the fields, but still use it generically as a tuple. If we have that facility on structs, then the only thing I need at that point is a way to be able to leave a field unnamed. I seem to recall other areas in the language where we want a placeholder for things that need a name syntactically, but not semantically. Then, rather than Tuple(Int, Double, Bool)
being the base syntax we are competing against, it becomes something more like struct { Int _; Double _; Bool _; }
(or whatever syntax we decide on for struct declarations). For the value case, the "base" syntax becomes something like struct { Int _; Double _; Bool _; }(1, 2.0, true)
. In some ways, tuples then become more special, but in other ways, they are not a distinct thing because they are just a generalization of a fundamental language feature. The trick here is that the tuple types must have structural equality -- two declarations of struct { Int _; }
must actually declare the same type. I'm not entirely sure how to resolve that in a way that doesn't cause more problems elsewhere, though.
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.
Our current rule is that structs use structural equality if they don't have names. This means
struct { var Int: x; var Int: y; }
and Tuple(.x = Int, .y = Int)
are actually equal types. If we adopted your _
convention for defining positional fields in structs, then
struct { var Int: _; var Double: _; var Bool: _; }
would be equal to Tuple(Int, Double, Bool)
.
However, struct GivenAName { var Int: x; var Int: y; }
is a different type than Tuple(.x = Int, .y = Int)
, but we allow you to convert values of the latter into values of the former.
If we provide some way of defining positional fields for structs (which we | ||
haven't done so far, but would allow us to make tuples a special case of | ||
structs), then it would be convenient to access those positional field using | ||
`[|index|]` to avoid interfering with any operator `[]` you might want to define | ||
for that struct type. |
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.
This is an interesting argument.
|
||
## Multiple indices | ||
|
||
Pass tuple of indices to a tuple to get another tuple: |
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.
If I pass a tuple of indexes to an array, do I get an array back? Or do I pass an array of indexes to the array?
If we want to support this syntax, it makes sense for the tuple case to accept a tuple of indexes because then I could do x[(|2, Int|)]
to index positionally and by type.
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.
In general, I would expect you to get a foo back from passing a foo to a bar for foo, bar in { array, tuple } (with the caveat that not all combinations may be defined). I think this follows from the fact that the result should have a fixed size if the argument to [...]
has a fixed size.
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 added a brief mention to the doc below:
The rule is that the expression inside the
[...]
would determine the structure
of the resulting value.
(|x, y|) = Position(that_point); | ||
``` | ||
|
||
## Named members |
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.
This further blurs the line between tuples and structs, and makes me further wonder whether one can just be a simpler syntax for the other.
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 discuss the relationship between tuples and structs in the tuple doc, which I will update soon to reflect the fact that I've copied a bunch of content from the struct doc into this doc since it makes more sense here.
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 meant to say, this is discussed in the struct doc. I have since deleted content from the struct doc that I had moved here.
docs/design/tuples.md
Outdated
We may need to provide a way for users to explicitly specify a struct layout and | ||
an explicit (different) order of initialization for the rare structs where this | ||
matters. |
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.
If we unify tuples and structs, more advanced use cases like these can always fall back on the "regular" full struct syntax and leave the simple syntax simple.
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.
Agreed, I've changed the text.
docs/design/tuples.md
Outdated
arguments are matched by the `...`. Is it influenced by <Type>? Or the next | ||
thing in the parameter list? | ||
|
||
We shall also define a type `NTuple(Int:$$ N, Type:$$ T)` which is equivalent to |
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 don't think NTuple
is a type, because it has parameters. It's also not a parameterized type, because that would imply that NTuple(1, Int)
and (|Int|)
are different types, which would defeat the purpose. It seems more like a function (i.e. given N and T, it returns a tuple consisting of N copies of T), but presumably we don't want to open the can of worms of allowing pattern-matching to deduce the parameters of a function from its return value. My best guess is it's a parameterized type alias; after all, C++ allows deduction through aliases in precisely this way. However, that relies on the fact that there are very tight restrictions on the structure of an alias definition, and it seems doubtful that we can relax those restrictions enough to permit defining something like NTuple
. So my best guess is that NTuple
is a special alias whose definition is "magical", but that doesn't seem very satisfying.
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've added a discussion about NTuple
specifically to the pattern matching doc, though it still has more questions than answers.
Move the details re: variadics from this tuples doc to the pattern matching design doc.
Co-authored-by: mconst <[email protected]>
This is covered in more detail in | ||
[the structs design doc](https://github.com/josh11b/carbon-lang/blob/structs/docs/design/structs.md#simple-initialization-from-a-tuple). | ||
|
||
## Function calling |
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.
There's an alternative model we could have, where function arguments literally are just a tuple, rather than having explicit pack/unpack operations to convert between tuples and argument lists. That seems like it would provide some important simplifications. I know we've discussed this before and provisionally decided against it, but I have trouble recalling the reasons, so I think it's important for this proposal to explicitly discuss and justify that decision.
``` | ||
|
||
**Concern** from [mconst](https://github.com/mconst): |
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 tend to agree with @mconst on this. In any event I'd recommend deferring it to a followup proposal, in the interests of minimizing PR size, unless you think this is an important constraint on other aspects of the tuple design.
that? | ||
|
||
### Concern | ||
#### Syntax concern | ||
|
||
[geoffromer](https://github.com/geoffromer) says: |
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.
Unless I'm misunderstanding what I meant by this comment, I now think this is completely wrong, for the reasons laid out in the comment you quote below. Maybe just delete this?
[geoffromer](https://github.com/geoffromer) also brings up that it would be | ||
consistent to use the `...` unpacking operator on a tuple type in a pattern to | ||
represent variadics [slightly edited]: |
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.
[geoffromer](https://github.com/geoffromer) also brings up that it would be | |
consistent to use the `...` unpacking operator on a tuple type in a pattern to | |
represent variadics [slightly edited]: | |
[geoffromer](https://github.com/geoffromer) also brings up that it would be | |
consistent to use the postfix `...` unpacking operator on a tuple type in a pattern to | |
represent variadics (rather than a separate pattern-only prefix operator)[slightly edited]: |
@@ -562,6 +579,50 @@ that? | |||
> syntax. I don't know of any good precedents, unfortunately- `**` seems | |||
> unworkable because of ambiguity with double-dereferencing. | |||
|
|||
[geoffromer](https://github.com/geoffromer) also brings up that it would be |
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.
It might be worth linking directly to the comment, so people can get additional context. It might even make sense to only provide the link (and a quick summary), rather than quoting the whole comment.
``` | ||
|
||
The rule is that the expression inside the `[...]` would determine the structure | ||
of the resulting value. | ||
|
||
## Slicing |
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.
As with multi-indexing, I'd recommend deferring this to a followup proposal. It seems like something we don't need to resolve in the first pass, because it'll probably have relatively few interactions with other parts of the tuple design, or other parts of the language.
We triage inactive PRs and issues in order to make it easier to find active work. If this PR should remain active, please comment or remove the |
We triage inactive PRs and issues in order to make it easier to find active work. If this PR should remain active or becomes active again, please reopen it. |
Tuples in Carbon play a role in several parts of the language:
particularly for supporting varying numbers of arguments, called "variadics."