Jump to page: 1 2 3
Thread overview
Good examples of version() algebra in real code
May 21, 2023
Dennis
May 21, 2023
Guillaume Piolat
May 22, 2023
Guillaume Piolat
May 21, 2023
max haughton
May 22, 2023
Hipreme
May 22, 2023
Walter Bright
May 22, 2023
Walter Bright
May 22, 2023
Hipreme
May 22, 2023
Walter Bright
May 22, 2023
Max Samukha
May 22, 2023
Walter Bright
May 22, 2023
Walter Bright
May 22, 2023
Hipreme
May 22, 2023
Walter Bright
May 22, 2023
Adam D Ruppe
May 22, 2023
Timon Gehr
May 22, 2023
Walter Bright
May 22, 2023
Walter Bright
May 23, 2023
Basile B.
May 23, 2023
Basile B.
May 21, 2023

D's version() statement deliberately does not allow composing version conditions with boolean operators ! || &&, in order to avoid C's #ifdef hell. This is a controversial design decision though, because some people don't like the resulting verbosity you sometimes get. Discussions about this come up now and then, see for example Issue 7417 and its duplicates.

The most recent incarnation of this comes from this Phobos PR proposing a library solution:
std.compiler.Version

import std.compiler;

static if (Version.D_InlineAsm_X86 || Version.D_InlineAsm_X86_64)
{
    version = UseX86Assembly;
}

I don't expect this to get approval though, I said in the PR discussion:

>

I'll bring this up in the next DLF monthly meeting, but without new compelling arguments, I don't expect a different outcome. If you can demonstrate problems (other than it not looking nice) in existing projects that the current logic causes, you'll have a stronger case.

So far I haven't received any real code examples, so I'm asking here:

Do you have any examples of existing projects (on github, dub, etc.) that either:

  • demonstrate version algebra done well
  • use a Version-like template successfully
  • have real problems with existing version() statements (besides 'it looks ugly')

Please reply with links and don't rekindle the old arguments. I can't stop you, but know that it will only be counter-productive.

May 21, 2023

On Sunday, 21 May 2023 at 18:43:32 UTC, Dennis wrote:

>
  • have real problems with existing version() statements (besides 'it looks ugly')

No big problem, but sometimes you need to do:

version(X86)
    version = AnyX86;
version(X86_64)
    version = AnyX86;

or

version (OSX)
    version = Darwin;
else version (iOS)
    version = Darwin;
else version (TVOS)
    version = Darwin;
else version (WatchOS)
    version = Darwin;

or

version (D_InlineAsm_X86)
    version = UseX86Assembly;
version (D_InlineAsm_X86_64)
    version = UseX86Assembly;

and those are the only case I remember being a tiny bit annoyed at version. I wouldn't use the above library solution to save one import.

May 21, 2023

On Sunday, 21 May 2023 at 18:43:32 UTC, Dennis wrote:

>

D's version() statement deliberately does not allow composing version conditions with boolean operators ! || &&, in order to avoid C's #ifdef hell. This is a controversial design decision though, because some people don't like the resulting verbosity you sometimes get. Discussions about this come up now and then, see for example Issue 7417 and its duplicates.

The most recent incarnation of this comes from this Phobos PR proposing a library solution:
std.compiler.Version

import std.compiler;

static if (Version.D_InlineAsm_X86 || Version.D_InlineAsm_X86_64)
{
    version = UseX86Assembly;
}

I don't expect this to get approval though, I said in the PR discussion:

>

I'll bring this up in the next DLF monthly meeting, but without new compelling arguments, I don't expect a different outcome. If you can demonstrate problems (other than it not looking nice) in existing projects that the current logic causes, you'll have a stronger case.

So far I haven't received any real code examples, so I'm asking here:

Do you have any examples of existing projects (on github, dub, etc.) that either:

  • demonstrate version algebra done well
  • use a Version-like template successfully
  • have real problems with existing version() statements (besides 'it looks ugly')
    It's not bad to try and make good look good. Most programming language design decisions boil down to some kind of aesthetically driven heuristic.

