www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Suffix-based literal syntax

reply Reiner Pope <some address.com> writes:
Here's a small thought to improve the syntax for some user-defined 
types: can we perhaps overload the suffix on literals, to give some user 
types nicer literals?

One of Walter's rationale for natively supporting complex numbers is to 
get nice looking literals:
    3 + 5i
  instead of
    3 + 5 * i
  or the more realistic
    complex(3, 5)


I propose that, for number and string literals (which already sport 
suffixes at the moment) if a suffix is found not from the language, it 
be rewritten as an opSuffix call, so
        5km
    turns into
        opSuff_km(5)

For reasons of sanity, the suffix should probably only be alphabetical, 
upper and lower case.


This isn't a general solution to literals for user types, but I can 
imagine it as useful for:

1. Complex numbers:
You could get the same syntax as we currently get, but in a library. 
That seems pretty impressive.

2. SI units libraries:
Instead of (acceptable) 5 * km, you can write 5km as a literal.

3. Some kind of native type wrapper:
Perhaps for dstring, or some other class that wraps strings, you could 
write:
    "foo"s
denoting a dstring literal, as opposed to char[]/wchar[]/dchar[] literal.

Similarly, you might want to write a vector class for this:
     [5, 4, 3]v
being the same as Vec([5, 4, 3]) but much more visually pleasing.

4. Wacky syntax extensions. I remember seeing Bill Baxter use the suffix 
'i' to denote 'inclusive' for ranges. I'm sure many other (ab)uses could 
come to mind.

----

If problems come with suffix clashes, renaming of imports could help:

import foo.string: opSuff_fs = opSuff_s;
import bar.string: opSuff_bs = opSuff_s;

...
auto a = "abc"fs; // a is a foo string
auto b = "abc"bs; // b is a bar string


I think it's kind of a nice idea, since it can give some user types a 
nicer syntax without creating any new ambiguities.


Of course, given how much is going on with const et al at the moment, I 
don't expect anything to come of this at the moment, but I hope someone 
likes it.


    -- Reiner
May 30 2007
next sibling parent Reiner Pope <some address.com> writes:
Reiner Pope wrote:
 I propose that, for number and string literals

May 30 2007
prev sibling next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
I like it; it'd make working with JNI types a lot easier. Would this work with
the current parser, though?

Reiner Pope Wrote:

 Here's a small thought to improve the syntax for some user-defined 
 types: can we perhaps overload the suffix on literals, to give some user 
 types nicer literals?
 
 One of Walter's rationale for natively supporting complex numbers is to 
 get nice looking literals:
     3 + 5i
   instead of
     3 + 5 * i
   or the more realistic
     complex(3, 5)
 
 
 I propose that, for number and string literals (which already sport 
 suffixes at the moment) if a suffix is found not from the language, it 
 be rewritten as an opSuffix call, so
         5km
     turns into
         opSuff_km(5)
 
 For reasons of sanity, the suffix should probably only be alphabetical, 
 upper and lower case.
 
 
 This isn't a general solution to literals for user types, but I can 
 imagine it as useful for:
 
 1. Complex numbers:
 You could get the same syntax as we currently get, but in a library. 
 That seems pretty impressive.
 
 2. SI units libraries:
 Instead of (acceptable) 5 * km, you can write 5km as a literal.
 
 3. Some kind of native type wrapper:
 Perhaps for dstring, or some other class that wraps strings, you could 
 write:
     "foo"s
 denoting a dstring literal, as opposed to char[]/wchar[]/dchar[] literal.
 
 Similarly, you might want to write a vector class for this:
      [5, 4, 3]v
 being the same as Vec([5, 4, 3]) but much more visually pleasing.
 
 4. Wacky syntax extensions. I remember seeing Bill Baxter use the suffix 
 'i' to denote 'inclusive' for ranges. I'm sure many other (ab)uses could 
 come to mind.
 
 ----
 
 If problems come with suffix clashes, renaming of imports could help:
 
 import foo.string: opSuff_fs = opSuff_s;
 import bar.string: opSuff_bs = opSuff_s;
 
 ...
 auto a = "abc"fs; // a is a foo string
 auto b = "abc"bs; // b is a bar string
 
 
 I think it's kind of a nice idea, since it can give some user types a 
 nicer syntax without creating any new ambiguities.
 
 
 Of course, given how much is going on with const et al at the moment, I 
 don't expect anything to come of this at the moment, but I hope someone 
 likes it.
 
 
     -- Reiner

