Skip to content
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

Nit: anytype is a really inconsistent name #5893

Open
SpexGuy opened this issue Jul 17, 2020 · 84 comments
Open

Nit: anytype is a really inconsistent name #5893

SpexGuy opened this issue Jul 17, 2020 · 84 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@SpexGuy
Copy link
Contributor

SpexGuy commented Jul 17, 2020

Sorry, this is kind of nitpicking, but it's been bothering me.

The value of a variable of type anyerror is some error.
The value of a variable of type anyframe is some frame.
The value of a variable of type anytype is usually not a type.

Said another way,
A variable that holds some unknown error is an anyerror.
A variable that holds some unknown frame is an anyframe.
A variable that holds some unknown type is a type.
A variable that holds some unknown value is an anytype?

var was a bad name because it was overloaded with the mutability specifier. But aside from that, IMO it was an ok name. I don't think we should restore it but I think anytype is also confusing. any might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it. infer was suggested previously, but doesn't convey that the type can change over the variable's lifetime. Maybe anyvalue, anyval, or vartype would be better?

@Sobeston
Copy link
Contributor

Sobeston commented Jul 17, 2020

I agree, and personally like anyvalue and anyval.

edit: not sure if I agree with myself anymore

@data-man
Copy link
Contributor

Important part was missed - declaration.

i: u32 // i is u32 variable
T: type // T can be type only
writer: anytype // writer is variable with any type, not unknown value

What's problem?

@ghost
Copy link

ghost commented Jul 17, 2020

1 for anyval. I think that's much better.

@timmyjose
Copy link

I agree, and personally like anyvalue and anyval.

Seconded. I found it terribly confusing when the ZIg formatter started changing my varS to anytypeS. I would have expectd anytype to really be a type, but it's apparently just the new name to var.

@timmyjose
Copy link

timmyjose commented Jul 18, 2020

Important part was missed - declaration.

i: u32 // i is u32 variable
T: type // T can be type only
writer: anytype // writer is variable with any type, not unknown value

What's problem?

@data-man Something like so:

const std = @import("std");

fn printAnyType(obj: anytype) void {
    std.debug.print("{}, {}, {}\n", .{ obj.@"foo", obj.@"bar", obj.@"baz" });
}

test "printAnyType" {
    printAnyType(.{ .foo = "Hello", .bar = 1, .baz = false });
}

where I would have assumed anytype to be a type, not a value. Instead, it is sort of like a generic object, a value. In a purely functional dependently-typed language like Idris, maybe types and values are truly equivalent, but in an imperative language, it feels jarring.

@loris-olsem
Copy link

1 for anyvalue since we have anyerror not anyerr, but anyval sounds nice too.

@rohlem
Copy link
Contributor

rohlem commented Jul 19, 2020

My opinion:
A value of anyerror is an error value. anyerror is the "supertype" of all more specialized error types.
A value of anyframe is a frame value. anyframe is the "supertype" of all more specialized frame types.
A value of the proposed anyvalue/anyval will be... a value value. Which kind of makes sense, because Zig treats everything, including types, conceptually as values. But when asking "what types of values can I expect" the answer is "value values". It doesn't seem quite as intuitive to me.

I suggest plain any. Then it's clear it's whatever you can think of - functions, types, values, whatever you can pass as an argument / bind to the name.
I think it's also clearer that any is even less specialized than anyerror and anyframe.

@AssortedFantasy
Copy link
Sponsor

AssortedFantasy commented Jul 19, 2020

I hard disagree with this.
Personally, I believe a type definition answers the question "What type is this?".

a: anyerror -> The type of a is any error.
b: anyframe -> The type of b is any frame.
c: anytype -> The type of c is any type.
d: u32 -> The type of d is u32.

Nothing is ambiguous, the only reason you have an ambiguity here is because you are omitting the "The type of" statement and transforming the statement around a bit (which is ususally fine) but for some reason you doing it incorrectly for anytype.

d: u32 -> d is a u32
But instead of this: c: anytype -> c is an any type (which is a weird thing to say, but its correct)
You're saying this c: anytype -> c is any type. (wrong, and can be interpreted wrong as saying that c itself is a type).

