I think you’re implying here that a tuple keyword would reduce the confusion, right?
Isn’t this support for a keyword tuple to differentiate?
Isn’t this about whether to (keyword) name a tuple explicitly (or otherwise consider differentiation), as tuples do actually exist (everybody would agree?)
Very important distinction, imo. Along with “tuples are not containers” (-@mnemnion) - which ties back into the “files are structs” bit. So, all in all, though keywords are expensive, I’m a fan of introducing the keyword tuple.
I think the reason for my initial confusion about tuples was from as @mnemnion was saying… We have struct for structs but nothing for tuples.
Honestly I thought for a while that tuples were comptime only, or some special anonymous thing, because they were relegated to such low status (easy to forget about, no keyword, easy to skip over in the langref, didn’t have destructuring until recently…)
Have just caused confusion for me debugging compile errors for missing commas / types in struct definitions, always thinking “wtf is a tuple I’m trying to make a struct…?”
One technical difference in doing this is the impact brought by type equality.
Make two separate calls: std.debug.print("Hello {[name]s}\n", .{.name = "jeff"});
and std.debug.print("Hello {[name]s}\n", .{.name = "john"});
Here, the second argument of two different calls will be considered two independently generated temporary types (even if the field names are both name), so they will be instantiated as two functions. If a tuple is used, their types are the same, so they will be instantiated as the same function.
└─ compile exe z Debug native 1 errors
/home/pollivie/.local/share/zigup/0.16.0-dev.1976+8e091047b/files/lib/std/Io/Writer.zig:701:13: error: too few arguments
@compileError("too few arguments");
It’s the #1 reason I’d like to see them syntactically separated from the struct keyword, I want them to look like structurals (one [4]usize is the same type as another) not nominals (all containers are nominal). The use of struct for tuples is irregular in a linguistic sense. It made more sense when anonymous struct types were a thing, but that was ages ago in Zig years.
This is correct in some important dimensions, they’re both product types. But as you allude to, implementation, in Zig they behave very differently. Even in type-theoretic terms, the distinction between equality of structure and equality of identity is a load-bearing one.
I don’t think we can quite make that work, but… we won’t need it. As you say, herein lies the rub:
I think we have to say it’s of type, in today’s grammar, struct { bool, bool }. I’m going to use @Tuple(bool, bool) as the reference spelling, because I hope it catches on. @TypeOf(that) is just type, as you said here:
It does, and it’s an important property which we shouldn’t mess with.
Otherwise it’s too magic. Either: we have a type which can be destructured, has a .len field, elements which can be referenced with [i] and .@"1" and so on, and that’s unacceptable: a type, and type is a type, should not have a member which behaves radically different from every other member. Types exist, in a sense, so that that doesn’t happen.
Or, we have no way to create a tuple-of-types, and that’s bad, someone is going to want one and that’s going to be confusing. So that’s out.
But! Callback time, here’s that error:
error: type ‘type’ does not support array initialization syntax
What if it did?
const tuple_of_types = .{ bool, bool };
// Is
const t_of_t2: @Tuple(type, type) = .{ bool, bool};
// But! Is _also_
const t_of_t3: .{ type, type } = .{ bool, usize };
try expect(@TypeOf(t_of_t2) == @TypeOf(t_of_t3)); // true
try expect(@Tuple(t_of_t2) != @Tuple(t_of_t3)); // true!
// It's just a cast!
const tuple_of_bool: t_of_t2 = .{ true, false };
const t3_tuple: t_of_t3 = .{ true, 42 };
// To further the point:
const t_of_t4: @TypeOf(t_of_t2) = .{ []u8, usize };
// Because its @Tuple(type, type), that's valid
// Obviously that is weird and terrible style,
// just an illustration
It’s literally just: a tuple of type can coerce into the type of a tuple of those types, in type position (where a type alone is valid).
Maybe it could be looser: both type position and where the result-type is known to be of type type. That one needs to cook longer methinks. It’s somewhere between possible and likely that result location semantics is how Zig creates a “type position”, actually, now that I think about it. Arbitrarily complex statements which return types are supported, after all, and how else would that work?
Type coercions are only allowed when it is completely unambiguous how to get from one type to another, and the transformation is guaranteed to be safe.
I think this easily clears both of those bars, as well as some implicit values of: clarity of expression and intention, being easy to use correctly, creating no surprise in readers or writers.
So we could state the coercion without saying “type” so often, like this:
A tuple of types coerces to a tuple type when the result location is of type type.
Ok that still says “type” a lot! But is maybe clearer?
We have struct for tuples too, that’s the issue! struct { bool, bool } is how we define a tuple type, which is basically only needed for fn return values, but we really need it there, and it’s valid anywhere a type can be specified.
Keywords are expensive was jocular, it’s true, but it’s more that I don’t think we should introduce one for tuples.
For one thing, we’re about to get @Tuple, which means we can use it to unambiguously spell a type name: @Tuple(bool, bool), or if I lose the bid on allowing variadics (why though??) @Tuple(.{bool, bool}) which is noisier. But if that’s the canonical form, so be it.
test "tuple type name" {
const TupleType = struct { bool, bool };
std.debug.print("TupleType is named {s}\n", .{@typeName(TupleType)});
}
Today (0.15.2 at least) prints:
TupleType is named struct { bool, bool }
So that could be
TupleType is named @Tuple(bool, bool)
And written:
const TupleType = @Tuple(bool, bool);
But mostly we use them anonymously, as a destructure-able multiple return value type:
fn yesAndNo() .{ bool, bool } {
return .{ true, false };
// Something... like this would also work, I think
// return @Tuple(bool, bool){ true, false}; // ??
}
We just don’t need a tuple keyword, because writing tuple { bool, bool } is basically identical to writing @Tuple(bool, bool) and we can do that (syntax details ignored) starting in 0.16.
Plus! That way, when Type gets refactored to reflect the change, we can write .tuple, not .@"tuple". Which is a little nicer, you must admit.
All that makes sense; I can endorse with ease, but…
… that might actually be one remaining argument for a tuple type keyword, unless you’re suggesting we would NOT define a tuple in such a way as a return value for a fn . But, if this was the only vestige, and one only had to look at it head-tilted for a moment to realize that this was a tuple return, and not a “normal” struct, well…… ok(?) After all, it’s what we have to do today. Fine, I guess.
So when 0.16 drops, we’ll have two ways to do it no matter what else happens. Technically true now, but making a tuple with @Type is truly verbose and no one would realistically do it in-place.
I see no good reason not to elide the &.{} noise based on the signature, and permit @Tuple(bool, bool), because builtins aren’t functions, they’re just spelled like functions, and variadic builtins already exist. The literal slice syntax should also be accepted, because a slice should be accepted, but builtins are magic, there’s no principled reason not to make them arbitrarily nice to use. But it’s a small point.
It just occurred to me that it might be appropriate for me to think of this conversation as answering the question, “is a tuple a ‘type’, or is it a ‘special “type” of struct’?” Currently, we can think of it as a special type of struct, for sure. IF the keyword tuple were to be employed, it might feel like it’s more of an outright type of its own, regardless of the similarities to a struct (there are plenty of similarities between types; that doesn’t bother us). Just thinking out loud a little, in terms of how to mentally structure this.
This is more significant than first struck me too - and actually addresses my corner concern… I think all you propose makes great sense if adequately spelled out in documentation.