May 30 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Its a neat idea.  I wonder if the proposed transformation is the right 
way though.

 I propose that, for number and string literals (which already sport 
 suffixes at the moment) if a suffix is found not from the language, it 
 be rewritten as an opSuffix call, so
         5km
     turns into
         opSuff_km(5)


Maybe that should be km.opSuff(5) --bb
May 30 2007
parent reply Reiner Pope <some address.com> writes:
Bill Baxter wrote:
 Its a neat idea.  I wonder if the proposed transformation is the right 
 way though.
 
 I propose that, for number and string literals (which already sport 
 suffixes at the moment) if a suffix is found not from the language, 
 it be rewritten as an opSuffix call, so
         5km
     turns into
         opSuff_km(5)


Maybe that should be km.opSuff(5) --bb

a few letters (like km) yet the type name would normally be much longer. I know that the rest of the overloaded operators must be defined as member functions of one of the types, but that doesn't really fit with the suffixes, given that they "operate" on the built-in types which already have literals. I envisage a usage something like this: struct LengthKM { ... } alias LengthKM.opCall opSuff_km; // Of course this would actually be auto generated by a template, but still... class String { ... } alias String.this opSuff_s; // does this alias actually work? It should. With a String class, for instance, there's not much point in 'String' being the suffix -- I hardly think "abc"String is better than String("abc") My initial thought was also a transformation like: opSuff!("km")(5) However, I think the main advantage that a free function has is that it can be renamed on importing and it separates the suffix name from the type name; my example from my original post is how I would actually expect suffixes to be used: import foo.string: opSuff_fs = opSuff_s; import bar.string: opSuff_bs = opSuff_s; ... auto a = "abc"fs; // a is a foo string auto b = "abc"bs; // b is a bar string -- Reiner
May 30 2007
parent reply Reiner Pope <some address.com> writes:
Reiner Pope wrote:
 I envisage a usage something like this:
 
 struct LengthKM { ... }
 alias LengthKM.opCall opSuff_km;
 // Of course this would actually be auto generated by a template, but 
 still...

Oh, and you might want: struct Length {...} Length opSuff_km(real r) { return Length(1000, r); } Length opSuff_m(real r) { return Length(1, r); } Length opSuff_mm(real r) { return Length(0.001, r); }
May 30 2007
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
Just had a thought: That's all well and good, but what types do these take? A
number could be floating-point or not (there are multiple floating point
types...), etc., etc. Operator overloading might work for some things, but
since there's no return-type operator overloading, there could be ambiguity.
Consider:

uint opSuff_abs(int value) { return abs >= 0 ? abs : -abs; }
ulong ofSuff_abs(long value) { return abs >= 0 ? abs : -abs; }

// ...

uint foo = -5abs; // While we know it has to call the int method; there's no
return-type overloading
long bar = -5abs; // Even with return-type overloading, implicit casts still
add ambiguity

Reiner Pope Wrote:

 Reiner Pope wrote:
 I envisage a usage something like this:
 
 struct LengthKM { ... }
 alias LengthKM.opCall opSuff_km;
 // Of course this would actually be auto generated by a template, but 
 still...

Oh, and you might want: struct Length {...} Length opSuff_km(real r) { return Length(1000, r); } Length opSuff_m(real r) { return Length(1, r); } Length opSuff_mm(real r) { return Length(0.001, r); }

May 30 2007
next sibling parent Chris Nicholson-Sauls <ibisbasenji gmail.com> writes:
Actually in the case you present, there is no ambiguity.  There /is/
overloading on 
return-type, just not overloading on /only/ return-type.  (I had the same
impression 
initially, but not so long ago was corrected on it -- to great joy, as it
happens.)

Ambiguities caused by implicit casts can be caught by enabling warnings, and
for that 
matter I'd expect the compiler to say something about the double-casting going
on here, 
since neither of the return types are 'long'.  (Most likely, in this case, the
'uint(int)' 
version would be selected, and since a 'uint' can fit safely in a 'long', there
should be 
no mis-behavior.)

For that matter, your example is best defined as a template, seeing as the
implementation 
is identical.  Probably quite a lot of these suffixes would be templates.

All in all, I'm neutral on the idea.  It might have its uses, and would
certainly find 
followers amongst the mathematicians among us... but I'd bet most people would
rarely if 
ever have a significant use for it.

-- Chris Nicholson-Sauls

Robert Fraser wrote:
 Just had a thought: That's all well and good, but what types do these take? A
number could be floating-point or not (there are multiple floating point
types...), etc., etc. Operator overloading might work for some things, but
since there's no return-type operator overloading, there could be ambiguity.
Consider:
 
 uint opSuff_abs(int value) { return abs >= 0 ? abs : -abs; }
 ulong ofSuff_abs(long value) { return abs >= 0 ? abs : -abs; }
 
 // ...
 
 uint foo = -5abs; // While we know it has to call the int method; there's no
return-type overloading
 long bar = -5abs; // Even with return-type overloading, implicit casts still
add ambiguity
 
 Reiner Pope Wrote:
 
 Reiner Pope wrote:
 I envisage a usage something like this:

 struct LengthKM { ... }
 alias LengthKM.opCall opSuff_km;
 // Of course this would actually be auto generated by a template, but 
 still...

struct Length {...} Length opSuff_km(real r) { return Length(1000, r); } Length opSuff_m(real r) { return Length(1, r); } Length opSuff_mm(real r) { return Length(0.001, r); }


May 30 2007
prev sibling parent Reiner Pope <some address.com> writes:
Robert Fraser wrote:
 Just had a thought: That's all well and good, but what types do these take? A
number could be floating-point or not (there are multiple floating point
types...), etc., etc. Operator overloading might work for some things, but
since there's no return-type operator overloading, there could be ambiguity.
Consider:
 
 uint opSuff_abs(int value) { return abs >= 0 ? abs : -abs; }
 ulong ofSuff_abs(long value) { return abs >= 0 ? abs : -abs; }
 
 // ...
 
 uint foo = -5abs; // While we know it has to call the int method; there's no
return-type overloading
 long bar = -5abs; // Even with return-type overloading, implicit casts still
add ambiguity

The way I would approach that is: uint opSuff_abs(int value) { ... } ulong opSuff_absL(long value) { ... } alias opSuff_absL opSuff_abs; auto foo = -5abs; // rewritten as abs(-5) -- compiler chooses the int overload auto bar = -100000000000abs; // rewritten as abs(-1000000000000) -- compiler chooses long overload since that literal is long auto bam = -5absL; // only long version is available. So, if there is a need to specify overloads, it's up to the library implementor to provide specially-named suffixes. -- Reiner
May 30 2007
prev sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Reiner Pope wrote:
 Reiner Pope wrote:
 I envisage a usage something like this:

 struct LengthKM { ... }
 alias LengthKM.opCall opSuff_km;
 // Of course this would actually be auto generated by a template, but
 still...

Oh, and you might want: struct Length {...} Length opSuff_km(real r) { return Length(1000, r); } Length opSuff_m(real r) { return Length(1, r); } Length opSuff_mm(real r) { return Length(0.001, r); }

What if we just got type extensions instead? Length km(this real r) { return Length(1000, r); } Length m(this real r) { return Length(1000, r); } auto distance = 15 .km; // Space to break the float literal auto otherdis = (10).m; // Or use parens That way, we don't need any new parsing rules, and this can be extended to any type, to do pretty much anything you want. -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
May 30 2007
parent Robert Fraser <fraserofthenight gmail.com> writes:
I like that more; it's clearer.

Daniel Keep Wrote:

 
 
 Reiner Pope wrote:
 Reiner Pope wrote:
 I envisage a usage something like this:

 struct LengthKM { ... }
 alias LengthKM.opCall opSuff_km;
 // Of course this would actually be auto generated by a template, but
 still...

Oh, and you might want: struct Length {...} Length opSuff_km(real r) { return Length(1000, r); } Length opSuff_m(real r) { return Length(1, r); } Length opSuff_mm(real r) { return Length(0.001, r); }

What if we just got type extensions instead? Length km(this real r) { return Length(1000, r); } Length m(this real r) { return Length(1000, r); } auto distance = 15 .km; // Space to break the float literal auto otherdis = (10).m; // Or use parens That way, we don't need any new parsing rules, and this can be extended to any type, to do pretty much anything you want. -- Daniel -- int getRandomNumber() { return 4; // chosen by fair dice roll. // guaranteed to be random. } http://xkcd.com/ v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/

May 31 2007
prev sibling parent "David B. Held" <dheld codelogicconsulting.com> writes:
Reiner Pope wrote:
 Here's a small thought to improve the syntax for some user-defined 
 types: can we perhaps overload the suffix on literals, to give some user 
 types nicer literals?
 [...]

I've considered it and argued for it before, but when it comes down to it, how much is this going to be used? Really, it's only going to be used for manifest constants, which tend to not be terribly numerous. Most other uses of dimensional types are as *variables*, and there the dimension is encoded in the type, not the suffix. The fact that we can get "almost there" syntax demotivates this feature for some folks. Dave
May 30 2007