Additionally there is a bit of a misunderstanding conflating values themselves to types to superclasses of types.

A variable that holds some unknown value is an anytype?

A variable that hold some unknown value is just a redundant way of saying "a variable".
Up there, d is also has some unknown value, the value has nothing to do with it, you can clearly see why anyvalue is wrong below.

e: anyvalue -> The type of e is any value [ of any type?]

This suggests that [the type of] e could be a value like 2. But obviously the type of e must still be a type (hence anytype).

Anyway for more on superclasses, anyframe isn't actually a specific type, it's a superclass where any specific frame type for each function can be transformed into it. Same for errors, anyerror is a superclass where any specific error enum type can be turned into it.

I understand why all this confusion specifically happens for anytype though because Zig treats types like values. So you have to deal with strange (at first glance) statements like this.

c: antype -> c is an any type (including a type itself)

Then the misinterpretation "c is any type" isn't unambiguously wrong, because technically c could be a type.

Anyway I'm sorry if my response is long and convoluted, but please read through it again if you don't understand. I spent a long time thinking about why this was wrong but I couldn't quite put it into words. It might not be abundantly clear, but I assure you, antype is not ambiguous and is correct.

@ghost
Copy link

ghost commented Jul 20, 2020

@AssortedFantasy You seem to be putting a lot of weight on a specific translation of Zig syntax into English, and moreover on a specific syntactic ambiguity which this particular translation has. There are other ways of thinking, and many of them arrive at different places. Just because something makes sense to you doesn't mean it's the only or best possible way of thinking.

anyerror is the supertype of all errors.
anyframe is the supertype of all frames.
anytype is...the supertype of all types? Well, yes, but that's hardly a faithful description.

@timmyjose
Copy link

but I assure you, antype is not ambiguous and is correct.

This is rather dismissive and rude. Also, from the very fact that this thread exists, evidence suggests that its intent and meaning are not as clear as you're implying.

@andrewrk
Copy link
Member

Easy there, I can sense the passions in this thread escalating a bit. Let's all take a moment to remind ourselves that we have each others' best interests in mind, and try to keep the arguments focused on the technical bits. One thing to remember is that the ultimate decisions here will be based on the technical points made, and nothing else. If somebody makes a bad argument, I promise you I know it is a bad argument, you can just let it slide. Please, for my sake 😅

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jul 20, 2020
@andrewrk andrewrk added this to the 0.7.0 milestone Jul 20, 2020
@AssortedFantasy
Copy link
Sponsor

AssortedFantasy commented Jul 20, 2020

@EleanorNB @timmyjose I understand I might come off as a bit aggressive, Sorry about that and I'll try to be a bit more agreeable from now on if that makes for better debate.

I was trying to avoid the word "supertype" and "superclass" because its not really what I meant, it implies that it's somehow a different structure than a type itself, a higher level abstraction of sorts, but its not, it's just a union of types (which is still just a type).

Let's get away from shakey language and maybe I can show my same point again with pure mathematics, then I can show why I personally believe anytype is a faithful and consistent name.

In mathematics, when you're being formal, you need to specify the domain (and codomain) of your functions when you define them.

For a function f(x,y) = ln(x) y you'd need to specify something like x ∈ R and y ∈ R. I believe that Zig should try to match this.
Basically I think the little : is supposed to be an element of symbol.

So then fn foo(x: u32, y: i32) i32 means

xu32 and yi32.

anyerror is a cool thing that's supposed to be the equivalent of a mathematical union of all errors. anyframe is supposed to be a union of all frames, and lastly anytype is supposed to be a union of all types.

Basically I don't believe

fn bar(k : anytype) is ambiguous, because a union of types makes perfect sense mathematically.

ku0u1 ∪ ... ∪ i0i1 ∪ ... ∪ f64 ∪ ... ∪ struct {a: i32, b:i32} ∪ ... type

The above is correct, k actually is an element of any of those things. And it also shows why I don't like using anyvalue, In my opinion any value doesn't make sense because a type itself is just a union of values. So 2 is a value, an instance of a point struct is a value, but k is not an element of 2, and k is not an element of a instance of a point struct.

