dotnet / csharplang

The official repo for the design of the C# programming language
11.58k stars 1.03k forks source link

Champion "Type Classes (aka Concepts, Structural Generic Constraints)" #110

Open gafter opened 7 years ago

gafter commented 7 years ago

See

VisualMelon commented 6 years ago

@HaloFour None of that wouldn't be the case if this was purely nominal. It would still provide static constraints (on static members and operators etc.), and it would still allow ad-hoc implementation of these static interfaces for existing types (which are what can't cleanly be done with interfaces already)

I dislike structural typing because:

I kind of don't care about the "you can make mistakes" bit; it's just a conceptual nightmare which (like duck-typing) elevates members to a position of power, and devalues named types (which are the valuable unit for modelling and reasoning about)

HaloFour commented 6 years ago

Sounds like one of those philosophical disagreements for which there isn't a resolution. In the case of concepts the "type" that describes the shape isn't supposed to be important. It's a container that exists only because that's how the signatures can be described to the CLR. The shape is the sum of the member signatures.

Notably this proposal doesn't change the behavior of interfaces. They're still strictly nominal. Concepts/shapes would be defined with different syntax. So unlike Go you have a choice. It's also quite possible that tooling could be improved to help find "implementers" of the shape.

As for the complaint that a type could "accidentally" implement the concept just by having the same shape, yes, that's the point, and it's not considered to be a problem.

VisualMelon commented 6 years ago

Interfaces are not as powerful as shapes, and people will use shapes. Without a nominal counterpart to this proposal, the choice is not "nominal" vs "structural", it is "exclusively virtual nominal generic constraints" vs "all manner of structural generic constraints" vs ugly code. Furthermore, other people will use and expose them (which is true for many proposals), so there is no choice (especially so here because you can't opt-out of implementing them unintentionally).

In the case of concepts the "type" that describes the shape isn't supposed to be important

A notion that I would appose on grounds I have already expressed.

Whether people care about these concerns is subjective (personally I think structural typing is pointless and unhelpful), and indeed not resolvable (at least some people at Google disagreed with me); however, I do get the feeling that few people are aware of the implications (and not just because it took me over 6months to get around to asking whether I was missing something and finally plant a -1 on this; I'm easily confused), so thanks for asking, else I wouldn't have bothered to write up my complaints.

orthoxerox commented 6 years ago

@VisualMelon I do not understand how your types will be violated by being matched against shapes. First, implicit conformance to shapes is not a given at all, and second, even if C# will auto-derive a witness for your class, the person using your type will still have to look at the signature of the shape-accepting method, look at the methods implemented by your type and make an explicit choice of passing an instance of your type to the method.

gafter commented 6 years ago

@VisualMelon It is not yet clear if this final version of this proposal will require an "instance" declaration to explicitly declare that a given type satisfies a given type class (shape), or it such a thing will always be inferred. One reason to think that an explicit declaration may be required is that, to fit with the underlying nominal type system of the CLR, a specific named type must exist as the glue between the shape and the type that satisfies the shape. Having the user provide that is one way to ensure that such a named thing exists. I personally feel that's a good idea from a language design standpoint. Even if it is required, however, we may permit the compiler to "fill in" the parts that are obvious satisfying parts from the type, and we may permit the compiler to automatically (implicitly) find the glue instance without it being mentioned everywhere it is used.

VisualMelon commented 6 years ago

@gafter thanks for the info, it's most welcome. Indeed, automatically 'filling in' the bits that are already there would be consistent with implicit member implementation for interfaces, and I can get behind that from a 'I don't want to write 100 redirections' point of view.

sighoya commented 6 years ago

Some remarks:

Which method will be invoked here:

1.)

void method<A,implicit EqA>(A a, A b);
void method<A, implicit OrdA>(A a, A b); 

2.) And here:

void method<A,implicit EqA>(A a, A b);
void method<A>(A a, A b);

3.) Does this proposal include the option to allow for multiple instances of the same type:

void first<A,implicit EqA1>(A a, A b);
void second<A,implicit EqA2>(A a, A b);
VisualMelon commented 6 years ago

@orthoxerox sorry, I wrote a long reply, but there was nothing much new in it, so I don't think I'll actually post it. Basically, unintentional implementation isn't my main concern; however, it is a horrifying prospect for me because there is no sensible way to mitigate it (you'd have to rely on good documentation and diligence), and it makes a mockery of the notion of expression of intent (of which I'm a big fan).

HaloFour commented 6 years ago

@VisualMelon

it makes a mockery of the notion of expression of intent

I disagree. The declaration of intent is the attempt to call the method that consumes the shape. This doesn't affect the identity of the type(s) that you're passing at all and thus that type doesn't have to (and shouldn't have to) make any declarations.

masaeedu commented 6 years ago

Can structural types require members that are of a type internal to the assembly?

BreyerW commented 6 years ago

Well, unintentional implementation seems to be more or less solved in shape prosposal, where you use extension everything feature to conform to shape after-the-fact but still explicitly. Because of this you cannot make unintentional mistake unless you dont fully understand what shape or extended instance is supposed to do in which case its your own fault. The only problem is that it will be hard to discover outside of IDE.

I know its not your biggest concern but i wanted to point out that in case of shape+extension everything combo, this problem is minimised and will exist mostly outside of IDE and even then, its not that bad, just look for extensions that extend said instance

Pzixel commented 6 years ago

@BreyerW if there is 10 matching shapes and every shape provide AsEnumerable method how you get which one is actually executing (without asking IDE)?

I know its not your biggest concern but i wanted to point out that in case of shape+extension everything combo, this problem is minimised and will exist mostly outside of IDE.

Au contrere, with extension everything you may shadow unintentionally something you were expecting to call.

BreyerW commented 6 years ago

Matching is the keyword, in this case code will likely not compile because of ambiguity and the situation is same as in not sufficiently overloaded methods. But lets say we have such bizzare example. In this case i think you should think about consolidating shapes. So many MATCHING shapes for ONE instance will likely point to potential overlaps.

VisualMelon commented 6 years ago

@HaloFour That is one expression of intent (not a public one), but if the type hasn't expressed its intent then how am I suppose to know how to use it? A type's external API is orders of magnitude more important than the statements inside a method.

@BreyerW (regarding you previous comment) indeed; but as soon as you make it explicit you can do away with the (redundant) structural typing. Relying on the structural typing just means you risk making the same mistake every time you try to use a type/shape combo, instead of going to effort of making it once when you write an explicit (nonsense) mapping (which is nice and easy to resolve when you realise).

CyrusNajmabadi commented 6 years ago

it means that concrete types do not express their intent explicitly,

There is nothing about structural types that mean you can't express your intent explicitly. I use typescript a ton and it is structural. However, i express my intent there all the time. I can choose not to in some cases, but i often do when i think it's valuable to do so.

CyrusNajmabadi commented 6 years ago

conceptually it devalues the name of the type, instead presuming that the signatures of the members define the type, not the name, which also makes the code harder to reason about

Note that the is already the case in many APIs. The name doesn't really matter, and is even a hindrance. That's why, for example, there are such vague names like Func and Action used extensively for delegates. Because what is actually important is the flow of data in and out. Structural types are just a way of composing those to higher level kinds.

CyrusNajmabadi commented 6 years ago

it's just a conceptual nightmare which (like duck-typing) elevates members to a position of power,

I use two structural langauges already on a day to day basis. At no point has it been a conceptual nightmare. it's just another tool in the toolbox. My team uses them effectively and has no trouble reasoning about things.

and devalues named types (which are the valuable unit for modelling and reasoning about)

So what? I don't care if named types are devalued by virtue of having more options. It's like telling me 'classes are devalued because now i can pick a struct'. Or interfaces are devalued because i could use a delegate in place of some subset of them. So what? If nominal types make sense for the domain i'm in, i'll use nominal types. If shapes make sense, i'll use shapes.

CyrusNajmabadi commented 6 years ago

E.g. this is what Go community thinks.

This is what a single member of the community thinks :) (though i'm sure there are many more who agree). Without question you can find community members for every language that will take issue with every part of your language. That's just how it works :)

As a whole though the go community has remained remarkably accepting of these decisions without a large sentiment i can see through any go channels (har har) that this needs to change.

Like any language choice, there are pros/cons, but overall the community seems to have no problem wit hthis side of go in balance.

CyrusNajmabadi commented 6 years ago

