Flag & byLine confusion.

Mike Parker aldacron at gmail.com
Sun Dec 20 00:40:23 UTC 2020


On Saturday, 19 December 2020 at 23:16:00 UTC, Rekel wrote:

>
> Most confusing was the way the documentation (website & 
> in-editor) used;
> 1. Yes.keepTerminator
> 2. KeepTerminator.yes
> 3. Flag!"keepTerminator".yes

Your confusion arises from the fact that KeepTerminator is 
combining multiple distinct D features to achieve its goal.

As Paul said, std.typecons.Flag is a templated enum of type bool. 
It's declared to take a string as its template parameter, and it 
has two fields: yes, and no. Minus the documentation:

template Flag(string name) {
     ///
     enum Flag : bool
     {
         no = false,
         yes = true
     }
}

The template parameter serves to make Flag!"foo" a distinct type 
from Flag!"bar".

Now, the goal of Flag is to make the purpose of a boolean 
function parameter more clear. Sometimes, we can misremember what 
a boolean parameter indicates. Does true mean do this extra thing 
or does it mean don't do this extra thing? Flag removes the doubt.

However, having to write Flag!"keepTerminator".yes all the time 
is more annoying that simply writing true, so functions that use 
Flag usually have a corresponding alias defined in the module 
scope to give it a less annoying syntax:

alias KeepTerminator = Flag!"keepTerminator";

Because of this, you can write KeepTerminator.yes and 
KeepTerminator.no.

std.typecons also has two structs: Yes and No. Both are 
implemented with a bit of template magic. D supports a feature 
called "Forwarding" in structs and classes and implements it via 
a special template called opDispatch:

https://dlang.org/spec/operatoroverloading.html#dispatch

The idea is that if you call a member function on a struct or 
class, and the class does not have a member function of that 
name, then the compiler will look for an opDispatch template 
implementation in that class or struct. If it finds one, it will 
call the template with the name of the missing function.

There are a number of use cases for this, if you look at the 
examples in the documentation of opDispatch, you'll find that 
there are two examples of implementing it as a function template 
and one that looks like this:

struct D
{
     template opDispatch(string s)
     {
         enum int opDispatch = 8;
     }
}

This impelemtation is an enum template that's essentially 
creating an enum with a single member, also called a "manifest 
constant" (a compile-time constant):

https://dlang.org/spec/enum.html#manifest_constants

This is an eponymous template, which means it can be accessed 
directly as opDispatch without using dot notation on the template 
name (opDispatch.opDispatch). So given `d` of type `D`, when the 
compiler sees `d.foo`, it looks for `foo` in the `D` struct. It 
doesn't find it, but it does find `opDisptach`, so it 
instantiated `d.opDispatch!"foo"` which, in this case, produces 
the number `8` as a compile-time constant.

Both the Yes and No structs use this technique:

struct Yes
{
     template opDispatch(string name)
     {
         enum opDispatch = Flag!name.yes;
     }
}

So when you call Yes.keepTerminator, you're getting 
Flag!"keepTerminator".yes as a result.

The point behind the structs is so that people who make use of 
Flag in their function parameter lists don't need to actually 
define an alias:

"The structs Yes and No are provided as shorthand for 
Flag!"Name".yes and Flag!"Name".no and are preferred for brevity 
and readability. These convenience structs mean it is usually 
unnecessary and counterproductive to create an alias of a Flag as 
a way of avoiding typing out the full type while specifying the 
affirmative or negative options."

So when implementing your function with a Flag!"foo", you can 
skip the alias and users can call the function with Yes.foo and 
No.foo.

However, I believe that the aliases for KeepTerminator in 
std.string (for splitLines and lineSplitter) and std.stdio (for 
byLine and byLineCopy) predate the implementation of the Yes and 
No structs in std.typecons, but were kept around so as not to 
break any code.

So, to summarize:

> 1. Yes.keepTerminator

This is because of Yes is a struct with an opDispatch template 
that "forwards" to Flag!"keepTerminator".yes. This is the 
preferred syntax and will work with any Flag parameter.

> 2. KeepTerminator.yes

This is because KeepTerminator is an alias to 
Flag!"keepTerminator". This syntax will only work on any given 
Flag parameter if the function implementer defines the alias.

> 3. Flag!"keepTerminator".yes

This is because Flag is a templated enum that takes a string 
parameter and has two members: yes and no. This always works, 
because it's the root feature for which the above two syntaxes 
were implemented as conveniences.

> & Don't get me started on the autocomplete trying to get me to 
> use KeepTerminator.Flag.yes (VSCode & code-d)

code-d uses DScanner to implement autocompletion. It can get 
confused in certain instances when compile-time features are 
involved. If there isn't an issue on this yet, you should report 
it:

https://github.com/dlang-community/D-Scanner/issues








More information about the Digitalmars-d-learn mailing list