www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Postfix type notation - readability and parsing?

reply aliak <something something.com> writes:
This is the most "readable" formatting I could think of for long 
declarations with current syntax.

static immutable Option!(ushort, "p|port", "Sets the port used 
for serving.", "PORT", 8888)
         port;
static immutable Option!(int, "db-port", "Sets the port to use 
for connecting to the db", "DB_PORT", 5432)
         dbPort;
static immutable Option!(string, "db-host", "Sets the port to use 
for connecting to the db", "DB_HOST", "127.0.0.1")
         dbHost;

The template declaration is quite long so:

1) putting the variable name at the end makes it hard to see and 
you'd need to scroll horizontally to get to see what this type is 
for.

2) doing it like the above is confusing to see which name belongs 
to which type because you're so used to a type declaration being 
on one line.

3) Splitting it over multiple lines like:

static immutable Option!(
     ushort,
     "p|port",
     "Sets the port used for serving.",
     "PORT",
     8888
) port;

Turns a potentially X line file in to a X * 10 line file, and you 
have scroll just to see what all the variables are

4) Deciding to go with a mixin approach so you can have the 
variable name on the left and on the same line like:

mixin Option!("port", ushort, "p|port",  "Sets the port used for 
serving.", "PORT",  8888);

destroys any chance of auto completion on the enclosing type, and 
also means you have to implement two types (the mixin template 
and the underlying type - i.e. Option and OptionImpl)

5) putting it on the right after an opAssign ala:

auto port = Option!(...)

means you can't initialize any immutable data in constructors 
since the initialization happens in the declaration line.

If we had postfix then:

static immutable port: Option!(ushort, "p|port", ...);
static immutable dbPort: Option!(int, "db-port",...)
static immutable dbHost: Option!(string, "db-host",...)

Without line wrapping you can see all the main information you 
need with a glance of the code. Isn't that more readable (in this 
scenario)?

The same would apply for functions with long types like this:

SomeType!("this is the first", int, "some other thing goes here" 
foo() {
}

vs

function foo(): SomeType!("this is the first", int, "some other 
thing goes here") {
}

You can't find out that it's a function until you move your eyes 
all the way to the right in the former, but the latter is 
instantaneously apparent.

Consistency could also increase because you will have this:

auto a: int;
const b = 3;

instead of:

int a;
const b = 3;

The type is an inferred type or an explicit type but they can 
bother be on the right?

And finally, I'm curious how this would effect parsing? Maybe 
someone familiar with dmd internals can say if things would be 
easier? Because it feels like currently you need to look ahead 
when you see "int identifier" - is it a variable or function? 
Can't know until you go farther right? Are there other situations 
like this?

Cheers,
- Ali
Mar 05 2019
next sibling parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 5 March 2019 at 22:29:33 UTC, aliak wrote:
 3) Splitting it over multiple lines like:

 static immutable Option!(
     ushort,
     "p|port",
     "Sets the port used for serving.",
     "PORT",
     8888
 ) port;

 Turns a potentially X line file in to a X * 10 line file, and 
 you have scroll just to see what all the variables are
I actually think this version looks the best out of all the examples you've given. In fact, I'd probably split the declaration over multiple lines even if the type were placed on the right.
Mar 05 2019
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 5 March 2019 at 22:29:33 UTC, aliak wrote:
 1) putting the variable name at the end makes it hard to see 
 and you'd need to scroll horizontally to get to see what this 
 type is for.

 2) doing it like the above is confusing to see which name 
 belongs to which type because you're so used to a type 
 declaration being on one line.

 3) Splitting it over multiple lines like:

 static immutable Option!(
     ushort,
     "p|port",
     "Sets the port used for serving.",
     "PORT",
     8888
 ) port;

 4) Deciding to go with a mixin approach so you can have the 
 variable name on the left and on the same line like:

 mixin Option!("port", ushort, "p|port",  "Sets the port used 
 for serving.", "PORT",  8888);

 5) putting it on the right after an opAssign ala:

 auto port = Option!(...)
Just to add a 6) to the mix: alias port = Option!(immutable ushort, "p|port", "Sets the port used for serving.", "PORT", 8888); static this() { port = 1234; } template Option(T, string s, string s2, string s3, T defaultValue) { import std.traits : Unqual, CopyTypeQualifiers; struct Impl { Unqual!T _value; alias _value this; this(T t) {} } CopyTypeQualifiers!(T, Impl) Option; } // Also does the right thing in structs and classes: struct S { static alias port = Option!(immutable ushort, "p|port", "Sets the port used for serving.", "PORT", 8888); } static this() { S.port = 1234; } -- Simen
Mar 05 2019
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Wednesday, 6 March 2019 at 07:53:42 UTC, Simen Kjærås wrote:
 alias port = Option!(immutable ushort, "p|port", "Sets the port 
 used for serving.", "PORT", 8888);
Of course, one issue is the lack of __GENSYM__, meaning that this will only declare one variable, with two aliases: alias a = Option!(int, "", "", "", 0); alias b = Option!(int, "", "", "", 0); unittest { assert(&a == &b); } -- Simen
Mar 06 2019
parent reply aliak <something something.com> writes:
On Wednesday, 6 March 2019 at 08:09:48 UTC, Simen Kjærås wrote:
 On Wednesday, 6 March 2019 at 07:53:42 UTC, Simen Kjærås wrote:
 alias port = Option!(immutable ushort, "p|port", "Sets the 
 port used for serving.", "PORT", 8888);
Of course, one issue is the lack of __GENSYM__, meaning that this will only declare one variable, with two aliases: alias a = Option!(int, "", "", "", 0); alias b = Option!(int, "", "", "", 0); unittest { assert(&a == &b); } -- Simen
Thanks for that code above! And hmm.. __GENSYM__ ... is this documented anywhere? (I found a post by you on the forum about it but nothing in the docs)
Mar 07 2019
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 7 March 2019 at 19:43:31 UTC, aliak wrote:
 Thanks for that code above!

 And hmm.. __GENSYM__ ... is this documented anywhere? (I found 
 a post by you on the forum about it but nothing in the docs)
`gensym` is a function in some Lisp-family languages that's used to generate unique ientifiers for use in macros. [1] It sounds like __GENSYM__ is a proposed version of this for D, implemented as a compiler builtin like __FILE__ and __LINE__. [1] http://clhs.lisp.se/Body/f_gensym.htm
Mar 07 2019
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Mar 07, 2019 at 08:22:36PM +0000, Paul Backus via Digitalmars-d wrote:
 On Thursday, 7 March 2019 at 19:43:31 UTC, aliak wrote:
 Thanks for that code above!
 
 And hmm.. __GENSYM__ ... is this documented anywhere? (I found a
 post by you on the forum about it but nothing in the docs)
`gensym` is a function in some Lisp-family languages that's used to generate unique ientifiers for use in macros. [1] It sounds like __GENSYM__ is a proposed version of this for D, implemented as a compiler builtin like __FILE__ and __LINE__. [1] http://clhs.lisp.se/Body/f_gensym.htm
I've found the need for generating unique identifiers before, especially for temporaries inside `static foreach`. One day, it dawned on me that templates can be be used to generate new names, precisely because each template instantiation is given a unique identifier based on the template arguments: struct S(size_t i) { ... // declarations that depend on i } static foreach (i; 0 .. 10) {{ S!i x; // <-- bingo, new type identifier per loop index! }} Unfortunately, this does not solve the problem of unique identifiers for the variable name (as opposed to the type name). But perhaps the same idea of using templates to generate identifiers might still be applicable: // Warning: untested concept code alias gensym(size_t line = __LINE__) = Option!(int, "", "", "", 0); Or something along these lines, might be the ticket? Essentially, generate a new identifier keyed on __LINE__ (you could also add __FILE__ if you wish to differentiate between declarations across source files). So the identifiers would be gensym!123, gensym!456, etc.. T -- What are you when you run out of Monet? Baroque.
Mar 07 2019
parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 7 March 2019 at 21:06:28 UTC, H. S. Teoh wrote:
 Unfortunately, this does not solve the problem of unique 
 identifiers for the variable name (as opposed to the type 
 name). But perhaps the same idea of using templates to generate 
 identifiers might still be applicable:

 	// Warning: untested concept code
 	alias gensym(size_t line = __LINE__) = Option!(int, "", "", 
 "", 0);

 Or something along these lines, might be the ticket?  
 Essentially, generate a new identifier keyed on __LINE__ (you 
 could also add __FILE__ if you wish to differentiate between 
 declarations across source files). So the identifiers would be 
 gensym!123, gensym!456, etc..


 T
The only issue with using __LINE__ is that it requires gensym to be evaluated in the same scope as the one the variable is being declared in. For example, if you have a function that generates code as a string for use as a mixin, you can't use a __LINE__-based gensym to name symbols in the generated code: import std.format; string gensym(size_t id = __LINE__) { return format("_gensym_%d", id); } string generateCode() { enum varName = gensym; return q{ import std.stdio; int %1$s = 123; writeln("%1$s = ", %1$s); }.format(varName); } void main() { mixin(generateCode); //mixin(generateCode); // Error: _gensym_8 is already defined } In Lisp, gensym uses a global counter (like GCC's __COUNTER__ macro [1]), so this isn't an issue. [1] https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
Mar 09 2019
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Saturday, 9 March 2019 at 20:50:37 UTC, Paul Backus wrote:

 The only issue with using __LINE__ is that it requires gensym 
 to be evaluated in the same scope as the one the variable is 
 being declared in.
There are other issues: string gensym(size_t id = __LINE__) { return format("_gensym_%d", id); } static assert(gensym() == gensym()); // We don't want this to pass. unittest { string[2] s; static foreach (i; 0..2) { s[i] = gensym; } assert(s[0] == s[1]); // Nor this. } The first would be fixed by having __COL__ (being the column of the line on which gensym is being called). However, that does nothing for the second case. -- Simen
Mar 11 2019
parent Paul Backus <snarwin gmail.com> writes:
On Monday, 11 March 2019 at 08:13:04 UTC, Simen Kjærås wrote:
 On Saturday, 9 March 2019 at 20:50:37 UTC, Paul Backus wrote:

 The only issue with using __LINE__ is that it requires gensym 
 to be evaluated in the same scope as the one the variable is 
 being declared in.
There are other issues: string gensym(size_t id = __LINE__) { return format("_gensym_%d", id); } static assert(gensym() == gensym()); // We don't want this to pass. unittest { string[2] s; static foreach (i; 0..2) { s[i] = gensym; } assert(s[0] == s[1]); // Nor this. } The first would be fixed by having __COL__ (being the column of the line on which gensym is being called). However, that does nothing for the second case. -- Simen
These are definitely annoying, but you can at least get around them by writing stuff like `gensym(i)`, as suggested in H. S. Teoh's post.
Mar 11 2019
prev sibling parent arakan arkino <marcosdonalonso gmail.com> writes:
On Thursday, 7 March 2019 at 19:43:31 UTC, aliak wrote:
 On Wednesday, 6 March 2019 at 08:09:48 UTC, Simen Kjærås wrote:
 On Wednesday, 6 March 2019 at 07:53:42 UTC, Simen Kjærås wrote:
 alias port = Option!(immutable ushort, "p|port", "Sets the 
 port used for serving.", "PORT", 8888);
Of course, one issue is the lack of __GENSYM__, meaning that this will only declare one variable, with two aliases: alias a = Option!(int, "", "", "", 0); alias b = Option!(int, "", "", "", 0); unittest { assert(&a == &b); } -- Simen
Thanks for that code above! And hmm.. __GENSYM__ ... is this documented anywhere? (I found a post by you on the forum about it but nothing in the docs)
Sorry guys, I did see the question about Kotlin when googling, but I re-asked it for not to get answers like "Because Kotlin (Go, Rust, ...) dev team decided like this". I was interested in a more common answer: why does it become kind of an epidemic?
Mar 31 2019
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Wednesday, 6 March 2019 at 07:53:42 UTC, Simen Kjærås wrote:
 Just to add a 6) to the mix:

 alias port = Option!(immutable ushort, "p|port", "Sets the port 
 used for serving.", "PORT", 8888);

 static this() {
     port = 1234;
 }
Option!(...) is a type, not a value, so this should actually be written as: alias PortOption = Option!(...); PortOption port; static this() { port = 1234; }
Mar 06 2019
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Wednesday, 6 March 2019 at 17:27:55 UTC, Paul Backus wrote:
 On Wednesday, 6 March 2019 at 07:53:42 UTC, Simen Kjærås wrote:
 Just to add a 6) to the mix:

 alias port = Option!(immutable ushort, "p|port", "Sets the 
 port used for serving.", "PORT", 8888);

 static this() {
     port = 1234;
 }
Option!(...) is a type, not a value, so this should actually be written as: alias PortOption = Option!(...); PortOption port; static this() { port = 1234; }
Not if you use the code in my post, which included a definition of Option to support exactly the code I wrote. -- Simen
Mar 06 2019
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Tuesday, 5 March 2019 at 22:29:33 UTC, aliak wrote:
 This is the most "readable" formatting I could think of for 
 long declarations with current syntax.

 static immutable Option!(ushort, "p|port", "Sets the port used 
 for serving.", "PORT", 8888)
         port;
 static immutable Option!(int, "db-port", "Sets the port to use 
 for connecting to the db", "DB_PORT", 5432)
         dbPort;
 static immutable Option!(string, "db-host", "Sets the port to 
 use for connecting to the db", "DB_HOST", "127.0.0.1")
         dbHost;

 The template declaration is quite long so:

 1) putting the variable name at the end makes it hard to see 
 and you'd need to scroll horizontally to get to see what this 
 type is for.

 2) doing it like the above is confusing to see which name 
 belongs to which type because you're so used to a type 
 declaration being on one line.

 3) Splitting it over multiple lines like:

 static immutable Option!(
     ushort,
     "p|port",
     "Sets the port used for serving.",
     "PORT",
     8888
 ) port;

 Turns a potentially X line file in to a X * 10 line file, and 
 you have scroll just to see what all the variables are

 4) Deciding to go with a mixin approach so you can have the 
 variable name on the left and on the same line like:

 mixin Option!("port", ushort, "p|port",  "Sets the port used 
 for serving.", "PORT",  8888);

 destroys any chance of auto completion on the enclosing type, 
 and also means you have to implement two types (the mixin 
 template and the underlying type - i.e. Option and OptionImpl)

 5) putting it on the right after an opAssign ala:

 auto port = Option!(...)

 means you can't initialize any immutable data in constructors 
 since the initialization happens in the declaration line.

 If we had postfix then:

 static immutable port: Option!(ushort, "p|port", ...);
 static immutable dbPort: Option!(int, "db-port",...)
 static immutable dbHost: Option!(string, "db-host",...)

 Without line wrapping you can see all the main information you 
 need with a glance of the code. Isn't that more readable (in 
 this scenario)?

 The same would apply for functions with long types like this:

 SomeType!("this is the first", int, "some other thing goes 
 here" foo() {
 }

 vs

 function foo(): SomeType!("this is the first", int, "some other 
 thing goes here") {
 }

 You can't find out that it's a function until you move your 
 eyes all the way to the right in the former, but the latter is 
 instantaneously apparent.

 Consistency could also increase because you will have this:

 auto a: int;
 const b = 3;

 instead of:

 int a;
 const b = 3;

 The type is an inferred type or an explicit type but they can 
 bother be on the right?

 And finally, I'm curious how this would effect parsing? Maybe 
 someone familiar with dmd internals can say if things would be 
 easier? Because it feels like currently you need to look ahead 
 when you see "int identifier" - is it a variable or function? 
 Can't know until you go farther right? Are there other 
 situations like this?

 Cheers,
 - Ali
The "Postfix type notation", to quote your naming of it, can be ambiguous and it breaks the C-ish looking style of D. Example of ambiguity: function foo(): function():int [] {} What is the return type of foo ? 1. an array of `function():int` ? 2. a function pointer returning an array of int ? I think it's a bad and unrealistic idea for D. Having two different styles for this would be a major inconsistency. That being said you could certainly hack the compiler to get this style working. It's only a grammatical change and the same AST could be reused. Now to come back to the problem you concretely encounter, I know what your are trying to do and you can do it otherwise: use an UDA. Instead of static immutable Option!(ushort, "p|port", "Sets the port used for serving.", "PORT", 8888) port; try to do Option!("p|port", "Sets the port used for serving.", "PORT", 8888) static immutable ushort port; you see ?
Mar 06 2019
next sibling parent Basile B. <b2.temp gmx.com> writes:
On Wednesday, 6 March 2019 at 18:06:34 UTC, Basile B. wrote:
 On Tuesday, 5 March 2019 at 22:29:33 UTC, aliak wrote:
   Now to come back to the problem you concretely encounter, I 
 know what your are trying to do and you can do it otherwise: 
 use an UDA. Instead of

     static immutable Option!(ushort, "p|port", "Sets the port 
 used for serving.", "PORT", 8888) port;


 try to do

      Option!("p|port", "Sets the port used for serving.", 
 "PORT", 8888)
     static immutable ushort port;

 you see ?
Well since it's an uda you change the thing to a non template struct: Option("p|port", "Sets the port used for serving.", "PORT", 8888) static immutable ushort port; By the way i proposed this because I did something like that which worked quite correctly although no finished: https://github.com/Basile-z/iz/blob/d9e36efced7069600b2d04b5a30fe905d6125347/import/iz/options.d#L602
Mar 07 2019
prev sibling parent reply aliak <something something.com> writes:
On Wednesday, 6 March 2019 at 18:06:34 UTC, Basile B. wrote:

   The "Postfix type notation", to quote your naming of it, can 
 be ambiguous and it breaks the C-ish looking style of D. 
 Example of ambiguity:

 [...]
Hmm, thanks for pointing that out! I guess that's indeed ambiguous without parens or something.
 [...]
Yeah I guess I can do that as well as another option.
Mar 07 2019
parent Dennis <dkorpel gmail.com> writes:
On Thursday, 7 March 2019 at 20:18:28 UTC, aliak wrote:
 Hmm, thanks for pointing that out! I guess that's indeed 
 ambiguous without parens or something.
I'm not in favor of adding a second type syntax, but note that the current scheme is also ambiguous: const int[] Is that const(int[]) or const(int)[]? It's the former, if you want the latter use explicit parentheses. Operators in general are full of ambiguities that are solved by precedence and associativity rules and parens to override them.
Mar 08 2019
prev sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 5 March 2019 at 22:29:33 UTC, aliak wrote:
 This is the most "readable" formatting I could think of for 
 long declarations with current syntax.

 static immutable Option!(ushort, "p|port", "Sets the port used 
 for serving.", "PORT", 8888)
         port;
 static immutable Option!(int, "db-port", "Sets the port to use 
 for connecting to the db", "DB_PORT", 5432)
         dbPort;
 static immutable Option!(string, "db-host", "Sets the port to 
 use for connecting to the db", "DB_HOST", "127.0.0.1")
         dbHost;

 The template declaration is quite long so:
This works: struct Option(T...) { string a; } static port = immutable Option!(ushort, "p|port", "Sets the port used for serving.", "PORT", 8888)();
Mar 07 2019