(at least some people at Google disagreed with me); however, I do get the feeling that few people are aware of the implications (and not just because it took me over 6months to get around to asking whether I was missing something and finally plant a -1 on this; I'm easily confused), so thanks for asking, else I wouldn't have bothered to write up my complaints.

If it helps, i was one of the people that created the structural type system for TypeScript. A particularly challenging project given that you are not defining the structure that you do want, but rather trying to find ways for users to express the structure that is there.

I also use go as one of my primary languages on a day to day basis. I think i'm pretty well in touch with all three ecosystems and communities and i'm pretty well aware of the implications. I just don't see any of this as a net negative. This is merely how any language feature works, where there usually are lots of pros, along with possibly some cons to things.

BreyerW commented 6 years ago

@VisualMelon Hmm im not sure if you realise but type classes and shapes are compromise solution to bring more powerful constraints. Theoretically, everything could be achieved by extending family of available purely nominal constraints in CLR but changing CLR is such a pain in the ass that team will avoid it at all cost even accepting inferior solutions as long as they dont require CLR changes.

In lieu of this shapes+extension everything is as explicit and nominal as we can get. Personally if i would have to choose no feature (especially this powerfull) or feature slighty flawed (and only if you are reeeaaally bad programmer in which case there is hundreds more traps already existing and awaiting these fools) i wouldnt think long.

BTW shapesare actually very explicit. You can use and treat them like any nominal constraint as long as you have absolute control over code base since you can ( or rather pretty much HAVE TO) extend any instance with shape exactly like interface. Extension everything will be used mostly for 3rd party classes/structs. See examples at the top post in #164

CyrusNajmabadi commented 6 years ago

Relying on the structural typing just means you risk making the same mistake every time you try to use a type/shape combo,

Can you explain this bi. What "same mistake" are you referring to. I've used structural typing in typescript and go extensively for the past 5+ years, and i can't think of a time when a mistake has happened here. I'm trying to even grasp how likely it would be that you coudl make a mistake. That you had a shape that was pretty much identical to the form of your domain object. And yet... you would not feel that your domain object matched that shape.

VisualMelon commented 6 years ago

@CyrusNajmabadi I'd be up all night trying to reply to all that (I write very slowly, and I'd probably just end up saying the same things again), but I'll try to respond to some of it...

@BreyerW this proposal could be perfectly nominal; it would be easier to implement because the compiler wouldn't have to interrogate types when trying to pass them implicitly as a shape. Nothing about shape + extension everything has to be structural.

BreyerW commented 6 years ago

@VisualMelon

Hm if you are so sure then care to make alternative proposal and link it here and in #164 ? Im certain team will be delighted to see proposal that might work and will be easier to implement. Remember CLR changes are pretty much a no-go.

CyrusNajmabadi commented 6 years ago

@CyrusNajmabadi I'd be up all night trying to reply to that (and I'd probably just end up saying the same things again)

The important bits for me are explanations as to the extent to how bad things are. For example, i certainly can't disagree that one could accidentally conform to a shape. What i'm trying to figure out is how that can be felt to be likely enough to warrant concerns. Again, this going against many years of experience using structural languages where i can't recall this happening once.

Similary, i would like understanding behind views of 'devaluing'. I do not see additional available tools as devaluing anything. Indeed, my view on C# is that it exists precisely by taking the starting point of Java and thinking deep and hard about many sorts of additions that provided value, despite arguably devaluing the 'core' java basis it carries its lineage from.

One of my favorite things about C# is that it is an unabashedly 'jack of all trades' language. Want OO constructs? You've got them. Want functional? Go for it. Want dynamic, there as well. Like pattern matching? There; with more coming. This fits into that world for me. There isn't one true language for me. There are just tons of language features that be combined in interesting ways to make life better for authors and consumers alike. This feels like a wonderful addition to that set of capabilities that i think will be very beneficial to the entire ecosystem.

CyrusNajmabadi commented 6 years ago

Nothing about shape + extension everything has to be structural.

Technically, nothing about shapes/extensions/concepts/etc. even needs C# language support. You can already write it all yourself today with teh constructs the runtime supports. Heck, the shapes/extensions proposal shows precisely how it would 'lower' into the constructs the runtime already has.

That argument isn't interesting to me simply because that is well understood and precisely the point o of the language proposal. The way to do thsi today with nominal is just unpleasant. Sure, it can be done, but people rarely do it because it's just not a great experience. On the other hand, shapes/extensions/concepts attempt to reformulate the pattern in a way that feels more natural and easier to pull off and understand for an existing C# developer. The entire purpose of hte feature is to repackage what you can already do, but in a way that is actually palatable and can ideally be successful.

VisualMelon commented 6 years ago

@BreyerW honestly, I'm now not sure again whether this proposal is structural. I have a really hard time reading the documents, and there have been some contradictory comments made. The implementation would be exactly the same, you just don't implicitly write instances when you can match the members of the concrete type to the structure of the concept/shape/thing (you error instead)

BreyerW commented 6 years ago

@Pzixel

Sorry didnt saw your comment to quote. I dont think it will be that big problem because extension everything force you to explicitly state what is extended and optionally state which interfaces and shapes you will abide. If you dont, instance will not abide any new interface and/or shape.

Outside of ide it might be problematic but all you will have to do is use search function and check if there is extension of MyClassIWantToCheck or whatever syntax will be

VisualMelon commented 6 years ago

@CyrusNajmabadi sorry, this is quite long. I tried to make it shorter but it just gained more paragraphs so I'll stop trying now.

The important bits for me are explanations as to the extent to how bad things are

I can't give a good answer to this, because I have essentially zero experience (except with auto currying...), because I stay away from non-nominal languages as best I can. I shouldn't wish to tell anyone whether these issues are likely or not, only that it is definitely possible, that I don't see a good way to deal with it if it should occur. I do think they will be more of an issue in an already mature nominal language than they would be in a structural-from-the-outset initiative like Go.

