-
-
Notifications
You must be signed in to change notification settings - Fork 2.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
Proposal: expose tuple type declarations #4335
Comments
Another usecase for declared tuples: emulating multiple returns. |
Do you foresee another |
|
Pondering it some more.. Hate to bikeshed, truly do usually, but would propose an alternative to "yet another keyword": extend the lang syntax such that What bugs me about a new keyword? One subtle quality (among various) I liked about Zig is that there is kinda mostly / overall / in-spirit one moniker for one underlying-mechanism/operational-semantic, so to speak. Now you'd have to explain in docs/chats/blogs/teaching-materials/talks at length "there are unions, they are like this, then there are structs, they are like that, and btw but there's also another special kind of structs without field names, so there are in addition to unions and structs also tuples but they're really structs under the hood". PLT folks love the "tuple" moniker but it was always understood that structs/records/classes/tuples are really all primarily one thing, product types. If a |
@metaleap 's ammendum #4335 (comment) is also accepted |
I don't understand what is being proposed. Is it a special way to declare struct without specifying field names? What is a packed tuple, i can't imagine that, there are no examples to visualise it. As for use case "emulating multiple returns": i would expect multiple returns to be named, as arguments to a function must have names. e.g: Also would anon list literals be coerceable to 'tuples'? @LemonBoy is saying that it should not be allowed #4148 (comment). |
Nor you have understood what I was saying as I was talking about coercing anonymous array literals to named struct literals where you really want to specify what field are you assigning a value to. |
@LemonBoy your argument that it is a time-bomb does not justify your point, however i understand why it would not be allowed - "favour reading code over writing code" Edit: how do 'tuples' fit into this zen? |
@Rocknest understand this: we already have tuple literals: The proposal is to have such a facility in the grammar. |
@metaleap as i said #4335 (comment) i wonder what the use case would be, is that really justified? |
I feel like a separate key word may cause more confusion than it's worth - new users would likely see it and assume it's a different data structure. I like the idea of making a struct type without specifying field names though ie stuct{u32, []u8}. Nice for function return types. +1 |
@Rocknest from my experience in langs that have them (Haskell, C#), unnamed-field "tuples" rarely make it into a somewhat-matured final "production-grade" code product to any great extent. But IMHO they're invaluable just for more-rapidly-iterating alone already, as you restructure fn signatures quickly and re-mold your data/flow structurings at an ideally fast pace. That alone is a valid programmer use-case, making the facility fit for every programming language =) Commonly seen is also the desire for what's hi-falutingly described as "heterogenous look-and-feel-like arrays" say Vec3[0] through Vec3[2] get accepted by compiler / type-checker, turning into field-accesses, even going as far as allowing loops. I won't argue with that, though in my practice it doesn't really come up. |
@Rocknest here's a real-ish example that I've encountered building my wasm interpreter: var stack: [65536]u8;
pub fn i32Store(self: *core.Instance, pop: packed tuple{ u32, i32 }) void {}
pub fn i64Store(self: *core.Instance, pop: packed tuple{ u32, i64 }) void {} A packed tuple is able to represent this data directly mapped inside of the memory space. Without packing, I'd have to copy the data out (which is fine but a little inconvenient), and without tuples I'd have to emulate it with struct fields (which is fine but I'd have to invent some incrementing name and have adhoc tuples). We already have tuples in the language because it was needed to support varargs. This proposal simply gives it a name and a way to declare the type. |
And here i am reading this code having no idea what first and second integers represent. Are you sure they do not need names? |
The shared invocation context only knows the position and types. Inside the function, we can apply conceptual names, but exposing it means I cannot share the parsing logic (which is based solely in bytes). Although thinking about it, I can possibly rely on a packed struct and iterating through the fields at comptime to generate the parser... |
I feel like a built-in function would be the best way to define tuple types. Tuples are just a special case of structs after all, so special syntax might be a bit redundant. const MyTuple = @TupleType(u32, i32);
const MyTuple2 = @TupleType(u64,u32,bool);
const MyPackedTuple = @PackedTupleType(u32,i32);
const x : MyTuple = .{32, -5};
const y : MyTuple2 = .{1,2,false}; |
Another crazy idea with tuples — we can sorta create generic structs at comptime: pub fn Soa(comptime Struct: type) type {
const fields = std.meta.fields(Struct);
const Data = switch (fields.len) {
1 => struct { []fields[0].field_type },
2 => struct { []fields[0].field_type, []fields[1].field_type },
3 => struct { []fields[0].field_type, []fields[1].field_type, []fields[2].field_type },
else => @compileError("TODO"),
}
return struct {
data: Data,
allocator: *std.mem.Allocator,
pub fn initCapacity(allocator: *std, cap: usize) !Self {
var result = Self{ .data = undefined, .allocator = allocator };
inline for (fields) |field, i| {
// This type of generic access is currently inaccessible
// since we can't generate field names
self.data[i] = try allocator.alloc(field.field_type, cap);
errdefer allocator.free(self.data[i]);
}
return result;
}
} |
@fengb is this what you are trying to achieve? |
I like the separate keyword because it makes the intent more clear but am fine with either. |
I've never seen really good use cases for tuples to be honest. In C++ they're super confusing, especially the way they're used in the standard library. In Python it's always tricky because you have to remember what goes where as it doesn't check anything for you. And then you want to print it because you have a bug somewhere and you're better of using As for multiple return values. I'm also not too fond to use tuples for that. We don't have tuples for multiple input values either, do we? If Zig wants multiple return values (and I do think that could be useful) then imho it would be better to name them (maybe name all return values?) and somehow refer to them inside the function. |
Tuples were already added to replace vararg inputs. This proposal only exposes the ability to name the type so you can declare constrained tuples instead of being always generic. |
But zig's 'tuples' are really just annonymous structs. This proposal goes in somewhat opposite direction. |
An unintended consequence of the currently accepted syntax is that it creates an element of ambiguity. |
@ominitay there is no ambiguity in the sense of ambiguity in a formal grammar. In fact, the formal zig grammar here already accepts both |
Yes, but then what type does |
Again, I'm not saying that the formal grammar is ambiguous. I'm saying that what Edit: amend typo |
That's not a grammar ambiguity, it's orthogonal to the grammar. If you're curious what an ambiguity in the grammar would look like see this page: https://en.wikipedia.org/wiki/Ambiguous_grammar. Note that a Parsing Expression Grammar like Zig's here is by definition unambiguous: https://github.com/ziglang/zig-spec/blob/master/grammar/grammar.y. What you are talking about is ambiguity later in the compiler pipeline. A similar ambiguity already exists with regards to the type of My take is that it doesn't really matter whether |
Oop, I missed the very important word "not" between the words "I'm" and "saying" in the first sentence, completely changing the meaning. I'm completely agreed with you in that regard.
Sure, that is why I'm asking for clarification here. If we are sticking with the shared keyword approach, then we need to decide whether |
I agree that tuples just being a special case of structs, and |
You can't have it be two types at once. Empty structs don't coerce to empty tuples either; this might be a non-issue, but if it is a problem, then this may need to be implemented. |
Tuples already are structs, they just have a true bool in |
We are in agreement on this :) |
An alternative for this which doesn't require new syntax would be to allow tuples with all elements being type test {
const T: type = .{ type, u32 };
const t: T = .{ bool, 2 };
} |
@Vexu How would you then suggest we express a packed or extern tuple type then? And how would we express field alignment? |
If you think that |
That was a typo, I meant using
We already have multiple ways to create types, why does this new thing need to be able to all the same things as
You mentioned it.
I didn't know that, when did that happen? |
We can already everything your proposal can through I forgot to mention that your solution would make it impossible for tuples to be namespaces, which is a feature that Andrew has agreed is intended, and makes sense considering enums and structs can. Additionally, I don't see your proposal fitting in with existing precedent. Tuples are effectively structs, so why should they be declared any differently? To me, it doesn't really seem like it fits in with the language as it is today.
My point here was that this is a proposal — we are considering things that don't exist.
I remember Andrew talking about it on Discord at some point, and @fengb did include them as part of the proposal. |
always could using |
After #4335, Can users use tuple to return multiple values from a single function now? |
This has always been possible simply by returning a struct from a function. This isn't much different now except you no longer have to specify field names: const std = @import("std");
fn x() struct { u8, u16 } {
return .{ 5, 10 };
}
pub fn main() void {
const thing = x();
std.debug.print("{}, {}\n", .{thing.@"0", thing.@"1"});
} I wouldn't always recommend it as it can decrease readability (what is Edit: there are a lot of people subscribed to this issue so it may be best to continue this discussion elsewhere. |
Is the performance overhead of doing this bigger than using tuple? |
Just to note, this can also be written as |
We now have anonymous tuples, but they can only be created at runtime using the
var
type. While this satisfies "varargs" and coercion to arrays / vectors, we still cannot define a tuple type at comptime.Potential syntax:
Packed tuples should also be possible since it can represent in memory data structures pretty well, like manually managed stacks.
Accepted syntax
Per #4335 (comment):
The text was updated successfully, but these errors were encountered: