beefytech / Beef

Beef Programming Language
http://www.beeflang.org
Other
2.51k stars 130 forks source link

Generic argument won't bind unless tuple item name is specified #1879

Closed RogueMacro closed 1 year ago

RogueMacro commented 1 year ago

Didn't know how to formulate this correctly in the title so here's what's happening. This throws an error even though it seems like perfectly valid code, Dictionary<String, String> do inherit from ICollection<(String, String)>, but you have to specify the names key and value of the tuple: ICollection<(String key, String value)>.

image

image

bfiete commented 1 year ago

Yes, this is by design.

Foo<(String key, String value)> is a different type than Foo<(String, String)>. It's true that (String key, String value) will implicitly cast to (String, String), but float will implicitly cast to double too, and they are definitely different types. (String key, String value) will not, however, cast to (String zebra, String cat).

You could argue that Foo<(String key, String value) should always considered to be "compatible" with Foo<(String, String), but remember that you could easily add a comptime initializer to Foo<T> which uses the names of the tuple arguments to add new data members to Foo, and then the memory layouts of those two types wouldn't even be the same...

bfiete commented 1 year ago

But to be clear, Dictionary<String, String> does not implement ICollection<(String, String), it implements ICollection<(String key, String value). "inherits from" is the wrong terminology there.

RogueMacro commented 1 year ago

Yes, I understand. My actual use-case was this: image

So I could easily just collect an enumerator into any collection without a for-loop. Worked around it by doing a map with tuple names: image

RogueMacro commented 1 year ago

Just to be clear again, there isn't a way to explicitly cast it from (String key, String value) to (String, String) (with generics)?

bfiete commented 1 year ago

It is not (and cannot be) possible to provide a way to cast from Foo<(String key, String value)> to Foo<(String, String)>.

bfiete commented 1 year ago

In your simple case, we know IEnumerable<(String key, String value)> is REALLY the the "same thing" as Foo<(String, String)>, but I could easily write a type using comptime where the internals of those two types are completely different.

RogueMacro commented 1 year ago

I just don't see how the names of the tuple items matter in the memory layout? If no name is specified, 0, 1, ... and so on is used right? Or is it different to Item0 and such from C#?

bfiete commented 1 year ago

The memory layout is not different. That's not the point.

bfiete commented 1 year ago

The point is that if you have a Foo<T> and a Foo<T2>, those types can have different memory layouts even if T and T2 have the same memory layout.

bfiete commented 1 year ago

If you need an example:

class Foo<T>
{
    public T mValue;

    [OnCompile(.TypeInit), Comptime]
    static void Init()
    {
        if (typeof(T) == typeof((String key, String value)))
            Compiler.EmitTypeBody(typeof(Self), "int mExtraData;");
    }
}
RogueMacro commented 1 year ago

Thanks, I think I am dyslexic. I completely see your point. I just think it's a little stupid that named and unnamed tuples can't be considered the same type, just in case someone wants to create specific behavior if the tuple names match exactly.

Correct me if I'm wrong please, I'm trying to understand the reasoning behind this, but if (String key, String value) were considered the same as (String, String), then you wouldn't be able to use comptime to change the memory layout anyway? Since you can't differentiate between the two? Is there any real use for what you showed in the example?

RogueMacro commented 1 year ago

I can't see a scenario where it's useful to differentiate between (String zebra, String horse) and (String key, String value) to make changes to the structure.

bfiete commented 1 year ago

Should we make struct Foo { float x; float y; } also be the same thing as struct Bar { float dollars; float cents; }? Or do member names only matter for structs but not tuples?

RogueMacro commented 1 year ago

At first I would argue that Foo and Bar are explicitly differently named types, while tuples are anonymous. Just pure data structures? But I guess they can also represent different things like Foo and Bar represent different things.

Then again, why not create a struct if you want the tuple to represent something specific like a key-value pair. If you want the key-value pair tuple to not be mixed/confused with/as something other than a kvp, then shouldn't you instead create a KeyValuePair<...> struct. That is in my opinion clearer, and almost exactly the same, right?.

I would think that since names for tuple items are optional, they are only there to help show what the value represents, not create a definite type.

bfiete commented 1 year ago

It might be helpful for you to think of tuples more like anonymous types, then.

bfiete commented 1 year ago

And when you omit field names then that just allows the compiler to name the fields for you and also you allows the compiler to create some implicit conversions operators to other some other tuple types.

bfiete commented 1 year ago

IE: just read (float x, float y) as an anonymous struct { float x; float y;} that gets some special treatment for construction and type conversion.

RogueMacro commented 1 year ago

If I understand correctly, the reason unnamed and named tuples cannot be "the same type" is that the field names say what the type represents, and shouldn't be changed to give it a different meaning?

If I can think of it as an anonymous struct, doesn't that also mean I get to decide what meaning to give it? In you example, I can say that x and y are positions on the screen, or they can be the size of an element. Isn't that's why it's anonymous? If I on the other hand say struct Size { float x; float y; }, then it means specifically a size and not position.

If it is anonymous and not given a meaningful name, I think that I should be able to use them interchangeably. Sorry if I'm being stubborn, but my point is that (as in C#), if you want a (String key, String value) to only be a key-value pair where it's used, why not do struct KeyValuePair<TKey, TValue> { TKey Key; TValue Value; }.

bfiete commented 1 year ago

You could also say that even if you had a struct Size { float x; float y; } then it still lacks units so somewhere it might mean miles and somewhere else it might mean kilometers, but that disparity does not make the name Size meaningless. Names still have meaning.

Anyway. I understand you have a different idea for how it should work but it's not changing.

RogueMacro commented 1 year ago

Thanks, I feel more enlightened than I did an hour ago, even though my opinion stands. Perhaps I should make my own language where tuples can't have field names 🤔

bfiete commented 1 year ago

I would very much suggest making your own language if you enjoy pain, you are looking for a project that can never ever be finished, you enjoy near-zero chances of "success", and you hate making money.

RogueMacro commented 1 year ago

... but you get to call your language Beef🐮 and many people are grateful for your sacrifice ✝️

bfiete commented 1 year ago

Oh, and you also get to deal with people telling you that your named your free project the wrong thing! So much fun.

RogueMacro commented 1 year ago

Maybe you should consider charging for this service. Begin a monthly subscription you pay on time or else all your source code will be purged. Nothing better than a pay-to-win programming language!