HackerTrans
TopNewTrendsCommentsPastAskShowJobs

Metasyntactic

no profile record

comments

Metasyntactic
·2 months ago·discuss
Yes. We do. And unions should work well in F#. It's designed to be a very easy pattern for all CLR languages and compilers to understand (including F#).
Metasyntactic
·2 months ago·discuss
Hi there! One of the C# lang designers here. I discuss that here: https://news.ycombinator.com/item?id=48255658

Note that my expectation would be that the non-boxed form would be as trivial as adding `[NonBoxedUnion(SomeImplStrategyChoiceEnum)]` (or `[NonBoxedUnion]` for some default strategy choices that likely are ok).

This would give you extremely fine grained flexible choice on how you wanted your non-boxing union to work. There's no single right answer. There are just tradeoffs in terms of space/speed/copying-costs/memory-safety/etc.

I think it would make the most sense as people who care about boxing will have very different views and needs in terms of things like space, casting costs, copying speed etc.

The vast vast majority of users do not need to care at all. And for that, a boxed approach works exceptionally well.
Metasyntactic
·2 months ago·discuss
Hi there. One of the C# lang designers here.

You're correct. The unions we're working on right now are 'type unions'. So the type is inherent in the union distinction, and you would not be able to distinguish that case. That said, we're also looking at full blown discriminated unions (you can look at one of my proposals for that here: https://github.com/dotnet/csharplang/blob/main/meetings/work...), which would allow for that. Syntax entirely tbd, but you'd do something like:

  enum struct Either<T1, T2> // or enum class
  {
     First(T1 value),
     Second(T2 value)
  }
We view these features as complimentary. Indeed, if you look at the extended enum proposal, you'll see it builds on top of unions and closed types (another proposal coming in the next version of the lang).
Metasyntactic
·2 months ago·discuss
Hi there, one of the C# lang designers here. I discuss this a bit in https://news.ycombinator.com/item?id=48255658.

Contrary to what a lot of people guess, boxing is actually a really good strategy most of the time. And, is indeed what many people are doing here anyways. The design supports a pattern that allows for non-boxing, and I expect that we will both supply an implementation for that with reasonable defaults, and that source generators will be a great way to augment this to get highly specialized impl strategies for non-boxing depending on the varying domain needs any specialized customer may have.
Metasyntactic
·2 months ago·discuss
Hi there! C# language designer here, and one of the people working on unions.

Boxing is not something inherently to be avoided. It actually can work better in many (most?) use cases, and avoids a lot of problems that non-boxing approaches often cause (like tearing and copy costs).

It's try that the non boxing pattern could be implemented by us. And it's very reasonable that that is something we may do post this release. However, it's a non-trivial area. There's no one correct 'non-boxed' implementation. For example, do you have separate fields for all your unmanaged data? or do you have a blob of bytes that is large enough to align all your unmanaged data from teh largest set of of unmanaged fields, and you unsafe index into that?

Similar question for managed data. Do you have strongly typed fields for that data? Or do you attempt to use objects, to compact to as little space as possible? The former avoids casting costs. The latter allows you to minimize space. You can also potentially use unsafe casts. But those might introduce memory holes in tearing situations. etc. etc.

Because of this, i think the best outcome is to define the pattern (which we've done) and then use generators to allow you to control precisely the impl strategy, giving you all the bells and knobs you want to best fit your domain.
Metasyntactic
·3 months ago·discuss
> 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.
Metasyntactic
·3 months ago·discuss
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 :)
Metasyntactic
·3 months ago·discuss
> Having to opt-in like the current preview shows it looks like a PITA that I'd quickly become tired of.

My belief is that we will have a `union struct` just like we have `record` and `record struct`. Where you can simply say you want value-type, non-boxing, behavior by adding the `struct` keyword. This feels very nice to me, and in line with how we've treated other similar types. You'll only have to state this on the decl point, so for each union type it's one and done.
Metasyntactic
·3 months ago·discuss
How do you define "objectively illogical" here?
Metasyntactic
·3 months ago·discuss
Sure, as an example: https://github.com/dotnet/csharplang/blob/main/meetings/work...

Again, very rough. We go round and round on things. Loving to decompose, debate and determine how we want to tackle all these large and interesting areas :)
Metasyntactic
·3 months ago·discuss
> I guess I overdramatized the situation a bit :) It's a passionate topic for me; as somebody who has been using C# at work for 10 years now, I'm just not happy with the direction the language has been taking.

You should come engage with us on this then :)

We do all our design in the open on github. And a lot of us are available to chat and discuss all this stuff in Discord and the like :)

> C# is not using its budget very wisely in my opinion.

I can promise you. Every feature you think are great had similar detractors over the years. Every Single One :)
Metasyntactic
·3 months ago·discuss
Hi there. Designer of this feature :D

> is non-intuitive and unnecessary.

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.
Metasyntactic
·3 months ago·discuss
Hi there! One of the C# language designers here, working on unions.

We're very interesting in this space. And we're referring to it as, unsurprisingly, 'anonymous unions' (since the ones we're delivering in C#15 are 'nominal' ones).

An unfortunate aspect of lang design is that if you do something in one version, and not another, that people think you don't want the other (not saying you think that! but some do :)). That's definitely not the case. We just like to break things over many versions so we can get the time to see how people feel about things and where are limited resources can be spent best next. We have wanted to explore the entire space of unions for a long time. Nominal unions. Anonymous unions. Discriminated unions. It's all of interest to us :)
Metasyntactic
·3 months ago·discuss
Hi there! One of the C# language designers here, working on unions. And the author of that feature :D

So I'm happy to discuss the thinking here. It's not about saving keystrokes. It's about our decision that users shouldn't have 7 (yes 7) different ways of creating collections. They should just be able to target at least 99% of all cases where a collection is needed, with one simple and uniform syntax across all those cases.

When we created and introduced collection expressions, it was able to get close to that goal. But there were still cases left out, leaving people in the unenviable position of having to keep their code inconsistent.

This feature was tiny, and is really intended for those few percent of cases where you were stuck having to do things the much more complex way (see things like immutable builders as an example), just to do something simple, like adding an `IEqualityComparer<>`. This was also something that would become even more relevant as we add `k:v` support to our collections to be able to make dictionaries.
Metasyntactic
·3 months ago·discuss
Hi there! One of the C# language designers here, working on unions. We're extremely interested in discriminated unions. A real problem is that there so much interest, with many varying proposals on how best to do them. It's a lot to go through, and we've found some of the best designs layer on standard unions. So we like this ordering to lay the foundation for discriminated unions to built on top of! :)
Metasyntactic
·3 months ago·discuss
Hi there! One of the C# language designers here, working on unions. All the different options have tradeoffs. As an example, the non-boxing options tear, which can be problematic. And, we have a lot of experience implementing the simple, reference-type, approach for types that make a lot of sense to people, but then adding a lightweight, value-type version for people who care about that later. See tuples, as well as records.

I expect the same will old here. But given the former group is multiple orders of magnitude higher than the latter, we tend to design the language in that order accordingly.

Trust me, we're very intersted in the low-overhead space as well. But it will be for more advanced users that can accept the tradeoffs involved.

And, in the meantime, we're designing it in C#15 that you can always roll the perfect implementation for your use case, and still be thought of as a union from teh language.
Metasyntactic
·3 months ago·discuss
Hi there! One of the C# language designers here, working on unions. We're interesting in both forms! We decided to go with this first as felt there was the most value here, and we could build the named form on top of this. In no way are we thinking the feature is done in C#15. But it's part of our ongoing evolution.

If you're interested, i can point you to specs i'm writing that address the area you care about :)
Metasyntactic
·6 months ago·discuss
I wrote several of typescript's initial compilers. We didn't use red/green for a few reasons:

• The js engines of the time were not efficient with that design. This was primarily testing v8 and chakra (IE/edge's prior engine).

• Red/green takes advantage of many things .net provides to be extremely efficient. For example structs. These are absent in js, making things much more costly. See the document on red-green trees I wrote here for more detail: https://github.com/dotnet/roslyn/blob/main/docs/compilers/De...

• The problem domains are a bit different. In Roslyn the design is a highly concurrent, multi-threaded feature-set that wants to share immutable data. Ts/JS being single threaded doesn't have the same concerns. So there is less need to efficiently create an immutable data structure. So having it be mutable meant working well with the engines of the time, with sacrificing too much.

• The ts parser is incremental, and operates very similarly to what I describe in for Roslyn in https://github.com/dotnet/roslyn/blob/main/docs/compilers/De.... However, because it operates on the equivalent of a red tree, it does need to do extra work to update positions and parent pointers.

Tldr, different engine performance and different consumption patterns pushed us to a different model.
Metasyntactic
·6 months ago·discuss
> I'm curious how you can make such statements involving absolute time values, without specifying what the minimum hardware requirements are.

That's a very fair point. In this case. I'm using the minimum requirements for visual studio

> Do you claim your engine would give me sub-ms feedback on such hardware?

I would expect yes, for nearly all edits. See the links I've provided in this discussion to our incremental parsing architecture.

Briefly, you can expect an edit to only cause a small handful of allocations. And the parser will be able to reuse almost the entirety of the other tree, skipping over vast swaths of it (before and after the edit) trivially.

Say you have a 100 types, each with a 100 members, each with 100 statements. An edit to a statement will trivially blow through 99 of the types, reusing them. Then in the type surrounding the edited statement, it will reuse 99 members. Then in the edited member, it will reuse 99 statements and just reparse the one affected one.

So basically it's just the computer walking 297 nodes (absolutely cheap on any machine), and reparsing a statement (also cheap).

So this should still be microseconds.

--

Now. That relates to parsing. But you did say: would give me sub-ms feedback on such hardware?

So it depends on what you mean by feedback. I don't make any claims here about layers higher up and how they operate. But I can make real, measured, claims about incremental parsing performance.
Metasyntactic
·6 months ago·discuss
> Hmm, the strong reason could be latency and layout stability. Tree-sitter parses on the main thread (or a close worker) typically in sub-ms timeframes

One of the designers/architects of 'Roslyn' here, the semantic analysis engine that powers the C#/VB compilers, VS IDE experiences, and our LSP server.

Note: For roslyn, we aim for microsecond (not millisecond) parsing. Even for very large files, even if the initial parse is milliseconds, we have an incremental parser design (https://github.com/dotnet/roslyn/blob/main/docs/compilers/De...) that makes 99.99+% of edits happen in microseconds, while reusing 99.99+ of syntax nodes, while also producing an independent, immutable tree (thus ensuring no threading concerns sharing these trees out to concurrent consumers).

> you introduce a flash of unstyled content or color-shifting artifacts every time you type, because the round-trip to the server (even a local one) and the subsequent re-tokenization takes longer than the frame budget.

This would indicate a serious problem somewhere.

It's also no different than any sort of modern UI stack. A modern UI stack would never want external code coming in that could ever block it. So all, potentially unbounded, processing work will be happening off the UI thread, ensuring that that thread is always responsive.

Note that "because the round-trip to the server (even a local one)" is no different from round-tripping to a processing thread. Indeed, in Visual Studio that is how it works as we have no need to run our server in a separate process space. Instead, the LSP server itself for roslyn simply runs in-process in VS as a normal library. No different than any other component that might have previously been doing this work.

> Relying on LSP for the base layer makes the editor feel sluggish.

It really should not. Note: this does take some amount of smart work. For example, in roslyn's classification systems we have a cascading set of classifying threads. One that classifies lexically, one for syntax, one for semantics, and finally, one for embedded languages (imagine embedded regex/json, or even C# nested in c#). And, of course, these embedded languages have cascading classification as well :D

Note that this concept is used in other places in LSP as well. For example, our diagnostics server computes compiler-syntax, vs compiler-semantics, versus 3rd-party analyzers, separately.

The approach of all of this has several benefits. First, we can scale up with the capabilities of the machine. So if there are free cores, we can put them to work computing less relevant data concurrently. Second, as results are computed on some operation, it can be displayed to the user without having to wait for the rest to finish. Being fine-grained means the UI can appear crisp and responsive, while potentially slower operations take longer but eventually appear.

For example, compiler syntax diagnostics generally take microseconds. While 3rd-party analyzer diagnostics might take seconds. No point in stalling the former while waiting for the latter to run. LSP makes multi-plexing this stuff easy