I hope maybe this makes a bit more sense and is less angry sounding.

To complete my argument from the last thing, my point about how the conflating confusion shows up is also shown in the mathematics. The very last thing at the end of the union, type happens to be a set containing everything that makes up that anytype union, including type itself. Which technically isn't mathematically sound (violates axiom of regularity) and makes the language a bit confusing, but ¯\_(ツ)_/¯, at least it's rigorous.

type = { u0, u1, ... , i0, i1, ... f64, ... ,type}

Since unions of types are still types (which are themselves sets of values) then type is a powerset of anytpe. Regardless, even with this weird recursive structure a single value by itself never appears on the left hand side (though all sets containing a single value does). So you can again see.

2anytype = {} (because 2 is not a type).

The conflatement happens becuse [in Zig] a union of all types is not completely different from a union of all values (anyvalue). Specifically the intersection is the set of all types, because types are values (but values are not types).

anytypeanyvalue = type

One way to linguistically resolve this inconsistency is to say that anyvalue is not the union of all values. But is instead a set containing all values (the set of all values would be a type).

This seemed reasonable until I realized that the only reason this makes sense grammatically is that it manages to avoid defining/mentioning the domain that you're talking about. It's the set of all things in some unmentioned universe of values. It's poorly defined, you might take it to be any value of a f128 for example, but it's not, its any value of a ... anything? It's explicitly mentioned what this universe is with the word anytype (when its taken to mean the union of all types), this universe is anything that can be an element of any Zig type.

@marler8997
Copy link
Contributor

marler8997 commented Jul 20, 2020

TLDR; I think the fact that anytype makes functions generic but anyerror and anyframe don't warrant a different syntax and/or naming scheme

I think the confusion here is caused by an inconsistency with the "any*" types.

anytype is not a type in and of itself. It is a "placeholder" for other types. Declaring an anytype parameter is what makes functions generic. The word "any" in this case refers to the type itself meaning it can be substituted with "any type".

This is in contrast with anyerror and anyframe which are actual types and do not make functions generic. They are union types which can hold "any value" from any error type or any frame type respectively. Here the word "any" means the type that can represent "any value", whereas with anytype, "any" means the type is a generic placeholder that accepts "any type".

const std = @import("std");

fn takeAnyError(x: anyerror) void {}
fn takeAnyFrame(x: anyframe) void {}
fn takeAnyType(x: anytype) void { }

pub fn main() void {
    std.debug.print("{}\n", .{&takeAnyError});
    std.debug.print("{}\n", .{&takeAnyFrame});
    // compile error because takeAnyType is generic
    //std.debug.print("{}", .{&takeAnyType});
}

At first glance, all 3 takeAny* functions appear as if they would behave similarly, but they are actually very different because only takeAnyType is generic. Regardless of the actual names, I think using a different naming convention to distinguish between generic and non-generic behavior would be more clear. Whether that means renaming anyerror/anyframe or anytype doesn't seem important but having a clear way to distinguish between them seems more so.

@ghost
Copy link

ghost commented Jul 20, 2020

That is a good point. Perhaps vartype? That communicates that the type is variable, and doesn't overload syntax.

@pixelherodev
Copy link
Contributor

pixelherodev commented Jul 20, 2020

I like vartype, but type is not variable - it's constant, we just don't know which.

fn a(b: vartype) void {
    // @TypeOf(b) is *constant*, and will never change within a function permutation
}

How about something that indicates generic type?

@SpexGuy
Copy link
Contributor Author

SpexGuy commented Jul 20, 2020

That's not correct, the type can change if the variable is not const. This code is perfectly valid:

comptime {
    const Any = struct { value: anytype };
    var a = Any{ .value = "foo" };
    a.value = 4;
    a.value = Any{ .value = 3.0 };
}

@xackus
Copy link
Contributor

xackus commented Jul 20, 2020

I like any. It has precedent in TypeScript.

@gpanders
Copy link
Contributor

I agree with what @SpexGuy said in his original comment:

any might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it.

@pixelherodev
Copy link
Contributor

How about generic, arbitrary, value, anything, etc?

