|
Xarn posted:Congratulations, your modern language handles enums even worse than C++ yeah. i think everyone who uses c# is aware of and disappointed in how shittily enums are handled. i'm personally disappointed the language team hasn't shown any interest in trying to improve them
|
# ? Feb 13, 2021 01:54 |
|
|
# ? May 30, 2024 21:20 |
|
Kilson posted:Everyone knows booleans have 3 values: True, False, and FileNotFound https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.core.msotristate?view=office-pia
|
# ? Feb 13, 2021 02:07 |
|
Dylan16807 posted:https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.core.msotristate?view=office-pia Why does this 3-state bool seem to have 5 values?
|
# ? Feb 13, 2021 03:20 |
|
OddObserver posted:Why does this 3-state bool seem to have 5 values? Only two of them are supported, so it's fine.
|
# ? Feb 13, 2021 03:33 |
|
Really disliked C# enums, it's a half-baked abstraction that's useful for c-api integration, but less useful for writing modern code. While it's sometimes the best solution, it's not usually a good idea to create an enum with the idea of adding new values over time. Adding new values is a pain because you have to update all call sites, some of which may be out of your control. This is where interfaces really shine, all you have to do is create a new implementation, and you are required to implement all operations and they are all in the same place. On the flip-side, adding a new operation to an interface is hard, and adding a new operation for an enum is easy.
|
# ? Feb 13, 2021 09:24 |
|
dwazegek posted:
Hold up, C# lets you just blast bits into memory? I thought it was supposed to be a modern and safer language, not like C++
|
# ? Feb 13, 2021 20:19 |
|
Xarn posted:Hold up, C# lets you just blast bits into memory? I thought it was supposed to be a modern and safer language, not like C++
|
# ? Feb 13, 2021 20:23 |
|
Xarn posted:Hold up, C# lets you just blast bits into memory? I thought it was supposed to be a modern and safer language, not like C++ C# lets you use the "unsafe" language keyword to drop into a context that lets you work with pointers and handle memory outside the runtime's safety fences. It's not entirely unmanaged, but you can use features like the "fixed" keyword to make sure that chunks of memory aren't moved around under the covers by the runtime, or GC'd while you still need them. "Unsafe code - C# language specification posted:While practically every pointer type construct in C or C++ has a reference type counterpart in C#, nonetheless, there are situations where access to pointer types becomes a necessity. For example, interfacing with the underlying operating system, accessing a memory-mapped device, or implementing a time-critical algorithm may not be possible or practical without access to pointers. To address this need, C# provides the ability to write unsafe code. Java effectively has the same thing through the sun.misc.unsafe library, except the pointers are plain old ints/longs, and the behavior is only a de facto standard instead of part of the language itself. There was a bunch of drama about formally deprecating it, because it's been used for bits of performance-critical code in a lot of web server frameworks.
|
# ? Feb 13, 2021 20:32 |
|
C# is willing to sacrifice ideological purity to be a more useful tool. That's not unusual, pretty much every general-purpose systems language makes the same decision. Even Rust lets you do the same thing.Deffon posted:Really disliked C# enums, it's a half-baked abstraction that's useful for c-api integration, but less useful for writing modern code. enums and interfaces are completly orthogonal concepts that don't have anything to do with each other
|
# ? Feb 13, 2021 21:17 |
|
Foxfire_ posted:enums and interfaces are completly orthogonal concepts that don't have anything to do with each other If you know all the subclasses/enum instances and operations that you could ever want for your type at the start you could use either or. You still have to define your operations for the enums as free-standing functions, but given that you could e.g. define a shape hierarchy using either. To attach state, you also need unsafe casting in the case of C#, but more functional languages solve it with sum types. I was just pointing it out that the issues of updating switches in languages like C# could be avoided by using interfaces, with other potential drawbacks since I don't know the details of this particular case. Deffon fucked around with this message at 21:46 on Feb 13, 2021 |
# ? Feb 13, 2021 21:40 |
|
Foxfire_ posted:C# is willing to sacrifice ideological purity to be a more useful tool. That's not unusual, pretty much every general-purpose systems language makes the same decision. Even Rust lets you do the same thing. ADTs and Interfaces are very similar concepts that make opposite trade offs, and while C# enums are lame and not ADTs they’re an important piece of creating one in C#. Anything which does not involve external extensibility which uses an interface could be refactored to use an enum instead.
|
# ? Feb 13, 2021 22:45 |
|
I guess you could try to shove all the logic that a thing uses into some interface'd thing, but it seems to me like it'd be usually be a mess and more confusing than just doing the simple thing. 99% of enums are going to be things like: - This serial receiver has a state that is one of IDLE, RX_NORMAL, or RX_ESCAPE_BYTE - This window is either MINIMIZED, MAXIMIZED, FLOATING, or FULLSCREEN - The game's difficulty is one of EASY, NORMAL, or HARD Doing something like having WindowState be an interface with functions for every operation you're going to ever do that cares about a window is going to be grouping logic from lots of unrelated things and make reading any particular thing a lot harder. e: I guess kind of are you more likely to be asking 'what does this particular thing do for various enumerated values?' vs 'how do the various enumerations change the entire universe of stuff?'. I could see the second being useful occasionally, like you might want to see all the code that changes in a game by difficulty in one place, but usually the first seems more likely. Foxfire_ fucked around with this message at 00:19 on Feb 14, 2021 |
# ? Feb 14, 2021 00:09 |
|
That's the opposite set of tradeoffs coming into play. ADTs make it easy to define new operations on the set of types but make it hard to add new types, while Interfaces make it easy to add new types but hard to add operations to the set of types. The "expression problem" is trying to come up with a way to make both easy.
|
# ? Feb 14, 2021 01:19 |
|
Foxfire_ posted:C# is willing to sacrifice ideological purity to be a more useful tool. That's not unusual, pretty much every general-purpose systems language makes the same decision. Even Rust lets you do the same thing. And if the safe parts of your language then have to safeguard against bool having 256 potential states/representations, you are doing it wrong.
|
# ? Feb 14, 2021 07:41 |
|
Xarn posted:And if the safe parts of your language then have to safeguard against bool having 256 potential states/representations, you are doing it wrong. It's only if you switch on a boolean, which no one does because why would you. I guess it could also still be an issue if you use unsafe code to read the bits of a boolean and only expect 0x00 or 0x01, but then you're really doing some stupid stuff. Using it in as part of a boolean expression, or in an if/while/for/ternary/whatever statement, will work correctly. Even a direct comparison to another boolean works as it should (i.e. true == true, regardless of whatever bits the true value has). Also, as I mentioned, it seems to have been fixed in later compiler or language versions. It really seems to have been a bug and not a flaw in the actual language.
|
# ? Feb 14, 2021 17:15 |
|
I would expect switching on a boolean to compile to the exact same code as an if statement
|
# ? Feb 14, 2021 17:24 |
|
Xarn posted:And if the safe parts of your language then have to safeguard against bool having 256 potential states/representations, you are doing it wrong. or you could be vb, where booleans are 16-bit wides, true = -1, false = 0, casting any other value to boolean means true. if you've ever wondered why variant_bool = -1 when doing windows programming, that's why.
|
# ? Feb 14, 2021 17:40 |
|
Objective-C used signed char as its boolean type for awhile, so you could gently caress yourself over by checking if (x == YES) because YES was just 1. (if (x) works ok.) You'd also get people taking sorta reasonable shortcuts like returning a collection's count as a boolean to indicate "false when empty", which is how you might end up with non-0 or 1 as a boolean value surprisingly often,Bruegels Fuckbooks posted:or you could be vb, where booleans are 16-bit wides, true = -1, false = 0, casting any other value to boolean means true. if you've ever wondered why variant_bool = -1 when doing windows programming, that's why. If -1 is represented as 0xFFFF then this makes some kind of sense, lotta ones in that representation.
|
# ? Feb 14, 2021 17:48 |
|
xtal posted:I would expect switching on a boolean to compile to the exact same code as an if statement It’s easy to go to sharplab and verify this: https://sharplab.io/#v2:CYLg1APgAgT...g5OBgC+lrlI2UA=
|
# ? Feb 14, 2021 18:16 |
|
pokeyman posted:If -1 is represented as 0xFFFF then this makes some kind of sense, lotta ones in that representation.
|
# ? Feb 14, 2021 19:21 |
|
Bruegels Fuckbooks posted:or you could be vb, where booleans are 16-bit wides, true = -1, false = 0, casting any other value to boolean means true. if you've ever wondered why variant_bool = -1 when doing windows programming, that's why. True being -1 goes back to at least the original Microsoft BASIC, possibly even farther. (PET BASIC is the only Microsoft product I ever voluntarily used.)
|
# ? Feb 14, 2021 21:47 |
|
Xarn posted:And if the safe parts of your language then have to safeguard against bool having 256 potential states/representations, you are doing it wrong. It's also kind of a separate issue. C# enums could have been defined so that it wasn't permitted to have an unlisted integer value in one (with normal code doing runtime checks to enforce that). Then a switch wouldn't need paths for handling unlisted values since the runtime could assume they never happened. If you used unsafe code to make one anyway, you'd crash the runtime, but that's not different from all the other ways you can use unsafe code to violate assumptions and crash the runtime (put random garbage in a pointer/reference). Boolwise, C++ is still worse since it doesn't prescribe any particular bit representation. It mandates the conversions, (true->1, false->0, 0->false, nonzero->true), but not what bits are actually stored in the bool. If you memcpy() to or from a bool, you technically have no portable expectation for what they mean. memcpy()-ing stuff in could also plausibly break real compilers if they implement an equality test by subtracting and then testing a zero flag.
|
# ? Feb 15, 2021 03:41 |
|
That 0 is false and !=0 is true may look like a hack to programmers that live in high level languages, but make generally everything so much simpler that it ascend to the level of Holy. Is really useful. It conducts to code that is easy to write and easy to read, instead of a novelization of a Tolkien movie but with objects.
|
# ? Feb 15, 2021 14:06 |
|
Foxfire_ posted:That's impossible to avoid. If you could algorithmically prove that some unsafe construct didn't break any other state and always produced valid bit patterns in everything it touched, it wouldn't need to be an unsafe construct to begin with. Right, but you should minimize how much the unsafe parts can infect you back, or at least how much you safeguard against that. Take Rust as an example: the compiler does basically no checks on unsafe blocks, and if your unsafe block messed something up terribly, that's your own drat problem. It does not try adding code to maybe catch some of those mess ups.
|
# ? Feb 15, 2021 18:30 |
|
https://github.com/angular/vscode-ng-language-service/issues/1112 Ignore the actual GitHub issue. Look at the font the guy is using in his editor. Absolute barbarian.
|
# ? Feb 16, 2021 13:03 |
|
LOOK I AM A TURTLE posted:Look at the font the guy is using in his editor
|
# ? Feb 16, 2021 13:09 |
|
Tei posted:That 0 is false and !=0 is true may look like a hack to programmers that live in high level languages, but make generally everything so much simpler that it ascend to the level of Holy. It maps to how most CPUs work. Want to see if something is zero? Load it into a register and check the Z bit in the status register. Super fast.
|
# ? Feb 16, 2021 17:14 |
|
it maps to twitter also. you can make up anything you want and pass it off as true! unless you happen to pick the one thing that is false (that day)
|
# ? Feb 17, 2021 04:55 |
|
DELETE CASCADE posted:it maps to twitter also. you can make up anything you want and pass it off as true! unless you happen to pick the one thing that is false (that day) this post also evaluate to true!
|
# ? Feb 17, 2021 12:45 |
|
redleader posted:yeah. i think everyone who uses c# is aware of and disappointed in how shittily enums are handled. i'm personally disappointed the language team hasn't shown any interest in trying to improve them Simply roll your own https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types ... and you can't use them in a switch anmore, solving two problems
|
# ? Feb 18, 2021 17:43 |
|
Munkeymon posted:Simply roll your own https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types is this one of those things where it's a contest to see how many problems you can find with the linked code?
|
# ? Feb 18, 2021 18:08 |
|
I don't think it's meant as a complete example, but it's also not the one I was trying to find that uses the curiously recurring template pattern instead of IComparable
|
# ? Feb 18, 2021 22:56 |
|
I've been using this enum pattern recently in C# (I typed this up from memory so it probably has errors):C# code:
- The code that produces results looks very similar to regular enum code. - You can use the switch expression in the code that consumes results. - You can attach extra data to each enum value, like with actual union/sum types. - It's easy to change the return data associated with each output type, without changing a lot of function signatures. A good example would be adding an error type to the AccessDenied value. Cons: - The code where you define the enum is very verbose. - Unlike regular enums, the type names leak into the global namespace. - They're also less discoverable than with enum values, since they're just classes. - You still can't guarantee exhaustive pattern matches at compile time. - (edit: the performance is probably pretty bad) I'm a little torn on the pattern, but overall I prefer it to standard enums. Hopefully C# just gets real sum types at some point. LOOK I AM A TURTLE fucked around with this message at 11:10 on Feb 20, 2021 |
# ? Feb 20, 2021 09:52 |
|
You could define the derived types as nested types of the base class, that would make the derived types a bit more discoverable, although then you'd get name clashes with the properties. Making the base class' ctor protected internal or even private protected would prevent types from being added outside the assembly. Still no guarantee that the switch statements are exhaustive, but with regular enums they aren't either... Another downside is that, since it's a class, there is no compile time way to prevent null values. The newer non-nullable reference type stuff helps, but it offers no guarantees.
|
# ? Feb 20, 2021 17:34 |
|
You can do that with F# while you wait for C# to support it properly. It's so easy to refactor and maintain as well. And the match expressions are dreamy.code:
|
# ? Feb 20, 2021 19:41 |
|
dwazegek posted:You could define the derived types as nested types of the base class, that would make the derived types a bit more discoverable, although then you'd get name clashes with the properties. 1. Yeah, I've considered using nested classes. It solves not only the discoverability problem but also the global namespace leak problem. The downside is that the names get very long. 2,. I do make the abstract class constructor protected, yeah. In my actual code I also have a generic Result<T> that I reuse for different result types, since the NotFound/AccessDenied/Success pattern comes up a lot for me, which helps with the boilerplate problem on the definition side. 3. NNRTs are indeed an important part of the puzzle. They don't give an ironclad guarantee, but the level of safety they give is no worse than what you already have with .NET style enums (arguably better). I probably wouldn't use this pattern without NNRTs enabled. Ola posted:You can do that with F# while you wait for C# to support it properly. It's so easy to refactor and maintain as well. And the match expressions are dreamy. That's neat, yeah. C# actually also has fairly powerful pattern matching now, but the missing ingredient is proper algebraic types.
|
# ? Feb 20, 2021 19:48 |
|
Ola posted:You can do that with F# while you wait for C# to support it properly. It's so easy to refactor and maintain as well. And the match expressions are dreamy. AFAIK you can do all of this in C# 9, with a very similar syntax. Edit: C# 9 isn't officially supported on anything other than Net5, but, unofficially, if you enable langversion latest you can use some features, including those patten matching features on earlier versions as well, including even older framework versions as far back as at least 4.7.2. dwazegek fucked around with this message at 11:54 on Feb 21, 2021 |
# ? Feb 21, 2021 11:48 |
|
dwazegek posted:You could define the derived types as nested types of the base class, that would make the derived types a bit more discoverable, although then you'd get name clashes with the properties. I thought about this some more, and I think using nested classes actually solves most of the problems with the pattern. As you say it solves the issues with discoverability, and also the namespace leak issue. The name clashes between the types and the properties aren't a big deal, since it often makes decent sense to have a suffix on the end of the type names like in my example. When the classes are nested you can actually use a completely private constructor on the base class, since nested classes can access private stuff on the containing class. That way you can make it impossible to create other subclasses without resorting to runtime reflection tricks. And the best part is that if you want to you can even avoid having to type out FooResult.FooNotFoundResult in the consumer code if you use using static FooResult. Now I'm thinking the only downsides of the class enum pattern are the verbosity at the definition site and the performance hit. I know the pattern has been known for a long time (e.g. https://codeblog.jonskeet.uk/2014/10/23/violating-the-smart-enum-pattern-in-c/), but I think the value proposition got a lot stronger when pattern matching entered the picture.
|
# ? Feb 21, 2021 12:42 |
|
LOOK I AM A TURTLE posted:I know the pattern has been known for a long time (e.g. https://codeblog.jonskeet.uk/2014/10/23/violating-the-smart-enum-pattern-in-c/), but I think the value proposition got a lot stronger when pattern matching entered the picture. On that page: quote:The class is not sealed, but it prevents other classes from subclassing it by using a private constructor. Why? Skeet doesn't explain why it's done this way instead of by sealing it. Presumably he thinks this is obvious, but it isn't to me. e: On looking at it a second time, the class contains nested private subclasses of itself. When I glanced at the code for the first time, I took the nested classes to be methods. Hammerite fucked around with this message at 14:42 on Feb 21, 2021 |
# ? Feb 21, 2021 14:38 |
|
|
# ? May 30, 2024 21:20 |
|
LOOK I AM A TURTLE posted:And the best part is that if you want to you can even avoid having to type out FooResult.FooNotFoundResult in the consumer code if you use using static FooResult. I have used this pattern and I would just avoid repeating the base class words from the subclass name. So its full name would be FooResult.NotFound, much like an enum in fact.
|
# ? Feb 21, 2021 19:11 |