November 23, 2018
On Friday, November 23, 2018 7:55:04 PM MST H. S. Teoh via Digitalmars-d- learn wrote:
> Adam does have a very good point about showing all alternatives to docs, though.  Arguably, that's what ddoc *should* do.  If the programmer wrote a ddoc comment in the code, it probably should be processed as part of doc generation, regardless of whether that code sits in some deeply-nested version blocks that ends up not being compiled.  Binding ddoc generation to the compile process seems not such a good idea in retrospect.

Honestly, I would argue that if you have multiple versions of the documentation, then there's a serious problem. The documentation shouldn't be platform-dependent even if the symbols are. Even if the documentation needs some extra notes for a specific platform, it should all be in one documentation block that anyone using the symbol can read. Providing different documentation for different platforms just leads to folks not understanding how the symbol differs across platforms, leading to code that is even more platform-dependent when it really should be as platform independent as possible.

The only situation I can think of at the moment where anything along the lines of combining documentation across platforms makes sense would be if there is a nested symbol that exists on only one platform (e.g. a member function of a struct or a member of an enum). In that case, one platform would have the main documentation, and then system-specific symbols would be documented in those version blocks - but only those symbols. A solution like that might work reasonably well, but you still have the problem of what to do when a symbol is documented in multiple version blocks, and having almost all the documentation in one version block and a few pieces of it in other version blocks would risk getting confusing and messy. As such, I'm not sure that the fact that ddoc forces you to have a separate set of declarations just for the documentation is really a bad thing. It puts all of the documentation in one place.

The bigger problem IMHO is how -D affects the build. Both it and -unittest have the fundamental problem that because they create their own version identifiers, they really shouldn't be part of the normal build, and yet the way that they're set up to be used, it's as if they're expected to be part of the normal build - with -D just causing the compiler to generate the documentation in addition to the binary, and -unittest making the unit tests run before main rather than replacing main.

At this point, I really wish that at minimum, -D were not set up to be part of the normal build process so that version(D_Ddoc) would not affect it. The same with -unittest. Ideally, it really would have replaced main rather than putting the unit tests before it, with it providing main if one wasn't there. That still doesn't solve all of the problems when using version(unittest), but I doubt that much of anyone really wants to be running unit tests as part of their application, and having such flags clearly designed to create special builds rather than being designed such that they could be used with the normal build would have fixed certain classes of problems.

As for the issue of versioning the documentation, I don't really see a clean one. Having the documentation build affected by version and static if and the like causes some problems, but having it ignore them would likely cause other problems. Regardless, I suspect that having the version identifiers and static ifs be ignored almost requires a separate tool from the compiler (such as Adam's documentation generator or ddox), because it's pretty clear that ddoc was set up to generate the documentation for symbols as the compiler compiles them, whereas a tool that ignored version identifiers and static ifs would be processing the file quite differently.

- Jonathan M Davis



November 24, 2018
On Fri, 23 Nov 2018 21:43:01 -0700, Jonathan M Davis wrote:
> A solution like that might work reasonably well, but you still
> have the problem of what to do when a symbol is documented in multiple
> version blocks, and having almost all the documentation in one version
> block and a few pieces of it in other version blocks would risk getting
> confusing and messy.

Keeping symbol names and function arguments consistent between them is also an issue; it's not just the documentation. The normal solution is to put the version blocks inside the relevant symbols -- sometimes type aliases inside version blocks and consistent code outside, sometimes functions where the entire body is a set of version blocks.
November 24, 2018
On Friday, November 23, 2018 11:22:24 PM MST Neia Neutuladh via Digitalmars- d-learn wrote:
> On Fri, 23 Nov 2018 21:43:01 -0700, Jonathan M Davis wrote:
> > A solution like that might work reasonably well, but you still
> > have the problem of what to do when a symbol is documented in multiple
> > version blocks, and having almost all the documentation in one version
> > block and a few pieces of it in other version blocks would risk getting
> > confusing and messy.
>
> Keeping symbol names and function arguments consistent between them is also an issue; it's not just the documentation. The normal solution is to put the version blocks inside the relevant symbols -- sometimes type aliases inside version blocks and consistent code outside, sometimes functions where the entire body is a set of version blocks.