@xackus
Copy link
Contributor

xackus commented Jul 21, 2020

I agree with what @SpexGuy said in his original comment:

any might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it.

In my experience error and test are much more common names than any when writing in languages that don't reserve them.
I think the benefit of having a (in my opinion) nicer keyword is far larger than the occasional inconvenience of choosing a different name.

@ghost
Copy link

ghost commented Jul 21, 2020

How about auto?

@MasterOfTheTiger
Copy link

I was about to suggest autotype.

@pixelherodev
Copy link
Contributor

pixelherodev commented Jul 22, 2020 via email

@MasterOfTheTiger
Copy link

With autotype there would not be the same confusion as with anyerror and such, since it would be the only one using "auto". Thus it "automatically" determimes the type.

@SpexGuy
Copy link
Contributor Author

SpexGuy commented Jul 22, 2020

C uses auto for type inference, where the type cannot change. That might cause confusion for people learning the language, and I don't think it really conveys that the type can change. The more I think about it, the more I like any. It's such a generic word that I think it might be ok for the language to reserve, especially since variables or functions with that name can be trivially replaced by "hasAny".

@ghost
Copy link

ghost commented Jul 23, 2020

C uses auto for type inference, where the type cannot change. That might cause confusion for people learning the language, and I don't think it really conveys that the type can change.

@SpexGuy, you keep emphasizing this point, but I'm not sure I follow. The documentation only mentions anytype for generic function arguments, which surely must remain constant after being inferred in a particular invocation. Your above example with anytype struct fields is interesting, but I couldn't find any actual use of this in the Zig code base. Is this some kind of undocumented feature? Or even intended? I kind of struggle to imagine what a dynamic Any type would even mean in a language that does not use boxed values and runtime dispatch.

@alexnask
Copy link
Contributor

alexnask commented Jul 23, 2020

@zzyxyzz
anytype as a mutable field is quite a useful feature when writing comptime code in my opinion.
The feature is undocumented but intended.
When you use an anytype field the container type becomes comptime-only.

Afaict atm this is only used in the standard library itself in some std.builtin.TypeInfo structs (e.g. for default values of fields and sentinel values).

However, it is also very useful for building up tuples.
Here is an example from ctregex:

        // args is `anytype`, will be apssed to a std.fmt function
        // We intercept u21 unicode codepoint values and encode them as utf8 
        const ArgTuple = struct {
            tuple: anytype = .{},
        };
        var arg_list = ArgTuple{};
        for (args) |arg| {
            if (@TypeOf(arg) == ?u21) {
                if (arg) |cp| {
                    arg_list.tuple = arg_list.tuple    .{ctUtf8EncodeChar(cp)};
                } else {
                    arg_list.tuple = arg_list.tuple    .{"null"};
                }
            } else if (@TypeOf(arg) == u21) {
                arg_list.tuple = arg_list.tuple    .{ctUtf8EncodeChar(arg)};
            } else {
                arg_list.tuple = arg_list.tuple    .{arg};
            }
        }

I agree with @SpexGuy here, the fact that the type of an anytype (/anyvalue/w.e.) parameter is constant is just a side effect of the fact that the parameter itself is constant.

@SpexGuy
Copy link
Contributor Author

SpexGuy commented Jul 23, 2020

I kind of struggle to imagine what a dynamic Any type would even mean in a language that does not use boxed values and runtime dispatch.

The type of an anytype cannot change at runtime. But comptime zig is an interpreted language with boxed values and type values and dynamic dispatch and everything, so the type of a variable stored in anytype changing at comptime makes just as much sense as the type of a var changing in JS. It's currently restricted to function parameters and struct members, but it could easily be extended to be allowed as the given type of a local comptime var in the future. I'm hoping this happens, because the Any struct workaround gives all the same functionality but is just more typing.

anytype fields are currently used by std.builtin.TypeInfo.Pointer to represent the sentinel. In order to robustly construct the type *<modifiers>[1]T from *<modifiers>T, for example, modifiers on the pointer need to be preserved. The maintainable approach is to use @typeInfo to get a type info for the pointer and modifiers, alter the target type of the pointer, and then use @Type to construct the altered type. But in order to do this, the type of the sentinel field must also change from @as(?T, null) to @as(?[1]T, null). That requires a struct member that can change types at comptime, so this behavior probably isn't going away.