Regarding expression, sure you can express your intent in a structural language, but nothing says "I implement interface IFace" better than saying "I implement interface IFace", and I think this is a really useful thing to do (that is, to unambiguously express (to the compiler and programmer) that you are meant to implement IFace).

conceptually it devalues the name of the type, instead presuming that the signatures of the members define the type, not the name, which also makes the code harder to reason about

Similarly, I would like understanding behind views of 'devaluing'

My point here is that to implement the type you no longer need to reference it by name, and (as someone else has said) it becomes a carrier bag for a set of methods. Conceptually I'm sure we can all agree that an interface should provide a meaningful and consistent interface, which comprises a set of well named members. The interface is not defined by the members, it is defined by the intent which is represented by its name, which is served by the members. Lists don't have an Add method, they have a List.Add method. Structurally typed interfaces/traits/whatever 'devalue' the the List. bit, because the implementer doesn't implement List.Add, it just implements Add (from the perspective of the structural typing engine).

I'm probably failing to make the context of my commentary clear, but the point is that this is a new powerful feature, but it is structural: this means that that the name of a shape/concept/whatever (structural) has less meaning (it is 'devalued') than the name of a C# interface (strictly nominal), and that the members which a type exposes start to define how it can used, rather than just being an incidental public API attached to the concrete type.

Structural types impart meaning onto member names and signatures in other types, meaningful or otherwise.

Somehow I don't think that will help clear up my position... but I tried.

Or interfaces are devalued because I could use a delegate in place of some subset of them.

No. Again, I've clearly not made my point well, but one of those few things in software that makes me smile is the knowledge that delegates are nominal: they have a name, and that name expresses intent, even if we have all agreed the Func and Action are meaningless. No matter: Func<int, int> has no bearing on my delegate int Cycle(int).

One of my favourite things about C# is that it is an unabashedly 'jack of all trades' language

One of my favourite things about C# is that it's nominally typed through and through. The 'functional' stuff is all implemented nominally (though as far as I am concerned, there is practically nothing functional about C#). Dynamic - which is explicit dynamic, not incidentally not nominal - is completely opt-in (and thankfully I never encounter it). The pattern matching is nominal. Interfaces are nominal. I am never in any doubt whether the code I write is meant to work or not (as opposed to will run or not); everything is completely explicit, and the compiler helps me every step of the way. Edit: Tuples are essentially/actually structural, and I don't like them much either, but they are opt-in and they can't influence other types (I'd still object to anyone using them as part of a public API)

My complaint here is that this feature will be used, and that it will produce public structurally typed APIs. It's another tool in the box, but for some reason it departs fundementally from the other tools in the box, and I don't see any benefit to doing so. It allows me to export a shape/concept/whatever, and a concrete class, and give you no explicit indication that one implements the other. No longer can the compiler help me, because I havn't told it what I want: I'm expecting it to work it out for me.

If this proposal were nominal (which as a point of information, I'm not sure it isn't at the moment), then I would be inclined to agree it would be a wonderful addition (though I'd still rather just have static interfaces (i.e. no implicitly selected instances, non-inheritable, etc.), but that's a different issue).


Since I've been attacking structural typing and hailing nominal typing for while, would someone like to make the case that there is actually some benefit to having shapes structurally typed?

I might need to take a break from all this, I'm running out of hair to pull from my head, and I don't want another repeat of in (honestly, I wasn't looking for a discussion on this, I was just trying to work out if it was structural or not so I could give it a -1 if appropriate).

Pzixel commented 6 years ago

@BreyerW

Outside of ide it might be problematic but all you will have to do is use search function and check if there is extension of MyClassIWantToCheck or whatever syntax will be

It won't really help you. Consider you have interfaces IA, IB, IC, where IC : IB, IB : IA. You have an extension on IA for method Foo(A, B), extension on IB for method Foo(A, B, C = null) and extension on IC for method Foo(A, B, params[] object foos). And then you call myIC.Foo(a,b). In this case it would be very fragile and it's not easy to determine which function is the best match. I can't say it right now without going to MSDN and rereading method resolution rules. Can you?

@CyrusNajmabadi

This is what a single member of the community thinks :) (though i'm sure there are many more who agree). Without question you can find community members for every language that will take issue with every part of your language. That's just how it works :)

I'm not saying it's a blocker and it's a no-no feature. Just like you proposed add yet another tool in a toolbox, I provide additional point of view in your point of view bag to make decision more deliberated 😄

sighoya commented 6 years ago

@VisualMelon

Afaict, it is still explicit but not at the global level more on a local level, i.e. on the method definition side. So you can't call shape methods on a instance of a type as long as it is not specified in the method signature. This make things much more flexible and circumvent the global uniqueness problem of Haskell. So theoretically you should be a able to select different implementations for the same type by different method signatures, at least in my mind.

You have to imagine that the "implicit" keyword conduct the implementation for you with some already implemented struct which can be shared for more than one type providing you, theoretically, a m:n relationship between instance selection and instance selected by.

In the end, you didn't inherit the mess from C++.

Please correct me, if I'am wrong.

Pzixel commented 6 years ago

@VisualMelon

Since I've been attacking structural typing and hailing nominal typing for while, would someone like to make the case that there is actually some benefit to having shapes structurally typed?

I can imagine it's extemly helpful when dealing with bad codebases (which are 99% of overall quantity). For example, let's take BCL itself. We had no IReadOnlyCollection for an ethernity. Finally it's here. But there still billions of libraries that just inherit IEnumerable and provide Count property, without implementing anything else.

When I was studying in university and I was writing my own collections I was never implementing ICollection because it was to restrictive - look at all these CopyTo, GetElementAt, and so on. So i was just exposing Count property and worked via LINQ successfully. But if I wanted to work with it via interface I had no other interface except IEnumerable. If you have R#, you are probably familiar with warning "possible multiple enumeration". So you have two options:

  1. Take IEnumerable and pray that it doesn't query data twice, trice or more.
  2. Take IEnumerable, call ToArray and make extra allocation. You are also lying to your clients that you take IEnumerable because you are actually work with array
  3. Take IReadOnlyCollection as argument, in this case you are not lying to your clients, but you limit them and now they have again to perform extra allications or just not use your method
  4. Take anything that has GetEnumerator and Count method and have best of two worlds: zero allications and suitable interface.
BreyerW commented 6 years ago

@Pzixel

Yeah with deep hierarchies it will be a mess but deep hierarchies are already a mess in itself due to complexity and everyone lives despite this problem so im not overly concerned. And there are still IDEs its not like we dont have great tooling for free nowadays.

@topic

I think it might be worth treating shapes and concepts separately. They achieve similar things as far as i understand but offer different approach: former is extremely explicit thus feel more nominal while the latter is more implicit in nature (hell, they even propose to add new keyword implicit to strengthen this nature further)

VisualMelon commented 6 years ago

@sighoya sorry, I'm not sure I follow (probably my fault, I'm easily confused). Which mess are we not inheriting from C++, and is the structural typing necessary to avoid it?

I'm not sure that the idea is to allow selection of a specific implementation by method signature, but rather by (optional) generic constraint (though I'm not sure this is a given).

My understanding of the proposal is (as of about 2 days ago) in a continuously but slowly shifting state, so there is a good chance I've got some of the details off or am reading things that aren't there.


@Pzixel if I'm understanding your concerns, solving them doesn't require structural typing. A nominal version would still allow you to define your own interface and provide an implementation for existing types (e.g. in a different assembly). The effect of structural typing here (as I understand) would be if (e.g. in 3) you provide a custom shape, then people can 'implement' it without having to write (once) a dedicated mapping.

