ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.1k stars 2.49k forks source link

Nit: anytype is a really inconsistent name #5893

Open SpexGuy opened 4 years ago

SpexGuy commented 4 years ago

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 commented 4 years ago

I agree, and personally like anyvalue and anyval.

edit: not sure if I agree with myself anymore

data-man commented 4 years ago

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 commented 4 years ago

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

timmyjose commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

rohlem commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 :sweat_smile:

AssortedFantasy commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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

pixelherodev commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

I like any. It has precedent in TypeScript.

gpanders commented 4 years ago

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 commented 4 years ago

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

xackus commented 4 years ago

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 commented 4 years ago

How about auto?

MasterOfTheTiger commented 4 years ago

I was about to suggest autotype.

pixelherodev commented 4 years ago

I like autotype, I think it's the best option I've heard yet.

I might prefer auto over autotype, since it doesn't imply that the value is of type type, but that might just be me.

MasterOfTheTiger commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

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.

ghost commented 4 years ago

@alexnask, @SpexGuy Thanks for the explanations. Makes sense.

I would actually argue that this makes the two uses of anytype quite distinct: One for creating generic functions and one for representing comptime mutable types. That both use the same keyword is economical, but confusing. Maybe the two uses should simply be split, with auto indicating a generic parameter and any or anyval or whatever standing for a comptime dynamic type. Of course that would reserve two short keywords...

kenaryn commented 4 years ago

I propose anyitem as item is in English language more holistic than object. It conveys the expression of a very generic concept like an idea, a person, a place or even a physical or conceptual object.

As for me, anyitem may be adequate to signify any logical concept as abstact as a value of a value.

Rocknest commented 4 years ago

Anytype looks a bit misleading to me. Lets look if the type system is consistent:

Lets see what TypeOf will produce:

pub fn abc(a: anyerror, b: anyframe, comptime c: type, d: anytype) void {
  @compileLog(@TypeOf(a), @TypeOf(b), @TypeOf(c), @TypeOf(d));
}
comptime { 
  abc(undefined, undefined, i32, @as(i32, 5));
}

| anyerror, anyframe, type, i32 <source>:2:3: error: found compile log statement

@TypeOf(a) == anyerror; @TypeOf(b) == anyframe; @TypeOf(c) == type; @TypeOf(d) != ~anytype~
//@TypeOf(anytype);
//error: expected token ')', found 'anytype'

As you can see that symmetry does not hold. I believe that this type/keyword should not be prefixed with any. The type of types may be renamed to anytype, that would allow to create fields and variables named type using the simple syntax.

RaphiJS6-13 commented 3 years ago

What about expr or expression?

RaphiJS6-13 commented 3 years ago

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.

There's a workaround for the anytype restriction that mentioned:

const Any = @typeInfo(struct { a: anytype }).Struct.fields[0].field_type;
vkcz commented 3 years ago

I'm in favor of (should this be changed) anything: it's consistent with any... type-category names, and coincides with a nicely relevant existing English word, but doesn't give any suggestion that the value is related to the type type.

marler8997 commented 3 years ago

A couple more ideas I thought of after reading people's comments:

fn foo(x : defer) void {
}
fn foo(x: placeholder) void {
}
pixelherodev commented 3 years ago

I would avoid defer, since it's already used for deferred execution.

ghost commented 3 years ago

Since we are discussing generic function arguments, why not just say so:

fn foo(x: generic, y: @TypeOf(x)) {
    ...
}

I think this was already suggested by @pixelherodev, but didn't gain traction.

generic also seems like a fair description of the other use-case mentioned in the thread -- comptime-typed struct fields and variables:

const ArgTuple = struct {
    tuple: generic = .{},
}
// ... See @alexnask's comment above for rest of example.
ikskuh commented 3 years ago

If we're actually going to change it, I am strongly for generic as this is self-explanatory and explains what it does

mikdusan commented 3 years ago
image

My vote: first choice any, second anything, third anyval

Mouvedia commented 3 years ago

I agree with @vkcz, anything works:

anyframe
anyerror
anything