Current state of affairs is oppressive because it makes common cases expensive. That being said a lot of version usage shouldn't exist. Let the compiler constant fold the details for you so your code ports to new architecture/cross target more easily

May 22, 2023

On Sunday, 21 May 2023 at 18:43:32 UTC, Dennis wrote:

>

D's version() statement deliberately does not allow composing version conditions with boolean operators ! || &&, in order to avoid C's #ifdef hell. This is a controversial design decision though, because some people don't like the resulting verbosity you sometimes get. Discussions about this come up now and then, see for example Issue 7417 and its duplicates.

The most recent incarnation of this comes from this Phobos PR proposing a library solution:
std.compiler.Version

import std.compiler;

static if (Version.D_InlineAsm_X86 || Version.D_InlineAsm_X86_64)
{
    version = UseX86Assembly;
}

I don't expect this to get approval though, I said in the PR discussion:

>

I'll bring this up in the next DLF monthly meeting, but without new compelling arguments, I don't expect a different outcome. If you can demonstrate problems (other than it not looking nice) in existing projects that the current logic causes, you'll have a stronger case.

So far I haven't received any real code examples, so I'm asking here:

Do you have any examples of existing projects (on github, dub, etc.) that either:

  • demonstrate version algebra done well
  • use a Version-like template successfully
  • have real problems with existing version() statements (besides 'it looks ugly')

Please reply with links and don't rekindle the old arguments. I can't stop you, but know that it will only be counter-productive.

Keep in mind that I have found plenty of codes when defining a version based on static if. This bug probably occurs for them being parsed at different stages of the compiler, I would be using that if weren't for the bugs.

Right now I've come to understand that using feature based versions instead of real versions really makes a lot of difference. But as Guillaume pointed out, there is still this other problem of defining a feature based on multiple platforms and this solution doesn't really make one write a lot less, so I still find this solution lacking for our problem which makes me wonder if there really exists a good solution for that.

May 22, 2023
I don't have links, it was a long time ago, but there was a time when someone introduced enums to druntime modules and used them with non-trivial static if expressions. People started extending this, and it soon became quite a tangle of some static ifs depending on other static ifs and the declarations of the enums became embedded in static ifs and then, inevitably, circular imports came into play.

Then, the whole thing reached a point where nobody could figure out whether particular static ifs were being triggered or not. The problem got dumped in my lap, and I yanked out all the static if's and replaced them with trivial logic.

In getting ImportC to work with system .h files, written by the best C experts on the planet, I've been re-acquainted with #ifdef hell.

I used to write #ifdef hell myself. It's very seductive.

So, yes, you can use static if and enums to implement version hell. But not in any official Dlang repositories.
May 22, 2023
On 5/21/2023 7:01 PM, Hipreme wrote:
> Right now I've come to understand that using feature based versions instead of real versions really makes a lot of difference. But as Guillaume pointed out, there is still this other problem of defining a feature based on multiple platforms and this solution doesn't really make one write a lot less, so I still find this solution lacking for our problem which makes me wonder if there really exists a good solution for that.

version(linux)         enum ExtraFunctionality = true;
else version(OSX)      enum ExtraFunctionality = true;
else version(Windows)  enum ExtraFunctionality = false;
else static assert(0, "system not accounted for");

The static assert is there because a very common failure of #ifdef hell is to have defaults that botch things up when a new version is added.

This happens sometimes in the druntime imports, when I discover them I add the static assert.

There are still other ways to do it:

```
import extra;
void foo()
{
    extraFunctionality();
}
```

```
module extra;

void extraFunctionality()
{
  version(linux)         doit();
  else version(OSX)      doit();
  else version(Windows)  { }
  else static assert(0, "system not accounted for");
}
```

Another way is to write a "personality module" for each operating system, and then import the right one:

```
module extra;
version (linux) import extraLinux;
else version (OSX) import extraOSX;
... and so on ...
```

Personally, I like to make the core code version-independent and OS-independent and hide the variances in separate modules. Isn't foo() clean looking?
May 22, 2023
```
version(linux)         enum ExtraFunctionality = true;
else version(OSX)      enum ExtraFunctionality = true;
else version(Windows)  enum ExtraFunctionality = false;
else static assert(0, "system not accounted for");
```

