The point being that there are always going to be more eyes, and more knowledge of available tools (i.e. including "D, E and F"), and more experience using them, with open source than with a single in-house dev team.
If true then logically it will be sufficient to run this "master model" once before any code release for the level playing field to be restored. After all, even open-source software is private until it is released.
> If true then logically it will be sufficient to run this "master model" once before any code release for the level playing field to be restored.
I'm struggling to see how it is a level playing field:
1. Closed-source: defender runs llms to check the sources for vulns, runs llms on each PR, runs llm on deployment of the compiled output. Attacker runs llm only on compiled output.
2. Open-source: both attacker and defender runs llms on source, on PRs and on compiled output.
My knowledge on functional languages is limited, but as I understand it, it’s possible to formulate expressions that are basically NP problems?
And hence impossible to speed up?
So is it a F# issue or inherent to functional programming?
AFAIK it was a much more down-to-earth thing. The implementation of computation expressions in F# compiled down to lots of function objects that were not very GC-friendly. Or something like that. To be honest, I never looked that deeply at it :)
I actually think F# has received some "love" over the recent years contrary to some on this forum; that feature being an example. My view, maybe unpopular but in the age of AI maybe less so, is there is a diminishing returns to language features anyway w.r.t complexity and the use cases that new feature will actually apply for. F# in my mind and many other languages now for that matter is pretty much there or are almost there; the languages are converging. When I used F# I liked how it unified features and tried to keep things simple. Features didn't feel "tacked on" mostly with some later exceptions.
Last time I used F# a few libraries started adopting this for their CE's (e.g. IcedTasks library, etc).
I'm 100% on board with the [] syntax. I'm not on board with adding the syntax for passing arguments to the constructor within that syntax.
I agree that = [] is perfectly fine syntax. But I would definitely argue that:
[with(capacity: values.Length * 2), ..
is non-intuitive and unnecessary. What other language is there that has this syntax? Alternatively, is this a natural way of writing this? I wouldn't say so.
My main language in my free time is Rust, a few years ago it was F#. So, I'm absolutely open to other syntax ideas. But I feel that there has to be a direction, things have to work together to make a language feel coherent.
Another example would be Clojure, which I started learning a few months ago (before we all got swept up in AI FOMO :D). Clojure as a language feels very coherent, very logical. I'm still a beginner, but every time I learn something about it, it just makes sense. It feels as if I could have guessed that it works this way. I don't get that feeling at all in many of the new features of C#.
> The example you point out is the advanced case, someone only needs in a very specific case. It does not have a lot todo with learning the language.
I disagree. When learning the language, you're going to have to read other people's code and understand it. It's the same basic principle, but, I'd argue, much worse in C++. Yes, in theory, you don't have to understand SFINAE and template metaprogramming and (now) concepts and all those things. You could just work in a subset of C++ that doesn't use those things. But in practice, you're always going to have issues if you don't.
intuitive is definitely in the eye of the beholder. When people saw:
`HashSet<string> people = [with(StringComparer.CaseInsensitiveComparer), .. group1, group2]`
they found it understandable. And this was also much nicer than what they'd have to write today (which would bring them out of the nice declarative collection-expression space).
Does that make it 'necessary'? Ultimately that's up to the individual. We felt like it was. Not being able to do simple things like this felt like a 'bitter pill'. Customization of collection construction is common (looking in codebases, it shows up about 7% of the time). So having to 'fall out' from the uniform collection-expr system into the much more verbose and clunky forms just for this common enough case felt 'necessary' to us.
>But I feel that there has to be a direction, things have to work together to make a language feel coherent.
I feel like this is conflicting feedback. Collection expressions made the language more coherent. Instead of 7 different ways of doing things (some of which were genuinely not efficient), we gave one uniform way of doing it. That makes things more coherent. Making it so you don't have to drop out of that for something as simple as configuring the collection makes things more coherent.
As someone who has been coding C# since the pre-generics days, this is the first syntax change which I strongly disagree with. I pretty much love every little bit of syntactic sugar you guys have added to the language. But this? This seems objectively illogical and just straight up ugly. It blows my mind that this is making it into the language, and it makes me worry about the future of C#.
Just guessing here: Putting something at index 0 that has nothing to do with the content.
I tend to agree, but I also didn’t try it.
I also think the 7% number is wrong. 0.5% seems more realistic. It’s just that you are not able to see the majority of the code. Not everything is checked in or on GitHub.
There isn't any such pressure. These features only happen because someone goes out of their normal job space to push for the necessity for them. All of the design team have full time work on other things. The design and impl only happens if the whole team can be convinced that it is important and worth investing in. Note that a lot of that convincing goes from the tons of feedback we get everywhere. This is anywhere from github, to partners (first, second, third), to conferences, forums, hacker news etc. etc. etc. We have tons coming in constantly. We pick these items up and spend this time on it precisely because we've seen the problems, and how it is affecting the ecosystem, and our future goals there, and we think it is then worthwhile.
I understand you feel this is ilke `!!`. We do not. We think being able to amke a dictionary, and pass in a custom comparer is deeply important. Analyzing code out there, we find that this happens in anywhere from 5-10% of all dicts. That is a ton of codebases and users impacted, and we've already heard from many of them about the friction this causes. Simply discarding that group greatly undercuts one of the core value props that collection expressions brings. A uniform and simple syntax that should suffice for nearly all collection needs.
You may feel differently. That's life in the design world :)
Practically speaking, I've found that Claude never uses collection expressions, so the feature has disappeared from my code. Before AI, the feature was looked at with skepticism by my coworkers. We like writing "var" for all variable declarations. You have to write the type on the left side if you want to declare a variable with a collection expression, and we would never do that otherwise. Can't do `foreach (var x in [1, 2, 3])`. Too often, you have to make specific accommodations in your code to allow the collection expression to be valid.
Collection expressions today are more the sort of thing that a code poet or golfer can do to prettify their code than something a newbie can count on using. It's tough to explain "you can only use this when the collection type is implied in that spot" to a newbie. The value of the base feature is still unproven for me. I'm not sure I agree, without some convincing, that collection expressions made the language more coherent rather than doing https://xkcd.com/927.
> Collection expressions made the language more coherent. Instead of 7 different ways of doing things (some of which were genuinely not efficient), we gave one uniform way of doing it.
I see your point on this. My dislike comes from a mixture of "I don't like how it looks" and "this language already has tons of features".
In terms of looks, I wish it could be more coherent with existing syntax.
List<int> = new {1, 2, 3} and List<int> = {1, 2, 3} are obviously taken up by anonymous types and blocks themselves. Would something like
List<int> = new(capacity: 10)[1, 2, 3]
have been possible? It feels like a combination of target-typed new and the initialization syntax. It involves the "new" keyword, which everybody already associates with constructor calls. It's short. Obviously, I don't know if this even works, maybe there's a parsing issue there (aren't those the most annoying issues in language design haha).
> they found it understandable
Kind of in my experience. Me and the people I've shown this to can easily remember it, but we all agree that it doesn't look like obvious syntax to them. Those two things are quite different to me. Contrast this to something like target-typed new, which immediately made sense to the same people. One might argue that that's fine enough and maybe it is, but I think, the less I have to remember about a language's syntax, the better. I'm going to have to remember many many other things anyway, better keep my memory free for the details of SynchronizationContext and async flow :)
I'm obviously aware that you get tons of bikeshedding comments like this all the time, so I'm sure you've gone through this. But to me, this invented syntax would have been fine. I just don't like the one that actually got in.
Now, the necessity on the other hand: May just be the company I'm working at, but my personal experience has never been that this is a big issue. Sure, it's nice to not have to fall back to explicit initialization a few more times. But personally, this doesn't pass my threshold of "painful enough to warrant additional syntax".
That's the core of my issue: Most, maybe all, of the new features in the language are fine to me in isolation. I may bikeshed about the explicit syntax (see: this thread). But my main issue is that the sum of complexity in the language and the issues beginners have when learning it are steadily increasing. I see this all the time at work.
As you said, this is definitely subjective. And in the end, language design is a very subjective process and maybe C# just won't be for me in the long run. But I wish it would, because at its core I like it, and .NET, a lot. Which is why I will continue to speak for my (subjective) viewpoint.
Well, this turned into a bit of an incoherent rant. I appreciate you exposing yourself to the HN acid pit ;)
> Would something like `List<int> = new(capacity: 10)[1, 2, 3]` have been possible?
Great question. And our design docs, and discussion with the community cover this. The reason that was eliminated as an option (we considered several dozen possible syntaxes) was that this syntax was actively confusing and misleading for people (for several reasons). These include (in no particular order):
1. the use of 'new' indicating that a new value was being allocated. That's not necessarily the case with collection expressions. The compiler is free to be smart here and not allocate if it doesn't need to. `[1, 2, 3]` for example, being constants, can in some cases just point at a data segment in the program.
2. the use of 'new' indicating that a constructor is being called ('new' has always meant that). That's not necessarily the case with collection expressions. Many collection forms (interfaces, immutables, spans, etc) do not go through constructors. This was actively confusing for people.
3. That syntax is already legal. It's an implicit objet creation that is being indexed into.
4. There was strong feedback from many in the community (and the design group, and lots that we talked to) that having things outside the boundary of the `[ ... ]` syntax was actively confusing. One could not easily tell what hte collection was and what wasn't part of it. The idea is that the `[ ... ]` is "the actual value". You know where it starts, where it ends, and what it represents.
--
Of course, at the end of the day, absolutely none of this may sway you. That's why we have a design process and we go through so many options. There were dozens considered here and we had many axes we were trying to optimize for. Overal, this struck a balance of working nicely, and having no major problems going for it (unlike other options).
> I'm obviously aware that you get tons of bikeshedding comments like this all the time, so I'm sure you've gone through this.
Yup :)
Totally ok with us though.
> But personally, this doesn't pass my threshold of "painful enough to warrant additional syntax".
Sure. But that's why we look at the entire ecosystem. And we converse with people who have full codebases they haven't been able to move over because of the lack of this. And we look at the pain that this will cause esp when we get dictionary/key/value support. All of this motivated what was ultimately a tiny feature that cost very little to get in. It was medium bang for very low buck.
And that's worth explaining too. We are always working on some huge features. But they take up a ton of time and need tons of effort and runway. Small features like this are easy to slot in in gaps and help deal with papercuts and friction that are often annoying people.
I'm also not sure that something not being intuitive or natural is necessarily a bad thing in of itself. You state it as if it's so, but you haven't demonstrated that this way of defining a list is worse. You also haven't made any attempt to understand any possible benefit, nor have you attempted any sort of analysis comparing the good and the bad aspects.
No, this is just a constructor call, it's purely syntax sugar for the new() way of doing it.
> I'm also not sure that something not being intuitive or natural is necessarily a bad thing in of itself. You state it as if it's so, but you haven't demonstrated that this way of defining a list is worse.
I would argue that a language having more features, without the feature being helpful, is a bad thing in itself. If the syntax isn't necessary or very convenient in many cases, it shouldn't exist. The syntax being natural (which, absolutely, is a very subjective thing) just makes it less of an issue, I'd say.
Every new syntax added to the language adds cognitive overhead to readers of code. But also, it adds possible interactions with other language features that may be added in the future. Now, the example I brought up doesn't really concern the second point, I'll concede that. But unions? That is a big concept to add to a language that already has decades of existing conventions and tons of other features. How will they interact with generics? Nullable reference types? And, just as importantly: How will they interact with any other features that might be added at some point that we don't even know about?
I'm not against adding syntax sugar. For example, I quite like primary constructors, which is another relatively new C# feature. I think it's a bit annoying that they were kind of added in a roundabout way, by first adding records and then adding primary constructors to classes, but this time they don't define properties but fields...but in the end, it's a nice convenience feature when using constructor injection. Which, whatever one may feel about this, is pretty common in C# code.
But the thing is: If every single feature that's nice for a few use cases gets added to a language, the language will explode. The best example for this is C++. C# is definitely not that bad, far from it, but my point is that I want it to stay that way :)
And you'd have to get the type right, even though the compiler knew the type, because it'd tell you off for getting it wrong. Sometimes it was easiest to just grab the type from the compiler error. ( This example is of course a bit OTT, and it's a bit of a code-smell to be exposing that detail of typing to consumers. )
No-one wants to go back to that, and anyone who says C# is over-complicated I think is forgetting how rough it was in the earliest versions.
While introduction of auto-typing through "var" helped a lot with that, you'd still regularly have to fight if you wanted to properly initialise arrays with values, because the syntax was just not always obvious.
Collection literals are amazing, and now the ability to pass things into the constructor means they can be used when you need constructor parameters too, that's just a good thing as you say.
> The example you point out is the advanced case, someone only needs in a very specific case
This is exactly how C++ landed where it is now. Every time it's "you only need to know that syntax if..." well it ends up everyone has to know that syntax because someone will use it and if you're a responsible programmer you'll end up reading a lot code written from other people.
One issue I have with all these syntax changes is that they are all just more overhead for one to remember. All for what though? Just to just save a few more keystrokes?
I work on multiple applications with different versions of C# and/or Dotnet. I find it quite annoying to have to remember what syntax sugar is allowed in which versions.
If C# did not want verbose syntax, then Java was a poor choice to imitate.
> reduces the amount of dead boilerplate code other languages struggle with.
given that most of the thinks added seem more inspired by other languages then "moved over" from F# the "other languages struggle with" part makes not that much sense
like some languages which had been ahead of C# and made union type a "expected general purpose" feature of "some kind":
- Java: sealed interfaces (on high level the same this C# features, details differ)
- Rust: it's enum type (but better at reducing boilerplate due to not needing to define a separate type per variant, but being able to do so if you need to)
- TypeScript: untagged sum types + literal types => tagged sum types
- C++: std::variant (let's ignore raw union usage, that is more a landmine then a feature)
either way, grate to have it, it's really convenient to represent a `TYPE is either of TYPES` relationship. Which are conceptually very common and working around them without proper type system support is annoying (but very viable).
I also would say that while it is often associated with functional programing it has become generally expected even if you language isn't functional. Comparable to e.g. having some limited closure support.
In isolation, yes, I agree with you. But in the context of the cornucopia of other "carefully evaluated" features mixed into the melting pot, C# is a nightmare of language identities - a jack of all trades, master of none, choose your dialect language. No thanks.
> C# is a nightmare of language identities - a jack of all trades, master of none, choose your dialect language.
I honestly have no idea where you would get this idea from. C# is a pretty opinionated language and it's worst faults all come from version 1.0 where it was mostly a clone of Java. They've been very carefully undoing that for years now.
It's a far more comfortable and strict language now than before.
I can see where he's coming from. For example, `dynamic` was initially introduced to support COM interop when Office add-in functionality was introduced. Should I use it in my web API? I can, but I probably shouldn't.
`.ConfigureAwait(bool)` is another where it is relevant, but only in some contexts.
This is precisely because the language itself operates in many runtime scenarios.
I guess that's a good point. I admit haven't used or seen `dynamic` in so long that I completely forgot about it.
But I'm not sure that's really a problem. Does the OP expect everyone to use an entirely different languages every single context? I have web applications and desktop applications that interact with Office that share common code.
Even `dynamic` is pretty nice as far as weird dynamic language features are concerned.
Interestingly enough `.ConfigureAwait(bool)` is entirely the opposite of `dynamic` -- it's not a language feature at all but instead a library call. I could argue that might instead be better as a keyword.
> That’s correct, most of ASP.NET Core doesn’t use ConfigureAwait(false) and that was an explicit decision because it was deemed unnecessary. There are places where it is used though, like calls to bootstrap ASP.NET Core (using the host) so that scenarios you mention work. If you were to host ASP.NET Core in a WinForms or WPF application, you would end up calling StartAsync from the UI thread and that would do the right thing and use ConfigureAwait(false) internally. Request processing on the other hand is dispatching to the thread pool so unless some other component explicitly set a SynchronizationContext, requests are running on thread pool threads.
>
> Blazor on the other hand does have a SynchronizationContext when running inside of a Blazor component.
So I bring this up as a case of how supporting multiple platforms and runtime scenarios does indeed add some layer of complexity.
> It is a library call, but one that is tied to the behavior of a language feature (async/await).
This is a good example of C# light-touch on language design. Async/await creates a state machine out of your methods but that's all it does. The language itself delegates entirely to platform/framework for the implementation. You can swap in your own implementation (just as it possible with this union feature)
> So I bring this up as a case of how supporting multiple platforms and runtime scenarios does indeed add some layer of complexity.
I agree that's true. A language that doesn't support multiple platforms and runtime scenarios can, indeed, be simpler. However that doesn't make the task simpler -- now you just have to use different languages entirely with potentially different semantics. If your task is just one platform and one runtime scenario, the mental cost here is still low. You don't actually need to know those other details.
> This is a good example of C# light-touch on language design.
Is it? F# code doesn't even need ConfigureAwait(false), one simply uses backgroundTask{} instead of task{} to ignore SynchronizationContext.Current, and this didn't require any language design changes at all (both are computation expressions), but it would for C# precisely because it delegates this choice to the framework.
dynamic was also added as part of DLR, initially designed for IronPython and IronRuby support.
This inspired the invokedynamic bytecode in the JVM, which has brought many benefits and much more use than the original .NET features, e.g. how lambdas get generated.
If it’s not for you I guess that is ok. But from your comment I would also deduct that you never professionally used it.
After so many different languages it’s the only one I always comeback to.
The only things that I wish for are: rusts borrow-checker and memory management. And the AOT story would be more natural.
Besides that, for me, it is the general purpose language.
Yes, C# is a jack of all trades and can be used at many things. Web, desktop mobile, microservices, CLI, embedded software, games. Probably is not fitted for writing operating systems kernels due to the GC but most areas can be tackled with C#.
Many systems programming languages with GC have existed since the 1970's, we don't seem most adoption mostly due to developer culture, and monetary issues with management.
C# is a perfect example of feature envy, but because "Java sucks" C# must be the best thing ever in the world of computing. Orthogonality and coherence be damned.
I think, this is a calculation to understand if an upgrade of hw3 to hw4 actually solves the problem or if hw3 must be updated to hw5.
One upgrade is more economical than two, but I would be annoyed for sure as well.
reply