But if the terms expression or value are more idiomatic in zig I would be fine with anyexpr/anyexpression or anyval/anyvalue.

ghost commented 3 years ago

Because I currently have important work to procrastinate on, I have carefully read through the entire thread and attempted a summary. My conclusion is that anytype is actually not too bad, but if we do decide to change it, then we should go for something that specifically emphasizes the generic instantiation behavior, such as auto, infer or generic. For the reasoning behind this conclusion, read on.

Prelude

I'd like to draw attention to the fact that anytype has two quite distinct uses:

  1. Generic functions with inferred argument type. This is the main use case.
  2. Comptime dynamic struct fields. Since comptime implements semantics resembling a dynamic scripting language, a struct field declared as anytype can change its type during comptime execution. This can be used, for example, to build up tuples programmatically. In the future, comptime dynamic variables might be allowed too. While not unimportant, this use case is both advanced and rare.

From a certain technical perspective, the two cases can be regarded as two sides of the same coin: Both have dynamic variables with the type inferred from the supplied value. In that view, the inferred type of a generic function argument is actually dynamic, and acquires its constness only as a side effect of function arguments being constant in Zig. While technically correct, I find this view mostly unproductive. In practice, the difference between the two use cases is almost as big as the difference between languages with static and dynamic typing. At least in my head, they occupy quite distinct mental niches, although I can't vouch for the heads of other developers.

Because of this, using the same keyword in both cases will always feel kind of unsatisfactory. The semantics of either one or the other will be described poorly. One way out of this would be to simply use different keywords, but for reasons of keyword economy and the fact that the two are indeed somewhat related, I would say this is overkill. However, the inherent conflict between the two uses should be acknoledged and preferably resolved in favor of generic functions. This case is not only much more common, but also much more likely to be encountered by newcomers, so descriptiveness and clarity are important here. I would find it acceptable if the much more expert-level case of dynamic comptime fields suffers a bit of poor aesthetics in return.

Type Theory

With that out of the way, let's dig in. For the original motivation of this issue, I'll quote @SpexGuy:

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?

As pointed out by @AssortedFantasy, there seems to be a subtle error of reasoning in that last line. "A variable that holds some unknown value" doesn't actually say anything. It is the purpose of variables to hold unknown values. Of course, the range of these values may be constrained by the type of the variable to various extents:

In short, anytype is actually the appropriate term to describe the type of a variable that may hold anything. any would do equally well.

Generics

Despite this, I'm actually in favor of changing it to something else. The above reasoning is more appropriate to case (2) mentioned in the prelude, that is, actual dynamically typed variables. But as argued before, case (1) is more important and anytype (and especially any) strongly suggests that you are dealing with a variable with unconstrained range. Instead, what happens is that you get a variable of a very specific and constant type in a separate function instantiation. This was pointed out by @marler8997: neither anyerror nor anyframe instantiate new functions, but anytype does.

For the above reasons, I think that all anyX proposals are actually solving the wrong problem. It is the any in anytype that we should take issue with, not the type. This would rule out anyitem, anything, anyval, anyvalue. In my opinion, all of these are strictly inferior to anytype or any.

The proposals that I would recommend for consideration are those that express the generic function instantiation semantics directly. These include, in order of my personal preference, auto, inferand generic. I'm less fond of autotype, and defer and defertype I find positively confusing in this context.

To whom it may concern: Thanks for reading!

ghost commented 3 years ago

Addendum

Though I personally don't have an issue with this, it can be argued that type in anytype is

  1. redundant, because it being a type is already implied by its location on the right of the colon.
  2. misleading, because Zig can have values of type type, which may suggest that anytype constrains the variable to holding type values of any kind.

Both of this is true, but the only correct solution in that case is any, which is in fact what most languages use to describe variables that can hold values of all types. Going for anyvalue or anyitem would be even even more redundant an misleading.

With that said, I think the difference between anytype and any is not that dramatic, and probably not worth changing. If we seriously contemplate a change, it should favor the "generics" interpretation, as argued in my above comment.

Rocknest commented 3 years ago

There is a universal 'don't care' syntax in zig - the underscore.