The benefit of structural typing would then be less typing and reduced friction when using your custom APIs with a third-party/custom type (you can provide a generic instance to cover 'the usual suspects', for example from IEnumerable<T> which (as I understand) can perform ideally (i.e. the instance can have a static constraint on the interface IEnumerable<T>, though checking back, they don't appear to show this explicitly in the proposal)).

I suppose there is a benefit in terms of unilaterally changing an API without breaking stuff in the "these types don't really implement what they are meant to" example (personally I object to this sort of trickery under any guise). However, if people are lying about implementing stuff, there is a chance they did so implicitly (otherwise the shape wouldn't work), and now you just have lots of types which 'look' like a shape but are not (they would still be lying, just about something else).


Is there a way that one can test Windsor's example implementation without messing with a local install of VS?

Pzixel commented 6 years ago

@VisualMelon

if I'm understanding your concerns, solving them doesn't require structural typing. A nominal version would still allow you to define your own interface and provide an implementation for existing types (e.g. in a different assembly). The effect of structural typing here (as I understand) would be if (e.g. in 3) you provide a custom shape, then people can 'implement' it without having to write (once) a dedicated mapping.

This is what this proposal does - it writes all required wrappers automatically. I don't like to type what could be otherwise inferred.

As I see it the reason for this proposal in a nutshell is provide autmatic wrappers that you could write yourself, but you don't.

More general, it allows you to create some post-hoc generalization. You always can write some additional wrappers. But in most cases you don't have enough time: this task should be done yesterday and thus you just write ICollection<T> and write some "here be dragons" cautions in comments, because you just don't have time to wrap it correctly. So this "less typing" actually allows you what you theoretically could do yourself, but in practice you never did.

sighoya commented 6 years ago

@VisualMelon

Which mess are we not inheriting from C++

C++ massive implicitism is a mess especially in the context of ad hoc polymorphism.

and is the structural typing necessary to avoid it? No, explicit conversions.

I'm not sure that the idea is to allow selection of a specific implementation by method signature, but rather by (optional) generic constraint (though I'm not sure this is a given).

Well, generic constraints should be a part of the method signature otherwise we don't know if a certain type satisfies the constraints. For example C++ doesn't allow for generic constraints they are implicitly inferred by the compiler (type deduction) which is a mess, too. But it will hopefully mitigated with new C++ standard.

To comeback to the selection of specific implementations, I could imagine a scenario where we have two (or more) implementations of set orderings:

instance OrdBySubsumption<T> : Ord<Set<T>> {
  public static bool operator <= (Set<T> set1, Set<T> set2) => return subsetEqOf(set1, set2)
...
}
instance OrdByCount<T> : Ord<Set<T>> {
  public static bool operator <= (Set<T> set1, Set<T> set2) => return set1.size() <= set2.size()
...
}

Then you select by method signature (or generic constraints):

Set<Set<T>> sortBySubsumption<T,implicit OrdBySubsumption<T>>(Set<Set<T>> set) where OrdBySubsumption<T>: Ord<Set<T>>;
Set<Set<T>> sortByCount<T,implicit OrdByCount<T>>(Set<Set<T>> set) where OrdByCount<T>: Ord<Set<T>>;
...
HaloFour commented 6 years ago

@gafter

It is not yet clear if this final version of this proposal will require an "instance" declaration to explicitly declare that a given type satisfies a given type class (shape), or it such a thing will always be inferred.

I'm curious if you have an example as to what this "instance" declaration might look like. I think the structural typing benefits most when the conversion is implicit and to require the consumer to do extra work in order to use the shape, to me, just feels like it would be noise.

VisualMelon commented 6 years ago

@Pzixel and I don't like my computer guessing at what I want; I like knowing that I've told it what I want, so that when I (or someone else) gets it wrong it can tell us back.

I don't understand that bit about ICollection and dragons.

@HaloFour there is an example of an instance declaration attached to Windsor's implementation: https://github.com/MattWindsor91/roslyn - such instances are necessary to allow the 'post-hoc' definition of behaviour (i.e. implementing interface/concept/whatever members that are not already (or not correctly) 'implemented' by a concrete type).

@sighoya I'm still trying to get my head around that (I don't know nearly as much C++ as I would like)... but yes, I think you are right about the overloading (I didn't see what you were getting at the previous time). scratch that, I'm confused again...

HaloFour commented 6 years ago

@VisualMelon

I see, so the "extension" type (or "instance" type) would more explicitly wire up the concrete type to the members of the shape that can't be automatically. I do see that in the shapes/extensions discussion:

public extension IntGroup of int : SGroup<int>
{
    public static int Zero => 0;
}

My question would be what would the compiler automatically wire up between the concrete type and the shape? Just existing members? General extension methods/members? When is it absolutely necessary to furnish that instance type?

VisualMelon commented 6 years ago

@HaloFour I hadn't thought about extension methods/members, and certainly haven't given them enough thought to make a sensible remark just now, but an extension member isn't really a member at all (there is no IEnumerable<T>.Select, there is only System.Linq.Enumerable.Select).

Including extension members would imply that a different implicit instance would be created if a different set of extension members were in context; however, it would allow another route to implement stuff (as you imply). There would be concerns with overloading (e.g. does the implicit instance prefer an extension member, or a 'real' member?). Explicit wiring would remain necessary if one wanted to disambiguate, and to provide post-hoc static members (can't implement static int Int32.Zero with extension members presently, but I'm assuming there is no reason not to add extension properties).

CyrusNajmabadi commented 6 years ago

I can't give a good answer to this, because I have essentially zero experience (except with auto currying...), because I stay away from non-nominal languages as best I can.

This concerns me. You've been adamantly railing against structural typing, even saying things like:

"screams" or "it is a horrifying prospect for me" or "it's just a conceptual nightmare" or "It seems my fears have been realized"

These are very strong statements, and have put you in the position of stating you are diametrically opposed to this, even though you have little experience htere and stay away from these things as much as possible.

Perhaps consider that your perception is not really fair and that if you spent more times with languages like this you might feel differently: http://www.paulgraham.com/avg.html

CyrusNajmabadi commented 6 years ago

Structurally typed interfaces/traits/whatever 'devalue' the the List. bit, because the implementer doesn't implement List.Add, it just implements Add (from the perspective of the structural typing engine).

Again, in practice, i have never encountered this. This feels like a very hypothetical concern on your part as opposed to a true issue that happens in practice. I use Go daily. I helped design the TS language. I never felt this devaluing that you ar ementioning, and i've been using or working on nominal languages since 2001.

CyrusNajmabadi commented 6 years ago

No. Again, I've clearly not made my point well, but one of those few things in software that makes me smile is the knowledge that delegates are nominal: they have a name, and that name expresses intent, even if we have all agreed the Func and Action are meaningless. No matter: Func<int, int> has no bearing on my delegate int Cycle(int).

You are saying that having structural function types is ok, because you can still have nominal function types. Tat's the same here. If you want nominal types, you can still have them. Classes/structs/intefaces are not going away. But if your domain is better suited to structural types, then you can have them.

CyrusNajmabadi commented 6 years ago

It's another tool in the box, but for some reason it departs fundementally from the other tools in the box, and I don't see any benefit to doing so.

For one thing: performance. That's a very large benefit.

CyrusNajmabadi commented 6 years ago

@Pzixel and I don't like my computer guessing at what I want; I like knowing that I've told it what I want, so that when I (or someone else) gets it wrong it can tell us back.

Inference is a large part of C# and has been since 2.0 onwards. Many people use 'var' extensively so they don't have to provide the types for locals. 99.9%+ of all generic methods call i make do not supply type arguments. That's because the language sensibly figures this out and does things write while allowing me to write less.

Having the language figure this out, and not forcing you to do everything explicitly is a virtue here.

VisualMelon commented 6 years ago

This concerns me. You've been adamantly railing against structural typing, even saying things like:

You are right, I certainly employ hyperbole far too liberally (it's really 2 layers of sarcasm, but that doesn't make it any less unhelpful in an internet forum); but I'm merely expressing my opinion, not trying to frighten or convince anyone. I don't have much experience because what experience I have had I did not like (I tend to avoid things I don't like), and nominal typing is the only system I'm aware of that fits my mental model. I don't believe there are right answers, and if there were I'm not so daft as to think I'd have them, but I'm not a nominal-nutcase because it's all I know. I'm sorry that I'm so bad at expressing myself in text (not that I'm much better in voice), it's really not helpful for anyone, and it always ends up being very stressful for me.

This feels like a very hypothetical concern on your part as opposed to a true issue that happens in practice

Again you are right. I am (certainly (I'd hope) by the standards of this forum) an inexperienced developer, so my arguments are academic in nature because I don't have the breadth of experience to draw from, and don't wish to pretend that I do. None-the-less, it is a frightening prospect for me that the compiler will allow code to be used for a purpose it wasn't explicitly intended, which is of course subjective, but I'm not trying to suggest everyone should feel like I do, I'm just trying (and it seems failing) to convey why I have developed the opinions I hold since people have asked.

For one thing: performance. That's a very large benefit.

Now this one isn't just me being a sarcastic and miserable human being!

As far as I can tell, there is no performance benefit attained by this proposal that requires it be structural: that is down to the decisions made by the CLR folks, and how this proposal exploits specialisation for structural generics (another thing I adore about C#).

You are saying that having structural function types is ok, because you can still have nominal function types. Tat's the same here. If you want nominal types, you can still have them. Classes/structs/intefaces are not going away. But if your domain is better suited to structural types, then you can have them.

Clearly I've still not made that point clearly. There is nothing that compares to shapes/concepts in the language already (i.e. that provides a clean syntax to achieve this post-hocness). There is no nominal alternative. Even if there was, the fact that I could write classes and interfaces and nominal-shapes does not mean that structural-shapes will never influence my decision making; rather, their existence will influence decisions I make when I write my classes and interfaces.

The 'structural' function types are just intently meaningless nominal types: you can't cast something as an Action or a Func implicitly (excepting lambdas, which don't have a meaningful type). This proposal means you can treat a class or a struct as a MyConceptNameHere implicitly; it is a completely different arrangement.

Inference is a large part of C# and has been since 2.0 onwards. Many people use 'var' extensively so they don't have to provide the types for locals. 99.9%+ of all generic methods call I make do not supply type arguments.

Indeed, and I exploit both those features all the time; however, I do not think it a useful comparison because they are not part of a public API (they are a private concern for implementers), and they don't reach inside types (they just piggyback off the nominal type info available). var is completely innocuous, though I'll grant that issues with generics do stem from overloading (I shan't share my opinions on that). Some other stuff I though might have been problematic with generic-type-parameter inference doesn't seem to be the case, because the compiler doesn't try too hard for its own good (again, phrasing things emotionally, please forgive me).

CyrusNajmabadi commented 6 years ago

As far as I can tell, there is no performance benefit attained by this proposal that requires it be structural: that is down to the decisions made by the CLR folks,

Yes. And that's a very big deal. The runtime changes almost not at all. We're about to get just about hte only single change to the runtime since 2.0. That's a virtue. The runtime acts as a large and stable platform that can be depended on to remain relatively constant for long swaths of time. The language then figures out great ways for people to be productive on top of that platform.

and I don't see any benefit to doing so.

So, when you say there is no benefit, and then disregard the benefit because of its necessity with how the platform works, that weakens the argument. The reality of hte situation is that the runtime is not going to change here. If it were, that would be probably 10 years down the line. In the meantime, people still want expressiveness, and would like it not at the cost of performance. And these sorts of approaches provide that. That is, unquestionably, a benefit.

You may not like that this is the balance that the runtime and languages have taken. That's fine. But it's the reality of hte situation.

CyrusNajmabadi commented 6 years ago

Again you are right. I am (certainly (I'd hope) by the standards of this forum) an inexperienced developer, so my arguments are academic in nature because I don't have the breadth of experience to draw from, and don't wish to pretend that I do. None-the-less, it is a frightening prospect for me

This concerns me. You are allowing your own admitted lack of experience to frighten you. Might i suggest an alternate approach going forward? Instead of being frightened by the different and new, consider using these as opportunities to grow and better understand the value these systems provide and why many people find them just as good, if not better, than the alternates.

As i mentioned before, C# is unabashedly "jack of all trades". The language has evolved and thrived over the years precisely because it has been willing to look at hte rest of the world and say "that's actually really nice and would def make many types of development problems easier". The job has then become figuring out the best way to try to integrate that into what is already a large language in a way that feels great, while also being implemented on a runtime with very interesting characteristics.

CyrusNajmabadi commented 6 years ago

their existence will influence decisions I make when I write my classes and interfaces.

Yes. Just like the existence of delegates influences my decisions wrt using an interface. The same with abstract classes vs interfaces. I have to make these decisions daily. But the presence of interfaces does not mean i don't use abstract classes (or vice versa). It means i pick the right tool for the job.

Indeed, and I exploit both those features all the time; however, I do not think it a useful comparison because they are not part of a public API (they are a private concern for implementers), and they don't reach inside types (they just piggyback off the nominal type info available)

But classes/structs/interfaces/delegates are part of your public API. And they do force people to think about the right tool for the job. Just because they're all nominal does not mean they don't make you think about a whole host of other factors. That's the job of hte API author in the first place. To pick from teh tools to provide the best solution for their domain. This just introduces a new tool. It will be the right choice in some domains, but will not be in others.

CyrusNajmabadi commented 6 years ago

his proposal means you can treat a class or a struct as a MyConceptNameHere implicitly; it is a completely different arrangement.

And a delegate is a completely different arrangement from an interface. They're all 'completley different arrangments' because they are all different things. So yes, a shape wuold be different from a non-shape. That's the point. If it was not different, why would you ever use it?

Now, as you've said, the way that they're different is in a way that you have yourself admitted seems to cause you stress for some reason (even though no actual real-world problems seem to be present). So yes, you do not like this new and different thing because how it is different is precisely what you do not like.

--

However, this approach is not really a good one for language design. Given anything new, you will have some person that likely doesn't like it because they personally stress over that specific aspect. The real question is: is this a real-world problem, and not just someone's visceral dislike over something they don't have much experience with?

Note: this is not hypothetical. We get this with every language release. You would not believe how some people were against generics (using similar arguments to you), or lambdas, or dynamic, etc. In hte end, these all ended up being very valuable parts of hte language that people now take for granted as great tools to build libraries and codebases out of.

CyrusNajmabadi commented 6 years ago

You are right, I certainly employ hyperbole far too liberally (it's really 2 layers of sarcasm, but that doesn't make it any less unhelpful in an internet forum);

I actually do think it's unhelpful. If this is actually a bad language feature we should be able to point to real cases that help justify that position. That happens all the time. Many language features are proposed and real examples of how that would make code worse are provided all the time.

You've been adamant that this will be bad for certain reasons (like people making more mistakes). But i still haven't seen a good justification for those positions. As such, this approach is actively unhelpful. I'm very open to hearing real criticisms and real world concerns. They're usually the #1 thing that i use when deciding if something is a bad idea or not. But if all i'm seeing is a vehement and passionate tirade against a feature, without actual real cases to back things up, then i'm left actually unchanged from where i was in the beginning because none of the critique provided any fruit.

--

IMO, the best way for this conversation to proceed would be to:

  1. dial waaaaay back on the rhetoric, fear, hyperbole and sarcasm.
  2. focus much less on what you "feel" and more on what sort of pros cons come directly from this feature on real code cases.

That will keep things well focused and will help ensure that if htere are problems, they are rightly considered when determining if this feature is an overall positive or not.