When you're versioning the implementation, it's trivial to just version the function internals. Where version(D_Ddoc) becomes critical is with stuff like structs or enums where the members actually differ across systems. And while it's generally better to try to avoid such situations, there are definitely situations where there isn't much choice. Fortunately, in the vast majority of situations, versioning across systems isn't required at all, and in most of the situations where it is, its only the implementation that needs to differ, but that's not true in all cases.

I do wish though that it were legal to use version blocks in more situations than is currently the case. For instance,

enum Foo
{
    a,
    b,
    version(linux) l,
    else version(Windows) w,
}

is not legal, nor is

enum Foo
{
    a,
    b,
    version(linux) c = 42,
    else version(Windows) c = 54,
}

You're forced to version the entire enum. Fortunately, structs and classes, do not have that restriction, so having to version an entire struct or class at once is usually only required when the type needs to be declared on some systems but not others (as is the case with WindowsTimeZone), and such situations are rare.

- Jonathan M Davis



November 24, 2018
On Fri, Nov 23, 2018 at 09:43:01PM -0700, Jonathan M Davis via Digitalmars-d-learn wrote: [...]
> Honestly, I would argue that if you have multiple versions of the documentation, then there's a serious problem. The documentation shouldn't be platform-dependent even if the symbols are. Even if the documentation needs some extra notes for a specific platform, it should all be in one documentation block that anyone using the symbol can read. Providing different documentation for different platforms just leads to folks not understanding how the symbol differs across platforms, leading to code that is even more platform-dependent when it really should be as platform independent as possible.

Actually, what would be ideal is if each platform-specific version of the symbol can have its own associated platform-specific docs, in addition to the one common across all platforms, and the doc system would automatically compile these docs into a single block in the output, possibly by introducing platform specific sections, e.g.:

Code:
	/**
	 * Does something really fun that applied across platforms!
	 */
	version(Windows)
	{
		/**
		 * On Windows, this function displays a really funny
		 * icon!
		 */
		int fun(int a) { ... }
	}
	version(Linux)
	{
		/**
		 * On Linux, this function displays a funny penguin!
		 */
		int fun(int a) { ... }
	}

Doc output:
	int fun(int a)

	Does something really fun that applied across platforms!

	Windows notes: On Windows, this function displays a really funny
	icon!

	Linux notes: On Linux, this function displays a funny penguin!


If the function signatures are different, then it should be displayed as "overloads", as Adam suggested. Maybe something like this:

Code:
	/**
	 * This function takes a different parameter depending on the
	 * OS.
	 */
	version(Windows)
		int fun(int x) { ... }
	version(Linux)
		int fun(long x) { ... }

Output:
	int fun(int x)		// on Windows
	int fun(long x)		// on Linux

	This function takes a different parameter depending on the OS.


[...]
> As such, I'm not sure that the fact that ddoc forces you to have a separate set of declarations just for the documentation is really a bad thing. It puts all of the documentation in one place.

It's a bad thing to separate the docs from the actual code, because then the two are liable to go out-of-sync with each other. It's even worse when the docs document a fake declaration that isn't actually part of the real code. Then the docs won't even show the right declaration when the code changes, and users won't even realize what has happened.


> The bigger problem IMHO is how -D affects the build. Both it and -unittest have the fundamental problem that because they create their own version identifiers, they really shouldn't be part of the normal build, and yet the way that they're set up to be used, it's as if they're expected to be part of the normal build - with -D just causing the compiler to generate the documentation in addition to the binary, and -unittest making the unit tests run before main rather than replacing main.
[...]

You can argue for it both ways.  Having the compiler generate docs per compilation isn't necessarily a bad thing -- it ensures the docs are up-to-date.  Running unittests also isn't necessarily a bad thing, though I agree it's sorta weird that unittests are setup to run before the main program. Technically, you want to run unittests first, independently of main(), which should be put in a separate executable. But you can argue for it both ways.

For larger projects, it does make more sense for unittests to be put in a separate executable and run separately.  But for small programs, especially when you're coding-compiling-testing, it's handy to have unittests automatically run before main(), so that any regressions are quickly noticed and fixed.  This doesn't make sense for larger programs, e.g., in my Android project, I really want unittests to run on the host system where possible, not as part of the APK installed to the device, because it's much easier to debug on the host system than to deal with crashlogs and remote debugging on the target machine.

(Of course, deploying unittests to the final execution environment also has its value. But not as part of the main executable, since tests should be run only once, not every time you invoke the executable, which depending on your application could be multiple times during a typical usage session.)


> As for the issue of versioning the documentation, I don't really see a clean one. Having the documentation build affected by version and static if and the like causes some problems, but having it ignore them would likely cause other problems. Regardless, I suspect that having the version identifiers and static ifs be ignored almost requires a separate tool from the compiler (such as Adam's documentation generator or ddox), because it's pretty clear that ddoc was set up to generate the documentation for symbols as the compiler compiles them, whereas a tool that ignored version identifiers and static ifs would be processing the file quite differently.
[...]

There may be a point to generating docs only for the executable that's being built -- if you configure your versions and static ifs for a specific customer, say, then you ship exactly that version of the docs to them.  However, in this day and age where everyone expects online docs, you really want your docs on your website to include *every* version of your code that they might encounter. You may wish the separate it so that the user can select which version(s) they are interested in, but one would expect you want full docs for every possible version configuration.

Otherwise you end up needing silly hacks like version(Std_ddoc) just to get the HTML to show up right, where it isn't actually the symbol that will be compiled, and worse, the docs are now separate from the implementation, and the two are liable to go out-of-sync. History has shown that when the docs aren't a part of the code, they tend to get neglected and treated as an afterthought, or worse yet, as an unwanted additional burden that everyone tries to avoid updating.  Needing to update docs on a declaration completely separate from the code being changed, that the person making the change may not even be aware of, is very bad in this regard.


T

-- 
Heuristics are bug-ridden by definition. If they didn't have bugs, they'd be algorithms.
November 24, 2018
On Saturday, 24 November 2018 at 07:00:31 UTC, Jonathan M Davis wrote:

> [not legal]
>
> enum Foo
> {
>     a,
>     b,
>     version(linux) c = 42,
>     else version(Windows) c = 54,
> }
>
> You're forced to version the entire enum.

Not in this case, no:

enum Foo
{
    a,
    b,
    c = {
       version(linux) return 42;
       else version(Windows) return 54;
    } ()
}

/pedantry
November 24, 2018
On Saturday, November 24, 2018 9:28:47 AM MST Stanislav Blinov via Digitalmars-d-learn wrote:
> On Saturday, 24 November 2018 at 07:00:31 UTC, Jonathan M Davis
>
> wrote:
> > [not legal]
> >
> > enum Foo
> > {
> >
> >     a,
> >     b,
> >     version(linux) c = 42,
> >     else version(Windows) c = 54,
> >
> > }
> >
> > You're forced to version the entire enum.
>
> Not in this case, no:
>
> enum Foo
> {
>      a,
>      b,
>      c = {
>         version(linux) return 42;
>         else version(Windows) return 54;
>      } ()
> }
>
> /pedantry

LOL. That's an interesting trick. It's ugly and hacky, but it does work around the problem. I'm still inclined to think though that it should be legal to just use version directly in the member list.

- Jonathan M Davis



November 24, 2018
On Saturday, 24 November 2018 at 17:43:35 UTC, Jonathan M Davis wrote:
> On Saturday, November 24, 2018 9:28:47 AM MST Stanislav Blinov via Digitalmars-d-learn wrote:
>> On Saturday, 24 November 2018 at 07:00:31 UTC, Jonathan M Davis
>>
>> wrote:
>> > [not legal]
>> >
>> > enum Foo
>> > {
>> >
>> >     a,
>> >     b,
>> >     version(linux) c = 42,
>> >     else version(Windows) c = 54,
>> >
>> > }
>> >
>> > You're forced to version the entire enum.
>>
>> Not in this case, no:
>>
>> enum Foo
>> {
>>      a,
>>      b,
>>      c = {
>>         version(linux) return 42;
>>         else version(Windows) return 54;
>>      } ()
>> }
>>
>> /pedantry
>
> LOL. That's an interesting trick. It's ugly and hacky, but it does work around the problem.

:)

> I'm still inclined to think though that it should be legal to just use version directly in the member list.

Yup. UDAs did get in there eventually, and version should too.
November 24, 2018
On Saturday, 24 November 2018 at 16:16:08 UTC, H. S. Teoh wrote:
> Actually, what would be ideal is if each platform-specific version of the symbol can have its own associated platform-specific docs, in addition to the one common across all platforms, and the doc system would automatically compile these docs into a single block in the output, possibly by introducing platform specific sections, e.g.:

Well, I probably could do that, but I'm not so sure it would make since since it separates stuff quite a bit and may have weird cases. Like what if the arguments are different? Should it merge the params sections? (That is doable btw)

But also from the user side, when they are looking for differences, it is a pain if most the stuff is shared and there is just a different sentence at the bottom or something. I'd prolly insist that it be called out. Of course, the doc gen could automatically merge the docs with headers too... but that brings us to something we already have:

I just use...

/++
    Does whatever.

    Note on Windows it does something a bit different.
+/
version(linux)
void foo();

/// ditto
version(Windows)
void foo();


So the ditto covers the case and tells the doc gen they share the same stuff, and the header is written however we want in the comment.

I don't *hate* having shared docs on version blocks, I am just being lazy and avoiding implementation by pushing back :P


(Though a parsing thing: /// version(linux) void foo(); - is that doc on version linux or on void foo or on both? I guess it could only apply it in if there are {}... but eh I still don't love it.)



> It's a bad thing to separate the docs from the actual code, because then the two are liable to go out-of-sync with each other.

that depends on what kind of docs. for api reference, sure, but other docs prolly should be separate because they are bigger picture, and thus don't naturally fit well in with the code.


But of course, version(D_Ddoc) is the worst of both worlds.
November 24, 2018
On Sat, Nov 24, 2018 at 10:27:07PM +0000, Adam D. Ruppe via Digitalmars-d-learn wrote: [...]
> (Though a parsing thing: /// version(linux) void foo(); - is that doc
> on version linux or on void foo or on both? I guess it could only
> apply it in if there are {}... but eh I still don't love it.)

Yeah that's a tricky case.


[...]
> > It's a bad thing to separate the docs from the actual code, because then the two are liable to go out-of-sync with each other.
> 
> that depends on what kind of docs. for api reference, sure, but other docs prolly should be separate because they are bigger picture, and thus don't naturally fit well in with the code.

That's what module docs are for. Or docs on aggregates as opposed to docs on individual members.


> But of course, version(D_Ddoc) is the worst of both worlds.

Agreed.


T

-- 
Customer support: the art of getting your clients to pay for your own incompetence.
November 24, 2018
On Sat, Nov 24, 2018 at 05:48:16PM +0000, Stanislav Blinov via Digitalmars-d-learn wrote:
> On Saturday, 24 November 2018 at 17:43:35 UTC, Jonathan M Davis wrote:
> > On Saturday, November 24, 2018 9:28:47 AM MST Stanislav Blinov via Digitalmars-d-learn wrote:
[...]
> > > enum Foo
> > > {
> > >      a,
> > >      b,
> > >      c = {
> > >         version(linux) return 42;
> > >         else version(Windows) return 54;
> > >      } ()
> > > }
> > > 
> > > /pedantry
> > 
> > LOL. That's an interesting trick. It's ugly and hacky, but it does work around the problem.
> 
> :)

That's an awesome trick! Genius.


> > I'm still inclined to think though that it should be legal to just use version directly in the member list.
> 
> Yup. UDAs did get in there eventually, and version should too.

I think this would be a trivial DIP, by making it such that a version block inside an enum would lower to the above code. Of course, it could be taken further: the above trick doesn't quite handle this case:

	enum E {
		a,
		version(Windows) {
			b, c
		}
		version(Posix) {
			d
		}
	}

But this looks like such an antipattern that it probably should be written differently anyway, or just generated via a string mixin.


T

-- 
Knowledge is that area of ignorance that we arrange and classify. -- Ambrose Bierce