@tauoverpi
Copy link
Sponsor Contributor

@amfogor type is a valid type that has the type type along with being the type of all types so I'd argue that changing it's name obscures more than it clarifies. It's also consistent with other languages which can work with types as values (Idris, Agda, etc).

@iacore
Copy link
Contributor

iacore commented Jan 7, 2022

Every term has a type, so a_type?

@jumpnbrownweasel
Copy link
Contributor

jumpnbrownweasel commented Jan 13, 2022

I also think that ducktype describes the semantics much more accurately and is a fairly well known concept in programming: https://en.wikipedia.org/wiki/Duck_typing

Using ducktype avoids confusion if the type is used as a duck type: its constraints are determined by how it is used in the implementation. But Zig allows even more flexibility than usual duck typing. The allowed types may include string and integer, which have no operations or characteristics in common, and the implementation may do two different things depending on the concrete type of the parameter. This is more like a union type in languages like Typescript.

Maybe the non-ducktype usage I'm describing is meant to be discouraged, in which case ducktype may be the best name. But if that sort of thing is intended, then another more general name may be better.

Edit: The (comptime T: type, xxx: T, ...) pattern is also used for duck typing. So it would be consistent to use the ducktype naming for both cases, or neither.

@batiati
Copy link
Sponsor

batiati commented May 30, 2022

What if, instead of fn foo(a: anytype) void we did fn foo(a: @TypeOf(a)) void
Being a: @TypeOf(a) not a recursive dependence, but the exact equivalent of anytype.

  • It would settle de discussion about anytype inconsistent name
  • @TypeOf(...) is clear enough to express generics.
  • It would allow more advanced use cases, such as:
// Parameter order
fn foo(a: @TypeOf(b), b: @TypeOf(b)) void { 
}

// In-place contract validations
fn foo(a: ValidateContract(@TypeOf(a))) void { 
}

fn ValidateContract(contract: type) type {
    // some meta validation here
    return contract;
}

// Type transformations
fn foo(a: TransformType(@TypeOf(a))) void { 
}

fn TransformType(arg_type: type) type {
    if (arg_type == comptime_int) {
        return i64;
    } else {
        return arg_type;
    }
}

@nektro
Copy link
Contributor

nektro commented May 30, 2022

i like #9260 as a solution to this

@ghost
Copy link

ghost commented May 30, 2022

