digitalmars.D - enums and version/static if/"inheritance"
- Witold Baryluk (156/156) Jul 28 I do often develop some low level codes, often interacting with
- Denis Feklushkin (3/3) Jul 29 On Sunday, 28 July 2024 at 22:20:47 UTC, Witold Baryluk wrote:
- Nick Treleaven (38/55) Jul 29 Possible workaround if each enum member is given a specific
- Nick Treleaven (25/40) Jul 30 Of course, the mixin there is not doing anything more than a
- Witold Baryluk (8/49) Jul 30 The struct trick is pretty nice. I will give it a go. At least it
- IchorDev (32/32) Jul 30 I’ve recently made a API for doing this in BindBC-Common. You can
- Witold Baryluk (15/47) Jul 30 All nice and dandy. I know how to write mixins, there are many
- IchorDev (29/41) Jul 30 I am just providing an existing solution that is tailored to
- Walter Bright (43/43) Jul 30 This comes up from time to time.
- IchorDev (11/36) Jul 31 Could you please rewrite the D version of [this
- Walter Bright (6/7) Jul 31 Indeed, it does. Consider the evolution of expertise:
- IchorDev (25/32) Jul 31 He this, he that. Where is ‘they’ when you need it?
- Walter Bright (10/10) Aug 01 If you really want conditional enums, you can do this:
- Witold Baryluk (85/95) Aug 04 ```
- Walter Bright (3/3) Aug 06 It depends on just what you want an enum for. The example I gave was for...
- Walter Bright (45/53) Aug 01 Not perceptibly faster (the lexer is very fast), and fewer lines is not ...
- Max Samukha (4/26) Jul 31 It's been said maybe a billion times why this solution rarely
- Richard (Rikki) Andrew Cattermole (4/32) Jul 31 Yeah not supporting this, just doesn't fit D anymore.
- Walter Bright (3/4) Jul 31 People try all kinds of things in programming, eventually they evolve to...
- Richard (Rikki) Andrew Cattermole (5/11) Jul 31 Luckily we have Dscanner, so if it turns out a pattern gets too complex
- monkyyy (8/13) Jul 31 ?
- Walter Bright (52/54) Jul 31 I know, it comes up regularly.
- IchorDev (5/6) Jul 31 Yes it feels a bit like a dogma, which is not really the D way
- Walter Bright (6/7) Aug 01 All languages have their particular dogmas.
- An Pham (32/71) Jul 31 The clean way is using "mixin" & "import"
I do often develop some low level codes, often interacting with Linux kernel, or some embedded systems. Where unfortunately some definitions of various constants and interfaces vary vastly between architectures or oses, or compilation options. My today complain would be with `enums`. A good context is just checking various `mman.d` files in phobos, where a number of top level `enum` values are defined using mix of version and static ifs, or even a bit of logic for fallback. Unfortunately this only works for top level enum values. It does not work for named enum types. A simple example ```d enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } version E = 9, } // static assert(checkUniqueValues!FOO); ``` Is simply not supported (not even that surprising considering that enum members are separated by comma, not a semicolon). Same with `static if`. Doing `enum FooBase { ... } version () ... enum FOO : FooBase { ... }` will not work (it does something else that expected) One will say, how about this: ```d enum FOO { A = 5, B = 6, C = ((){ version (x86_64) return 7; else return 17; })(), E = 9, } // static assert(checkUniqueValues!FOO); ``` That is unfortunately not good enough, and very verbose. There are often multiple values that need to have these branching structure, and now one is forced to repeat it multiple time. And even worse, this only allows defining enum members that only differ in value. One cannot use this method to decide if enum member/value exists or not (or maybe put deprecated or other UDA on them conditionally). Example might be on linux x86_64 one should only use `HUGE_TLB_2MB` and `HUGE_TLB_1GB`, but on ppc64, different values are allowed, like only `HUGE_TLB_16MB` is allowed. In linux uapi / libc all values are defined on all archs, but using wrong ones will definitively will cause a runtime error, and it would be preferably that library just hides unsupported ones by default. Another example might be enum with some values hidden because they are broken / deprecated, but some special `version` could be used to enabled if one knows what they are doing (Example on linux might be some syscall numbers, which are deprecated or essentially broken, and superseded by better interfaces, example might be `tkill`, or `renameat`). And last consideration, again for `mmap` flags. There is a set of mmap flags defined by posix (the actual values might different between systems and even archs). And a bunch of extra flags supported on various systems. In Phobos these is done by just having top level enum values (not part of any enum type), and importing proper modules with these values. This makes impossible to write "type-safe" wrappers around `mmap` (lets assume we are calling Linux kernel directly so we can ignore all glibc / musl stuff, and we can invent a new interface), and flags must be just int. In ideal world, one would `import ...linux.mman : mmap`, and it would have signature: ```d module ...linux.mman; version (X86_64) { void* mmap(void* p, size_t, MMapProt prot, MMapFlags flags, int fd, off_t off) { return cast(void*)syscall!(Syscall.MMAP)(cast(ulong)p, cast(uint)prot, ...) } } ``` where for example MMapFlags would be (hypothetically): ```d module ...linux.mman; enum MMapFlags : imported!"...posix.mman").MMapFlags { // some extra linux specific flags MAP_DROPPABLE = 0x08, MAP_UNINITIALIZED = 0x4000000, } ``` and common ones: ```d module ...posix.mman; enum MMapFlags { version (linux) { MAP_PRIVATE = 0x02, } // ... } ``` So only options are: 1. Redefine entire struct of the enum (including comments, and expressions, i.e. if some enum member values are expression, like `MAP_HUGE_1GB = 30U << HUGETLB_FLAG_ENCODE_SHIFT,`) for every possibly combination of system, architecture, and in some cases even libc. This does not scale well. 2. Create some kind of string DSL, CTFEs and `mixin`s to build the full enum definition, something like this possible. ```d mixin(buildEnum(` A 5 B 6 C 7 if x86_64 C 17 if AArch64 E 9 `)); ``` And then provide some reflection based enum inheritance (where enum member values or their presence can be also be steered by some static ifs / version logic). Surely doable, but not very clean. But it is pretty ugly and limiting, not to mention probably does not play well with most IDEs. Maybe something like this would be interesting too. ```d enum FOO { A = 5, B = 6, mixin("C = 7") } else version (AArch64) { C = 17, } else { static assert(0); } version E = 9, } // static assert(checkUniqueValues!FOO); ``` 3. Use typedef, to create new distinct type for these various flag constants, and use that. Maybe ```d import std.typecons; alias Foo = Typedef!int; enum Foo B = 6; ``` But then the actual structure and discoverability is lost. Plus if one has two classes of values both having `B`, they will clash, unless one puts some prefixes, or puts them in different modules, which is a big limitation. I am not advocating for extending language necessarily, as I am sure library solution could be worked out too. Still it is a bit strange that one cannot easily use `version` and `static if` in `enum`s, where they can use it in almost every other place in the language.
Jul 28
On Sunday, 28 July 2024 at 22:20:47 UTC, Witold Baryluk wrote: This is the same thing? https://issues.dlang.org/show_bug.cgi?id=24666
Jul 29
On Sunday, 28 July 2024 at 22:20:47 UTC, Witold Baryluk wrote:Unfortunately this only works for top level enum values. It does not work for named enum types. A simple example ```d enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } version E = 9, } ```Possible workaround if each enum member is given a specific initializer: ```d struct FooEnum { int A = 5, B = 6; version (x86_64) { int C = 7; } else version (AArch64) { int C = 17; } else { static assert(0); } } mixin enumGen!(FooEnum, "FOO"); static assert(FOO.A == 5); static assert(FOO.B == 6); version (x86_64) static assert(FOO.C == 7); template enumGen(T, string name) { private string _gen() { T v; auto r = "enum " ~ name ~ " {"; foreach (m; __traits(allMembers, T)) { import std.conv; r ~= m ~ "=" ~ __traits(getMember, v, m).to!string ~ ","; } r ~= "}"; return r; } mixin(_gen); } ```
Jul 29
On Monday, 29 July 2024 at 11:25:45 UTC, Nick Treleaven wrote:Possible workaround if each enum member is given a specific initializer: ```d struct FooEnum { int A = 5, B = 6; version (x86_64) { int C = 7; } else version (AArch64) { int C = 17; } else { static assert(0); } } mixin enumGen!(FooEnum, "FOO");Of course, the mixin there is not doing anything more than a struct with enum members: ```d version = x86_64; struct FOO { enum A = 5, B = 6; version (x86_64) { enum C = 7; } else version (AArch64) { enum C = 17; } else { static assert(0); } } static assert(FOO.A == 5); static assert(FOO.B == 6); version (x86_64) static assert(FOO.C == 7); ``` But potentially the mixin approach with int members could do more - e.g. auto increment any int members without initializer (i.e. whose value is zero), based on the previous lexical member initializer.
Jul 30
On Tuesday, 30 July 2024 at 19:32:45 UTC, Nick Treleaven wrote:On Monday, 29 July 2024 at 11:25:45 UTC, Nick Treleaven wrote:The struct trick is pretty nice. I will give it a go. At least it should work for the time being. I will try to type-safe it a bit more. I.e. so doing something like `Foo.A | Foo.B` results in some (compile time) constant with proper type and not bare int. So functions receiving these enums can properly detect pass of naked integers, and such. This has a potential.Possible workaround if each enum member is given a specific initializer: ```d struct FooEnum { int A = 5, B = 6; version (x86_64) { int C = 7; } else version (AArch64) { int C = 17; } else { static assert(0); } } mixin enumGen!(FooEnum, "FOO");Of course, the mixin there is not doing anything more than a struct with enum members: ```d version = x86_64; struct FOO { enum A = 5, B = 6; version (x86_64) { enum C = 7; } else version (AArch64) { enum C = 17; } else { static assert(0); } } static assert(FOO.A == 5); static assert(FOO.B == 6); version (x86_64) static assert(FOO.C == 7); ``` But potentially the mixin approach with int members could do more - e.g. auto increment any int members without initializer (i.e. whose value is zero), based on the previous lexical member initializer.
Jul 30
I’ve recently made a API for doing this in BindBC-Common. You can see it on the latest commit of my repository [here](https://github.com/BindBC/bindbc-common/blob/f9b30591e05238fcda80bb6b070acacd1f77f018/source/bindbc/com on/codegen.d#L500). This is not tagged as a version yet, but you can download it & use `dub add-local` until I tag it. It’s designed for making language bindings (mostly to C) so there’s a convenient way to have a D version of the enum (e.g. `Plant.flower`) and a C version (e.g. `PLANT_FLOWER`) with the same code. And you can turn them on and off easily so that people who don’t like one of the styles are still happy. Usage is like… ```d import bindbc.common; mixin(makeEnumBindFns(cStyle: true, dStyle: true)); //this defines `makeEnumBind` //makes `enum Plant: ulong`: mixin(makeEnumBind(q{Plant}, q{ulong}, members: (){ EnumMember[] ret = [ {{q{flower}, q{PLANT_FLOWER}}, q{1}}, {{q{bush}, q{PLANT_BUSH}}, q{2}}, ]; if(condition){ EnumMember[] add = [ {{q{herb}, q{PLANT_HERB}}, q{4}}, ]; ret ~= add; } return ret; }())); ``` Please note that some parameters and fields are **required** to be referred to by name. Read the documentation comments for `makeEnumBind`, `EnumMember`, and `EnumIden` to avoid misusing them.
Jul 30
On Tuesday, 30 July 2024 at 23:26:58 UTC, IchorDev wrote:I’ve recently made a API for doing this in BindBC-Common. You can see it on the latest commit of my repository [here](https://github.com/BindBC/bindbc-common/blob/f9b30591e05238fcda80bb6b070acacd1f77f018/source/bindbc/com on/codegen.d#L500). This is not tagged as a version yet, but you can download it & use `dub add-local` until I tag it. It’s designed for making language bindings (mostly to C) so there’s a convenient way to have a D version of the enum (e.g. `Plant.flower`) and a C version (e.g. `PLANT_FLOWER`) with the same code. And you can turn them on and off easily so that people who don’t like one of the styles are still happy. Usage is like… ```d import bindbc.common; mixin(makeEnumBindFns(cStyle: true, dStyle: true)); //this defines `makeEnumBind` //makes `enum Plant: ulong`: mixin(makeEnumBind(q{Plant}, q{ulong}, members: (){ EnumMember[] ret = [ {{q{flower}, q{PLANT_FLOWER}}, q{1}}, {{q{bush}, q{PLANT_BUSH}}, q{2}}, ]; if(condition){ EnumMember[] add = [ {{q{herb}, q{PLANT_HERB}}, q{4}}, ]; ret ~= add; } return ret; }())); ``` Please note that some parameters and fields are **required** to be referred to by name. Read the documentation comments for `makeEnumBind`, `EnumMember`, and `EnumIden` to avoid misusing them.All nice and dandy. I know how to write mixins, there are many ways to do it. I am just surprised language feature of `version` and `static if` that work so easily in many other places, cannot be used for anything useful in enums and enums only. Is it really like so rare that it is not in the language? Usually one would not want to define an enum that have different values and enum members, as this can mess other code. But then it is pretty normal when interfacing with other languages and systems, or even doing things like changing some default values depending on a version. Phobos (especially core and sys and stdc) with free standing enums, partially due to this problem. I do like idea of the struct with static enum members. It might just work, without CTFEs and mixins. I will give it a try.
Jul 30
On Wednesday, 31 July 2024 at 00:50:59 UTC, Witold Baryluk wrote:All nice and dandy. I know how to write mixins, there are many ways to do it.I am just providing an existing solution that is tailored to language bindings and should see extensive use in the near future and will therefore be less likely to have bugs in the long run.I am just surprised language feature of `version` and `static if` that work so easily in many other places, cannot be used for anything useful in enums and enums only.Judging by the replies to [my DIP ideas thread](https://forum.dlang.org/thread/gkvhaaqrnbizhacpikde forum.dlang.org), I think it’s partly an issue with allowing a declaration in the middle of one type of comma-separated list when it is not permitted in all others: ```d int[] x = [1,2,3, version(X) 4 else 0]; void fn(int a, version(X) int b, int c){} ``` The options are really: 1. We make a way of declaring enums with statements instead of a list, much like in Swift, which would also allow things like enum member functions; or 2. We allow for conditional compilation declarations in all comma separated lists. I don’t see much use of this for function parameters outside of language bindings, but with array literals this could alleviate having to laboriously concatenate many items together, and with associative array literals this would be **very** useful since you can’t just concatenate them easily. I’d love to see if there’s any interest in either of those possibilities.Usually one would not want to define an enum that have different values and enum members, as this can mess other code. But then it is pretty normal when interfacing with other languages and systems, or even doing things like changing some default values depending on a version.Yes, it’s mostly needed for making language bindings, so that means that most people don’t understand why you’d even want the feature; but as someone who creates a lot of language bindings, I absolutely see the utility.I do like idea of the struct with static enum members. It might just work, without CTFEs and mixins. I will give it a try.It’s a clever workaround but won’t work the way people want with a lot of enum-based meta-programming (e.g. `std.conv.to`). You should also ` disable this();`.
Jul 30
This comes up from time to time. ``` enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } version E = 9, } ``` I've seen this forever in C. It just makes my brain bleed. Here's the D way: ``` version (x86_64) { enum FOO { A = 5, B = 6, C = 7, } } else version (AArch64) { enum FOO { A = 5, B = 6, C = 17, } } else static assert(0); ``` Ah, doesn't that look nicer? It's nicer because the code forms a regular pattern. A regular pattern is easier to understand. It also uncovers errors, like the `version E = 9,` error in the opening example. Also, when I'm debugging AArch64 code, I don't want to see x86_64 code interleaved in with it. Such makes it too easy to inadvertently mix them up.
Jul 30
On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:Here's the D way: ``` version (x86_64) { enum FOO { A = 5, B = 6, C = 7, } } else version (AArch64) { enum FOO { A = 5, B = 6, C = 17, } } else static assert(0); ``` Ah, doesn't that look nicer? It's nicer because the code forms a regular pattern. A regular pattern is easier to understand.Could you please rewrite the D version of [this enum](https://github.com/BindBC/bindbc-glib/blob/b5b8131226f176bcd1ab68af0402c2ce984c2f64/source/ lib/unicode.d#L130) to use this pattern for me? When you’re done, post it as a reply so we can see how much nicer it looks. ;) There’s only 14 different possible versions, and each one is at least 67 lines long (unless you inline the enum, in which case you can kiss readability goodbye), so it should only be a bit over 1000 lines of code when you’re finished there. Compared with the 238 lines of my original version—which could be further shortened if it was using conditional compilation instead of meta-programming—and it might become apparent that your suggestion forsakes the DRY principle.
Jul 31
On 7/31/2024 2:23 AM, IchorDev wrote:your suggestion forsakes the DRY principle.Indeed, it does. Consider the evolution of expertise: 1. novice - follows the rules because he's told to 2. master - follows the rules because he understands the point of the rules 3. guru - violates the rules because he understands when the rules don't apply BTW, your example looks fine as it is!
Jul 31
On Wednesday, 31 July 2024 at 19:03:32 UTC, Walter Bright wrote:Indeed, it does. Consider the evolution of expertise: 1. novice - follows the rules because he's told to 2. master - follows the rules because he understands the point of the rules 3. guru - violates the rules because he understands when the rules don't applyHe this, he that. Where is ‘they’ when you need it? Nonetheless, when you say to do things one way, people will feel that they have to follow. I’ve seen a **lot** of horrendous enum repetition in D because of this mentality that ‘we have to do things the bad way because it’s correct’. Before I took over BindBC-SDL it had **so** much repetition because of this design pattern. I replaced all the enum types with typeless lists, but now people can’t just iterate over the members of an enum properly. Relegating something that’s easy to do anywhere else (structs, unions, classes) to mixins is just cruel and encourages people to make unreadable or inferior code because it’s the path of least resistance. Not everyone will spend a day making an enum generator like I did; and it didn’t even occur to me 2 years ago to use a mixin for enums in the case of BindBC-SDL because there was this dogma.BTW, your example looks fine as it is!You said that it hurts your head to read code like that. But even if it’s ‘fine’, it would compile faster & take fewer lines if we could put conditional compilation in comma-separated lists, particularly enums and (associative) array literals. I would only need to write one array literal instead of a CTFE lambda with a series of ifs. Notice also that I did not nest those ifs, which is slightly worse for performance, but I had to preserve readability. With a single array literal, I would not have to make these compromises.
Jul 31
If you really want conditional enums, you can do this: ``` struct E { enum A = 3; enum B = 4; version (XYZ) enum C = 5; } E e = E.A; ```
Aug 01
On Thursday, 1 August 2024 at 17:46:37 UTC, Walter Bright wrote:If you really want conditional enums, you can do this: ``` struct E { enum A = 3; enum B = 4; version (XYZ) enum C = 5; } E e = E.A; `````` e2.d:8:7: error: cannot implicitly convert expression ‘A’ of type ‘int’ to ‘E’ 8 | E e = E.A; ``` Also fails to catch other issues: ```d auto e = E.A | 7; ``` Compiles, but I would prefer it didn't in this case. The closest I was able to get to something decent (using version in this example is trivial, so only showing inheritance and flag manipulation) is this example: ```d struct Typedef(T, T init = T.init, string cookie = "", alias inherit_from = T) { private T payload = init; this(T init) { payload = init; } this(typeof(this) tdef) { this(tdef.payload); } static if (!is(inherit_from : T)) { this(inherit_from tdef) { this(tdef.payload); } } auto ref opBinary(string op, this X, B)(auto ref B b) if (op != "in") { return Typedef(mixin("payload "~op~" b.payload")); } //static if (!is(inherit_from : T)) { auto ref opBinaryRight(string op, this X)(auto ref inherit_from b) { return Typedef(mixin("b.payload " ~ op ~ " payload")); } //} //auto ref opCast(T, this X)() { // return cast(T)payload; //} } struct MMap { alias Flags = Typedef!(ulong, 0, "MMap"); static const Flags None = 0; static const Flags A = 1; static const Flags B = 2; }; struct LinuxMMap { alias Flags = Typedef!(ulong, 0, "LinuxMMap", MMap.Flags); static foreach (x; __traits(allMembers, MMap)) { static if (x != "Flags") { mixin("static const LinuxMMap.Flags "~x~" = MMap."~x~";"); } } static const Flags C = 4; version (X86_64) { static const Flags D = 30; static const Flags E = 34; } else version (AArch64) { static const Flags F = 33; static const Flags G = 36; } else { static assert(0); } } auto n(T)(T flags0) { LinuxMMap.Flags flags = flags0; import std; writefln("%s %d", flags, flags.payload); } void main() { n(MMap.A | MMap.B | LinuxMMap.C); } ``` The "inheritance" part can be easily wrapped in a reusable mixin template. It detects (and rejects) constructs like `n(MMap.A | 5)`, etc. But still accepts `n(5)`, but this is fixable using some template constraints. So of course this works, and I knew it even before sending initial , but it is just quite ugly. I also did check generate assembly code, and it looks good (all inlined, and folded to a constant `7` above), at least using gdc.
Aug 04
It depends on just what you want an enum for. The example I gave was for a scoped collection of named ints, you're right it doesn't not actually create an enum type.
Aug 06
On 7/31/2024 7:06 PM, IchorDev wrote:You said that it hurts your head to read code like that. But even if it’s ‘fine’, it would compile faster & take fewer linesNot perceptibly faster (the lexer is very fast), and fewer lines is not always the best thing.if we could put conditional compilation in comma-separated lists, particularly enums and (associative) array literals. I would only need to write one array literal instead of a CTFE lambda with a series of ifs. Notice also that I did not nest those ifs, which is slightly worse for performance, but I had to preserve readability. With a single array literal, I would not have to make these compromises.I do things like this: ``` /// Map to unsigned version of type __gshared tym_t[256] tytouns = tytouns_init; extern (D) private enum tytouns_init = () { tym_t[256] tab; foreach (ty; 0 .. TYMAX) { tym_t tym; switch (ty) { case TYchar: tym = TYuchar; break; case TYschar: tym = TYuchar; break; case TYshort: tym = TYushort; break; case TYushort: tym = TYushort; break; case TYenum: tym = TYuint; break; case TYint: tym = TYuint; break; case TYlong: tym = TYulong; break; case TYllong: tym = TYullong; break; case TYcent: tym = TYucent; break; case TYschar16: tym = TYuchar16; break; case TYshort8: tym = TYushort8; break; case TYlong4: tym = TYulong4; break; case TYllong2: tym = TYullong2; break; case TYschar32: tym = TYuchar32; break; case TYshort16: tym = TYushort16; break; case TYlong8: tym = TYulong8; break; case TYllong4: tym = TYullong4; break; case TYschar64: tym = TYuchar64; break; case TYshort32: tym = TYushort32; break; case TYlong16: tym = TYulong16; break; case TYllong8: tym = TYullong8; break; default: tym = ty; break; } tab[ty] = tym; } return tab; } (); ``` to make complex initializers. Works great! I don't know the details of your particular solution.
Aug 01
On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:version (x86_64) { enum FOO { A = 5, B = 6, C = 7, } } else version (AArch64) { enum FOO { A = 5, B = 6, C = 17, } } else static assert(0); ``` Ah, doesn't that look nicer?It's been said maybe a billion times why this solution rarely works in the read world. And the arguments have been dismissed in the classic D way.
Jul 31
On 01/08/2024 4:04 AM, Max Samukha wrote:On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:Yeah not supporting this, just doesn't fit D anymore. Too many people try it, which shows that it is clearly good language design. It helps to fulfill peoples ideas, so an easy win to me.version (x86_64) { enum FOO { A = 5, B = 6, C = 7, } } else version (AArch64) { enum FOO { A = 5, B = 6, C = 17, } } else static assert(0); ``` Ah, doesn't that look nicer?It's been said maybe a billion times why this solution rarely works in the read world. And the arguments have been dismissed in the classic D way.
Jul 31
On 7/31/2024 10:51 AM, Richard (Rikki) Andrew Cattermole wrote:Too many people try it, which shows that it is clearly good language design.People try all kinds of things in programming, eventually they evolve towards Best Practices.
Jul 31
On 01/08/2024 7:08 AM, Walter Bright wrote:On 7/31/2024 10:51 AM, Richard (Rikki) Andrew Cattermole wrote:Luckily we have Dscanner, so if it turns out a pattern gets too complex and therefore is not best practice, we can throw a warning at it without breaking code! Which severely diminishes any possible objections.Too many people try it, which shows that it is clearly good language design.People try all kinds of things in programming, eventually they evolve towards Best Practices.
Jul 31
On Wednesday, 31 July 2024 at 19:08:13 UTC, Walter Bright wrote:On 7/31/2024 10:51 AM, Richard (Rikki) Andrew Cattermole wrote:? Someone who starts at haskell will end up very different then someone who starts with c; to say nothing of different kinds of problems. No. If you ask 5 80 year old programmers youll get 5 different answers for whats a "best practice" we all wont "evolve" to the same answers for the foreseeable future.Too many people try it, which shows that it is clearly good language design.People try all kinds of things in programming, eventually they evolve towards Best Practices.
Jul 31
On 7/31/2024 9:04 AM, Max Samukha wrote:It's been said maybe a billion times why this solution rarely works in the read world.I know, it comes up regularly. A classic case is the antecedent where a list of operating system enum values changes with each platform. I've had the dubious pleasure of having to fix the equivalent for struct fields that vary by operating system, and were wrong because it's hard to compare a mess of conditional compilation with the operating system spec which does not have conditionals in it. Let's look at the original again: ``` enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } E = 9, // fixed that! } ``` The example does follow best practice by adding the static assert for an unanticipated operating system. Now, let's add a G for AArch64: ``` enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } E = 9, G = 10, } ``` Does x86_64 have a G, or not? Is the programmer going to check each supported system for G? No, they're not. Are they going to wrap it in its own version declaration? Nope. (Even if they do, it runs afoul of what to do about the else clause.) I see it again and again. I fix them again and again. Sometimes they are never caught at all. One could put G in the AArch64 block, but then it is out of order, making it hard to check them against the operating system spec which will be in order. But if the operating systems are in separate declarations, this is not an issue. The person adding G to x86_64 doesn't need to check operating systems he's not familiar with. Getting the enum values wrong results in really hard to debug failures. I know you don't find this convincing.
Jul 31
On Wednesday, 31 July 2024 at 16:04:43 UTC, Max Samukha wrote:And the arguments have been dismissed in the classic D way.Yes it feels a bit like a dogma, which is not really the D way IMO… we have a lot of choice about how we write code to best meet our needs. Especially with meta-programming—think of Pegged or similar.
Jul 31
On 7/31/2024 5:54 PM, IchorDev wrote:Yes it feels a bit like a dogma,All languages have their particular dogmas. Scott Meyers wrote a popular series of "Effective C++" books which are accepted C++ dogma, for example. D tries to discourage constructs that have a troublesome history. I know that some of these constructs seem like good ideas, the trouble only appears years later.
Aug 01
On Wednesday, 31 July 2024 at 06:11:15 UTC, Walter Bright wrote:This comes up from time to time. ``` enum FOO { A = 5, B = 6, version (x86_64) { C = 7, } else version (AArch64) { C = 17, } else { static assert(0); } version E = 9, } ``` I've seen this forever in C. It just makes my brain bleed. Here's the D way: ``` version (x86_64) { enum FOO { A = 5, B = 6, C = 7, } } else version (AArch64) { enum FOO { A = 5, B = 6, C = 17, } } else static assert(0); ```The clean way is using "mixin" & "import" foo_windows.enum file enum FOO { A = 5, B = 6, C = 7, } foo_aarch64.enum file enum FOO { A = 5, B = 6, C = 17, } main.d file module m; version(Windows) mixin(import("foo_windows.enum")); else version(AArch64) mixin(import("foo_aarch64.enum")); else static assert(0); version(Windows) static assert(FOO.C == 7); else version(AArch64) static assert(FOO.C == 17); void main() { } build with dmd.exe -J="directory of those enum files..." main.d
Jul 31