fn foo(x: _) void {
    // i don't care what the type is, just give me the value. 
}
haze commented 3 years ago

maybe we could use bruh with a similar sentiment as usingnamespace

fn foo(x: <bruh>) void {
    // i don't care what the type is, just give me the value. 
}
mikdusan commented 3 years ago

it's emoticon time (_:_)

ghost commented 3 years ago

A couple more ideas I thought of after reading people's comments:

  • defer or deferred: rather than declaring a type, the function is choosing to "defer" that responsibility to something else
fn foo(x : defer) void {
}

In that case...

if (defer == defer) {
    defer _ = defer;
} else defer switch (defer) {
    else => defer == defer
};
Mouvedia commented 3 years ago

After reading the comments a second time, I am leaning towards 2 proposals:

Personally I still like anything because it's consistent with the existing anyerror and anyframe and the upcoming anyopaque.

Zooce commented 3 years ago

Disclaimer: I'm rather new to this language -- been watching it for a while but haven't yet put it into much practice, until now. So I figured that maybe a newcomer's perspective might be useful here...maybe not...?

Today I was trying to learn the difference between x: anytype and comptime T: type, x: T and the nice folks over at zigforum.org helped get me through it.

I read the comments in this issue in their entirety, and there are some great ideas here. My takeaway is that anytype, although not necessarily incorrect, is somewhat inconsistent with the other any* keywords. As a newcomer to the language, I think it would be much easier to understand anytype if it were changed to something more indicative of its generics use case. The auto and generic suggestions are my favorite.

Also, anytype does make sense to me in the compile time use case (i.e. as a dynamic type).

I did have one (potentially crappy) solution -- just getting the idea out there. Since we can essentially accomplish the generic behavior of x: anytype with comptime T: type, x: T, maybe one way to deal with this is to remove anytype as a function parameter, and just keep it as a struct field type (so we still have the dynamic type behavior at compile time). One argument in favor of this would be that the builtins use the comptime T: type, x: T pattern already, so there's a consistency there. Just an idea...

Edit: ... well I guess @bitCast(comptime DestType: type, value: anytype) DestType is a good example of how both patterns together is useful...

marler8997 commented 3 years ago

@Zooce, yes I've also found that anytype is inconsistent with the other any* keywords which can cause some confusion. I agree that changing it to something like auto or generic would be an improvement.

As far as I can tell, there's very little difference between x: anytype and comptime T: type, x: T besides ergonomics for the caller. I believe the caller can emulate the behavior of either one:

fn exampleAnytype(x: anytype)
fn exampleComptimeT(comptime T: type, x: T)

// emulate ComptimeT when calling a function that takes Anytype
exampleAnytype(@as(T, x))

// emulate Anytype when calling a function that takes ComptimeT
exampleComptimeT(@TypeOf(x), x)

In practice, there seems to be a number of factors that determine which one someone would choose. Accepting anytype can make it more convenient to call the function. If the function only accepts slice types, then declaring the signature as comptime T: type, x: []T helps clarify what the function accepts more than x: anytype would.

In my opinion it's not clear when to use which kind of signature, which doesn't jive with one of Zig's zen points "Only one obvious way to do things". I created an issue a couple years ago to discuss when the standard library should use one signature type over the other here: https://github.com/ziglang/zig/issues/2904

Zooce commented 3 years ago

Those are good points (one way to do things and ergonomics). Personally, I would tend to favor the "only one way to do things" philosophy, but ergonomics (and also readability) are certainly important. Here's a bad example that ruins ergonomics (at least for me):

fn testMe(comptime OutType: type, comptime InType: type, x: InType) OutType {
    if (OutType == bool) {
        return x > 0;
    } else {
        return if (x > 0) 1 else 0;
    }
}

test "bad example" {
    const x: i32 = 5;
    const u = testMe(u64, i32, x);
    const b = testMe(bool, i32, x);

    //...do some `expect`s
}

From the call site, having more than one type anywhere in the function parameters, it's not at all clear to the reader what's going on. Maybe if Zig had parameter labels it would work...but still not great. Using anytype for x in this case is obviously better.