(forgot to use Markdown! Oops!)
May 22, 2023
On Monday, 22 May 2023 at 08:21:50 UTC, Walter Bright wrote:

>
> Personally, I like to make the core code version-independent and OS-independent and hide the variances in separate modules. Isn't foo() clean looking?

I sense echoes of the infamous "Clean Code" debate. Why can't we just let the programmer decide whether to put the versions inline or to hide them behind an abstraction?
May 22, 2023
On 22/05/2023 8:21 PM, Walter Bright wrote:
> The static assert is there because a very common failure of #ifdef hell is to have defaults that botch things up when a new version is added.

If you want to be principled about it, one way to consider it is that not having an else branch (even if empty) should be an error. Because you clearly didn't think about the multiplicative issues of the versions.

Regardless, or'ing isn't the issue, its when you and versions that you get an explosion of multiplicative issues.

An option could be to use comma instead of ``||`` to handle or'ing. That way people won't be so tempted to "just extend it" to other operators like and.
May 22, 2023
On Monday, 22 May 2023 at 08:21:50 UTC, Walter Bright wrote:
> On 5/21/2023 7:01 PM, Hipreme wrote:
>> Right now I've come to understand that using feature based versions instead of real versions really makes a lot of difference. But as Guillaume pointed out, there is still this other problem of defining a feature based on multiple platforms and this solution doesn't really make one write a lot less, so I still find this solution lacking for our problem which makes me wonder if there really exists a good solution for that.
>
> version(linux)         enum ExtraFunctionality = true;
> else version(OSX)      enum ExtraFunctionality = true;
> else version(Windows)  enum ExtraFunctionality = false;
> else static assert(0, "system not accounted for");
>
> The static assert is there because a very common failure of #ifdef hell is to have defaults that botch things up when a new version is added.
>
> This happens sometimes in the druntime imports, when I discover them I add the static assert.
>
> There are still other ways to do it:
>
> ```
> import extra;
> void foo()
> {
>     extraFunctionality();
> }
> ```
>
> ```
> module extra;
>
> void extraFunctionality()
> {
>   version(linux)         doit();
>   else version(OSX)      doit();
>   else version(Windows)  { }
>   else static assert(0, "system not accounted for");
> }
> ```
>
> Another way is to write a "personality module" for each operating system, and then import the right one:
>
> ```
> module extra;
> version (linux) import extraLinux;
> else version (OSX) import extraOSX;
> ... and so on ...
> ```
>
> Personally, I like to make the core code version-independent and OS-independent and hide the variances in separate modules. Isn't foo() clean looking?

Yes, I do understand. Although I prefer `static assert` to not be used, but the runtime `assert`. I have done a port of the druntime and the `static assert` usage really is a pain since there is just a plain lot of code which is not used by me, but only for its existence, it causes a compilation error.


I think the feature based is cleaner to read most of the time (and scalable), I have done a good refactor in a lot of D code already using that, and IMO, it did done wonders into making the code intention crystal clear even for non maintainers. This is a thing which I've come to understand the decision of not allowing boolean operators on `version`. Maybe if there was a construct for allowing **only version declaration** with boolean operators like:
`version RelaxedSystems = version(Windows && linux && !OSX)` (it would not be global as the `version` is right now. i.e: not allow this syntax to be used standalone.

So, the operators aren't really the hell that causes the `#ifdef` hell as you said. The problem are 2:

1: They being defined over all files. While trying to port newlibc, I've come to find a type to be defined over 6 files. Which meant I really went jumping from a file to file until I was able to find how the type were defined. This is a real problem since the type is not self contained so it is super hard to look. How to solve that: D has solved! Just make the `version =` not spam into multiple files.

2: Operators: they don't really make sense when other people are looking into them, which is solved by having the feature named, so, enforcing the naming to use the operators could be a problem solving in the syntax. Since they aren't global, they are painful to keep writing all the time the same thing (I've tried doing that on directx-d binding and omg, I basically got a 8 line headers in almost all files, sure, it is easier to read than C, but it was painful writing them).

« First   ‹ Prev
1 2 3