@batiati
There was already a proposal for this idea (#6615). It has been rejected on the grounds that it doesn't really add anything that can't be done with the status quo. But now that anytype fields are gone from the language, this may allow us to remove anytype completely, which may just tip the balance for this feature. I don't know.

@jibal
Copy link

jibal commented Jun 5, 2022

any might be a good choice except that it is a common enough variable or function name that the language probably shouldn't reserve it. infer was suggested previously, but doesn't convey that the type can change over the variable's lifetime. Maybe anyvalue, anyval, or vartype would be better?

How about simply anything?

@ityonemo
Copy link
Sponsor Contributor

ityonemo commented Jun 14, 2022

A bit more work, but why not just:

fn my_function(foo: _, bar: u8) void {
  \\ code here
}

this possibility seemed to have gotten buried under a previous post that had a prefixed generic.

@jibal
Copy link

jibal commented Jun 14, 2022

Because _ is something ignored or not cared about, but that's not what anytype means ... it means that the parameter is generic. (So generic is a possibility.)

@leecannon
Copy link
Contributor

leecannon commented Jun 14, 2022

In array's it means infer the length const arr = [_]u8{1,2,3};

@Gaai
Copy link

Gaai commented Nov 12, 2022

My brainstorm came up with:
alltype
flextype
flexy
mocktype
mock
supertype
Take it, or leave it. Not saying it's super brilliant. I liked suggested any and ducktype.

@deflock
Copy link

deflock commented Feb 28, 2023

After getting some more Zig-experience anytype just blows my mind. So inconsistent.

If this cannot be solved by eliminating it and we're choosing a new name then my ordered preferences:

  • fn (x: anything)
  • fn (x: generic)
  • fn (x: any) - only on 3rd place because of TypeScript's any influence on newcomers where it's considered as a bad-practice
  • fn (x: whatever) 🤣

@nacho00112
Copy link

nacho00112 commented Jan 10, 2024

vary?

fn myFunction(x: vary, y: vary) vary {}

@jibal
Copy link

jibal commented Jan 10, 2024

@AssortedFantasy

Personally, I believe a type definition answers the question "What type is this?".

No. Types should describe values, not themselves.

e: anyvalue -> The type of e is any value [ of any type?]

This should be read as... e: anyvalue -> e can hold any value. e: anyframe -> e can hold any frame. e: anyerror -> e can hold any error.

Indeed, this cuts to the heart of it. It is clear that AssortedFantasay's reasoning, despite the time he spent on it, his assurances that it is correct, and the 29 upvotes it received, is in fact not correct, from the fact that we have anyerror and anyframe, not anyerrortype and anyframetype (nor, of course do we have u32type etc.) x: foo means that x is a foo. x: anyerror means that x is any error, or x is any sort of error, not that x is any type of error, because x is not a type at all. (The type of x is any type of error, but as you correctly point out, type designators give the type of the variable, not the type of the type designator.) Likewise x: anyframe means that x is any sort of frame (its type is any frame type, but a type designator describes the variable, not itself), and x: anything should mean that x is any sort of thing (its type is any type, but a type designator describes the variable, not itself). Again, in x: foo, foo is x's type, and x's type should only have the word "type" in it if x is a type. Thus, anytype is wrong.

Another way to put this is that a type designator says what a variable is and what type it has. So x: anyerror says that x is any error, and it has any error type. x: any says that x is anything, and it has any type. Whereas x: anytype says that x is any type (but it's generally not), and it has any type type (but there's only one type type, namely type, and that is not necessarily x's type.)

@nacho00112
Copy link

nacho00112 commented Jan 10, 2024

unset?

fn myFunction(x: unset, y: unset) unset {}

@mlugg
Copy link
Member

mlugg commented Jan 10, 2024

Please let's not just flood this issue with drive-by keyword suggestions which just vaguely match the idea. If you have a suggestion, justify why you consider it an improvement on status quo, and why it fits with Zig's existing keywords and types. Also note that, as always, your opinion will have a lot more weight if you have demonstrable practical experience using Zig.

@nacho00112
Copy link

nacho00112 commented Jan 11, 2024

I agree with you.
But the vary one has its reason, the old name of anytype was var.
about unset, I think it has potential to be a keyword, beyond that, a keyword that may be reused for another thing.
I don't like anything, because remember that what anytype is supposed to do is to allow the value to be of any type, not to be anything,
that it is anything is not more than a side effect,
so technically I think the correct name would be something like ofanytype, this way it would be less confusing
but that's ugly, at this point, I suggest to find a prettier way of saying that

@jibal
Copy link

jibal commented Jan 11, 2024

remember that what anytype is supposed to do is to allow the value to be of any type, not to be anything,

To repeat my previous comment: a type designator describes the thing it's the type of, not itself. In x: anything, x can be anything, not any type.

vary, anyarg, unset, and ofanytype are just plain bad and certainly aren't better than previous suggestions.

@nacho00112
Copy link

forget the anyarg one, it was a quick one and it doesn't even make so much sense so I edited it.
I've said myself that ofanytype is ugly, it was only so you can get the idea.
unset purpose is to be more like a keyword, and as a keyword, I believe it sounds good.
vary is based on the old name, what I'm trying is to do is to replicate it, I don't know if var was considered a bad one.
my point is that I think that saying that it's of any type describe better what the keyword do
than saying it can be anything, both ways describe the value as you say

@terraquad
Copy link

terraquad commented Aug 30, 2024

If anytype gets changed to something else, maybe we could also rename type to anytype because I believe that "type" is a common variable/field name (at least for me).

@InKryption
Copy link
Contributor

You can already use type as a field name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests