www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - foo => "bar" key/value literals in D!

reply Adam D. Ruppe <destructionator gmail.com> writes:
Have I gone completely mad?!?!

---
void main() {
         import std.stdio;
         writeln(obj!(
                 foo => "bar",
                 baz => 12
         ));
}
---

Prints out:

{
         foo: bar
         baz: 12
}



A few tweaks would make a whole loose typed hash thing more akin 
to Ruby or PHP than D. What's obj? Behold:


string obj(T...)() {
         import std.conv, std.traits;
         string jsonResult = "{";
         foreach(arg; T) {
                 jsonResult ~= "\n\t";

                 // I don't know why the usual is(__parameters) 
trick
                 // won't work here, but a stringof hack will!
                 string hack = typeof(arg!string).stringof;
                 import std.string;
                 hack = hack[hack.indexOf("function(string ") + 
"function(string ".length .. $];
                 hack = hack[0 .. hack.indexOf(")")];

                 jsonResult ~= hack;
                 jsonResult ~= ": ";
                 jsonResult ~= to!string(arg(""));

         }
         jsonResult ~= "\n}";
         return jsonResult;
}




As you probably know, D has a couple lambda literal syntaxes. One 
of these is the fat arrow, with a valid form of argument => 
return_expression.

The compiler makes templates out of these when you pass them 
around.... and those templates contain the parameters, including 
the name, and are callable code (if instantiated with a concrete 
type).

I was disappointed to see the ordinary reflection tools didn't 
work here - I know, I'm abusing the language - but the trusty old 
.stringof hack did! Combined with the magic knowledge that these 
things are templates, I instantiated them (tbh I was a bit 
surprised it actually let me!) and extracted the name of the 
argument.

Then simply running it results in the value at runtime.

Combining these with facilities for building values - here, I 
just did a string but it could be whatever - results in an array 
that almost looks like it was pulled from one of those dynamic 
languages.



Complete program here (may include bug fixes made after posting 
this announcement):

http://arsdnet.net/dcode/have_i_lost_my_marbles.d


I might actually use this nasty trick in some of my ugly code.
May 23 2016
next sibling parent reply Daniel N <ufo orbiting.us> writes:
On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
That makes two of us, I also use similar techniques. Please help argue in favour of pulling this: https://github.com/dlang/phobos/pull/3620/files https://issues.dlang.org/show_bug.cgi?id=13780 This pull request just removes an intentional restriction and make this feature more easily accessible for meta-programming, so that not everyone has to reinvent the wheel in their own libraries.
May 23 2016
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Monday, 23 May 2016 at 20:08:11 UTC, Daniel N wrote:
 This pull request just removes an intentional restriction and 
 make this feature more easily accessible for meta-programming, 
 so that not everyone has to reinvent the wheel in their own 
 libraries.
Soooo I'm actually not entirely convinced on that change. Parameter names should *not* be part of a function pointer or delegate type - that's not supported and kinda randomly buggy to depend on (if the types match, the names might not: the compiler is free to reuse the structure for matching types which means the names you get might be the ones from a previous definition!) Alias arguments are IMO different though, because they aren't a function pointer/delegate type, they are actually a function symbol, just one that happens to be defined inline. They should work. But I think the restriction on delegates' parameter names is actually a good one, protecting you from bugs in more complex programs.
May 23 2016
parent reply Daniel N <ufo orbiting.us> writes:
On Tuesday, 24 May 2016 at 01:33:40 UTC, Adam D. Ruppe wrote:
 On Monday, 23 May 2016 at 20:08:11 UTC, Daniel N wrote:
 This pull request just removes an intentional restriction and 
 make this feature more easily accessible for meta-programming, 
 so that not everyone has to reinvent the wheel in their own 
 libraries.
Soooo I'm actually not entirely convinced on that change. Parameter names should *not* be part of a function pointer or delegate type
From an end-user perspective I find it reasonable to expect that an API which takes lambda:s works consistently for both below examples. i.e. if we support one we should support the other. [1] fun!( x => y) [2] fun!((int x) => y) Currently I just copy/paste the fix to ParameterIdentifierTuple everywhere as the needed primitives already exist. Removing the already existing primitives now is imho too late, as it breaks code, which sometimes is OK, but this time there's no clear solution how to rewrite the broken code. Unfinished Library based Named-parameters proof-of-concept: (yes, I know. It needs to be updated to handle case [2]) https://dpaste.dzfl.pl/ae55de677f86 /vote open floodgates for introspection, capitalizing on the strengths of D.
May 25 2016
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2016-05-25 11:24, Daniel N wrote:

 From an end-user perspective I find it reasonable to expect that an API
 which takes lambda:s works consistently for both below examples. i.e. if
 we support one we should support the other.

 [1] fun!(     x  => y)
 [2] fun!((int x) => y)

 Currently I just copy/paste the fix to ParameterIdentifierTuple
 everywhere as the needed primitives already exist.

 Removing the already existing primitives now is imho too late, as it
 breaks code, which sometimes is OK, but this time there's no clear
 solution how to rewrite the broken code.

 Unfinished Library based Named-parameters proof-of-concept:
 (yes, I know. It needs to be updated to handle case [2])
 https://dpaste.dzfl.pl/ae55de677f86

 /vote open floodgates for introspection, capitalizing on the strengths
 of D.
I don't think this is the right approach. I think the correct approach is to allow introspection of template parameters [1]. That would allow to get the parameter names without having to instantiate the template. ParameterIdentifierTuple can hopefully be updated to take advantage of this feature when it's available in compiler without requiring to change the API of ParameterIdentifierTuple. [1] https://github.com/dlang/dmd/pull/5201 -- /Jacob Carlborg
May 25 2016
next sibling parent reply Daniel N <ufo orbiting.us> writes:
On Wednesday, 25 May 2016 at 11:22:41 UTC, Jacob Carlborg wrote:
 On 2016-05-25 11:24, Daniel N wrote:

 ParameterIdentifierTuple can hopefully be updated to take 
 advantage of this feature when it's available in compiler 
 without requiring to change the API of ParameterIdentifierTuple.

 [1] https://github.com/dlang/dmd/pull/5201
Issue9608 is good too, but insufficient. import std.traits; static assert(__traits(isTemplate, a => 0)); static assert(!__traits(isTemplate, (int a) => 0)); The 2nd case can't be solved by 9608 since it's about introspecting templates and "(int a) => 0)" is no template.
May 25 2016
parent Jacob Carlborg <doob me.com> writes:
On 2016-05-25 13:57, Daniel N wrote:

 Issue9608 is good too, but insufficient.

 import std.traits;
 static assert(__traits(isTemplate, a => 0));
 static assert(!__traits(isTemplate, (int a) => 0));

 The 2nd case can't be solved by 9608 since it's about introspecting
 templates and "(int a) => 0)" is no template.
Hmm, I though the second case was already possible, but I see now that it isn't. -- /Jacob Carlborg
May 25 2016
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Wednesday, 25 May 2016 at 11:22:41 UTC, Jacob Carlborg wrote:
 I don't think this is the right approach. I think the correct 
 approach is to allow introspection of template parameters [1]. 
 That would allow to get the parameter names without having to 
 instantiate the template.
Is that true? I don't claim to know exactly how the compiler works, but given a template lambda: foo => 3 Doesn't it generate something like the following? template __lambda10293(T) { <deduced return type> __lambda10293(T foo) { return 3; } } In that case, "foo" is not a template parameter, but a runtime parameter, so template parameter introspection would not help in this case.
May 25 2016
parent Jacob Carlborg <doob me.com> writes:
On 2016-05-25 20:55, Meta wrote:

 Is that true? I don't claim to know exactly how the compiler works, but
 given a template lambda:

 foo => 3

 Doesn't it generate something like the following?

 template __lambda10293(T)
 {
      <deduced return type> __lambda10293(T foo)
      {
          return 3;
      }
 }

 In that case, "foo" is not a template parameter, but a runtime
 parameter, so template parameter introspection would not help in this case.
Well, a runtime parameter in a template then :) -- /Jacob Carlborg
May 26 2016
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Wednesday, 25 May 2016 at 09:24:31 UTC, Daniel N wrote:
 From an end-user perspective I find it reasonable to expect 
 that an API which takes lambda:s works consistently for both 
 below examples. i.e. if we support one we should support the 
 other.

 [1] fun!(     x  => y)
 [2] fun!((int x) => y)
Those are indeed both can work to fetch names, though they aren't exactly the same since the first one is a template and the second one isn't. The technique is slightly different right now, but both doable (and I have no objection to unifying the techniques) The big difference I'm talking about is passing them as an alias argument vs as a runtime argument. Runtime function pointers lose many of the introspection capabilities of compile time alias arguments (well, runtime args still can pretend to give you names, but you'll find it doesn't actually work very well). From the looks of the Phobos PR too, it looks like some of the commentators conflated the two issues too: template vs non-template isn't the same as alias vs delegate.
May 25 2016
prev sibling next sibling parent reply Meta <jared771 gmail.com> writes:
On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!

 ---
 void main() {
         import std.stdio;
         writeln(obj!(
                 foo => "bar",
                 baz => 12
         ));
 }
 ---

 Prints out:

 {
         foo: bar
         baz: 12
 }



 A few tweaks would make a whole loose typed hash thing more 
 akin to Ruby or PHP than D. What's obj? Behold:


 string obj(T...)() {
         import std.conv, std.traits;
         string jsonResult = "{";
         foreach(arg; T) {
                 jsonResult ~= "\n\t";

                 // I don't know why the usual is(__parameters) 
 trick
                 // won't work here, but a stringof hack will!
                 string hack = typeof(arg!string).stringof;
                 import std.string;
                 hack = hack[hack.indexOf("function(string ") + 
 "function(string ".length .. $];
                 hack = hack[0 .. hack.indexOf(")")];

                 jsonResult ~= hack;
                 jsonResult ~= ": ";
                 jsonResult ~= to!string(arg(""));

         }
         jsonResult ~= "\n}";
         return jsonResult;
 }




 As you probably know, D has a couple lambda literal syntaxes. 
 One of these is the fat arrow, with a valid form of argument => 
 return_expression.

 The compiler makes templates out of these when you pass them 
 around.... and those templates contain the parameters, 
 including the name, and are callable code (if instantiated with 
 a concrete type).

 I was disappointed to see the ordinary reflection tools didn't 
 work here - I know, I'm abusing the language - but the trusty 
 old .stringof hack did! Combined with the magic knowledge that 
 these things are templates, I instantiated them (tbh I was a 
 bit surprised it actually let me!) and extracted the name of 
 the argument.

 Then simply running it results in the value at runtime.

 Combining these with facilities for building values - here, I 
 just did a string but it could be whatever - results in an 
 array that almost looks like it was pulled from one of those 
 dynamic languages.



 Complete program here (may include bug fixes made after posting 
 this announcement):

 http://arsdnet.net/dcode/have_i_lost_my_marbles.d


 I might actually use this nasty trick in some of my ugly code.
Clever and terrible. Now just modify the code to generate a struct or class and you've invented new anonymous struct/object syntax. Also, I think this has revealed a bug (or deficiency) in the compiler. If you put this inside the foreach loop: import std.traits; alias inst = arg!string; pragma(msg, ParameterIdentifierTuple!inst); It prints out `tuple("")` both times, meaning that for some reason it sees these lambdas as having no parameter names.
May 23 2016
next sibling parent Meta <jared771 gmail.com> writes:
On Tuesday, 24 May 2016 at 01:11:39 UTC, Meta wrote:
 Clever and terrible. Now just modify the code to generate a 
 struct or class and you've invented new anonymous struct/object 
 syntax.

 Also, I think this has revealed a bug (or deficiency) in the 
 compiler. If you put this inside the foreach loop:

 		import std.traits;
 		alias inst = arg!string;
 		pragma(msg, ParameterIdentifierTuple!inst);

 It prints out `tuple("")` both times, meaning that for some 
 reason it sees these lambdas as having no parameter names.
And looks like I filed a bug 2 years ago and then promptly forgot about it.
May 23 2016
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 24 May 2016 at 01:11:39 UTC, Meta wrote:
 Clever and terrible. Now just modify the code to generate a 
 struct or class and you've invented new anonymous struct/object 
 syntax.
Indeed.
 Also, I think this has revealed a bug (or deficiency) in the 
 compiler. If you put this inside the foreach loop:
Right, I tried that too and it didn't work... and I think it should, since this is an alias argument instead of a runtime function pointer, the names should be available. I tried the low level is(foo params == __parameters) too to no avail, the compiler is the buggy one here rather than Phobos.
May 23 2016
prev sibling next sibling parent Bauss <jj_1337 live.dk> writes:
On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!

 ---
 void main() {
         import std.stdio;
         writeln(obj!(
                 foo => "bar",
                 baz => 12
         ));
 }
 ---

 [...]
This is a pretty amazing find!
May 23 2016
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On 2016-05-23 21:00, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!

 ---
 void main() {
          import std.stdio;
          writeln(obj!(
                  foo => "bar",
                  baz => 12
          ));
 }
 ---

 Prints out:

 {
          foo: bar
          baz: 12
 }



 A few tweaks would make a whole loose typed hash thing more akin to Ruby
 or PHP than D. What's obj? Behold:


 string obj(T...)() {
          import std.conv, std.traits;
          string jsonResult = "{";
          foreach(arg; T) {
                  jsonResult ~= "\n\t";

                  // I don't know why the usual is(__parameters) trick
                  // won't work here, but a stringof hack will!
                  string hack = typeof(arg!string).stringof;
                  import std.string;
                  hack = hack[hack.indexOf("function(string ") +
 "function(string ".length .. $];
                  hack = hack[0 .. hack.indexOf(")")];

                  jsonResult ~= hack;
                  jsonResult ~= ": ";
                  jsonResult ~= to!string(arg(""));

          }
          jsonResult ~= "\n}";
          return jsonResult;
 }
That's pretty cool and pretty ugly :).
 As you probably know, D has a couple lambda literal syntaxes. One of
 these is the fat arrow, with a valid form of argument => return_expression.

 The compiler makes templates out of these when you pass them around....
 and those templates contain the parameters, including the name, and are
 callable code (if instantiated with a concrete type).

 I was disappointed to see the ordinary reflection tools didn't work here
 - I know, I'm abusing the language - but the trusty old .stringof hack
 did! Combined with the magic knowledge that these things are templates,
 I instantiated them (tbh I was a bit surprised it actually let me!) and
 extracted the name of the argument.
__parameters doesn't work because an "untyped" lambda is a template and __parameters works with functions, but I guess you already know that. Instantiating the lambda and then using __parameters should work. There's a PR for DMD which adds support for inspecting template parameters [1] that would help. Unfortunately it's closed. Here's a version building an associative array mapping strings to variants: import std.stdio : println = writeln; import std.variant; Variant[string] hash(T...)() { import std.conv, std.traits; Variant[string] aa; foreach(arg; T) { // I don't know why the usual is(__parameters) trick // won't work here, but a stringof hack will! string hack = typeof(arg!string).stringof; import std.string; hack = hack[hack.indexOf("function(string ") + "function(string ".length .. $]; hack = hack[0 .. hack.indexOf(")")]; aa[hack] = Variant(arg("")); } return aa; } void main() { auto h = hash!( foo => 3, bar => "asd" ); writeln(h); } [1] https://github.com/dlang/dmd/pull/5201 -- /Jacob Carlborg
May 23 2016
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/23/16 3:00 PM, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
This is very creative, thanks. Definitely make it part of TWID :o). Apparently it can be made to work with non-templates as well, see https://dpaste.dzfl.pl/c4b7a8b6978b. Regarding applying a hack/ingenious label, I'd cautiously say "looks interesting" and wait for a bit of experience to accumulate. -- Andrei
May 24 2016
parent Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 24 May 2016 at 16:00:32 UTC, Andrei Alexandrescu 
wrote:
 Apparently it can be made to work with non-templates as well, 
 see https://dpaste.dzfl.pl/c4b7a8b6978b.
Oh, that's the buggy area. The compiler keeps parameter names for runtime delegates... but it also reuses the structures. Thus, in simple examples, it works, but outside that it becomes very unreliable. Parameter names are NOT part of a delegate's type. But an alias parameter to a template is a different story!
May 25 2016
prev sibling next sibling parent reply Vladimir Panteleev <thecybershadow.lists gmail.com> writes:
On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
Yes, though what does it have to do with this thread? :D This is by far the most appealing way to implement named arguments that I've seen so far: https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
May 27 2016
next sibling parent reply Taylor Hillegeist <taylorh140 gmail.com> writes:
On Friday, 27 May 2016 at 18:10:59 UTC, Vladimir Panteleev wrote:
 On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
Yes, though what does it have to do with this thread? :D This is by far the most appealing way to implement named arguments that I've seen so far: https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
This is very nice... way more clean than I imagined possible.
May 27 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 5/27/16 10:17 PM, Taylor Hillegeist wrote:
 On Friday, 27 May 2016 at 18:10:59 UTC, Vladimir Panteleev wrote:
 On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
Yes, though what does it have to do with this thread? :D This is by far the most appealing way to implement named arguments that I've seen so far: https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
This is very nice... way more clean than I imagined possible.
s/args/make/g and we have a nice function for std.conv. -- Andrei
May 30 2016
parent reply Vladimir Panteleev <thecybershadow.lists gmail.com> writes:
On Tuesday, 31 May 2016 at 06:21:03 UTC, Andrei Alexandrescu 
wrote:
 On 5/27/16 10:17 PM, Taylor Hillegeist wrote:
 On Friday, 27 May 2016 at 18:10:59 UTC, Vladimir Panteleev 
 wrote:
 On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
Yes, though what does it have to do with this thread? :D This is by far the most appealing way to implement named arguments that I've seen so far: https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
This is very nice... way more clean than I imagined possible.
s/args/make/g and we have a nice function for std.conv. -- Andrei
Do I gather from that that you propose adding only the overload that creates structs?
Jun 10 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/11/16 3:57 AM, Vladimir Panteleev wrote:
 On Tuesday, 31 May 2016 at 06:21:03 UTC, Andrei Alexandrescu wrote:
 On 5/27/16 10:17 PM, Taylor Hillegeist wrote:
 On Friday, 27 May 2016 at 18:10:59 UTC, Vladimir Panteleev wrote:
 On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!
Yes, though what does it have to do with this thread? :D This is by far the most appealing way to implement named arguments that I've seen so far: https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
This is very nice... way more clean than I imagined possible.
s/args/make/g and we have a nice function for std.conv. -- Andrei
Do I gather from that that you propose adding only the overload that creates structs?
No, both are nice to have. If one name is needed for both, "args" is indeed a good commonality. "Invoke function f with these args" and "Construct an object of type T with these args". The problem is it's not very intuitive in usage. Perhaps "call" for functions and "make" for objects are better. If we add these to std.experimental.functional and std.experimental.conv and they get a lot of usage we might make a small change to the language. Andrei
Jun 11 2016
parent reply ArturG <var.spool.mail700 gmail.com> writes:
On Saturday, 11 June 2016 at 09:07:43 UTC, Andrei Alexandrescu 
wrote:

 No, both are nice to have. If one name is needed for both, 
 "args" is indeed a good commonality. "Invoke function f with 
 these args" and "Construct an object of type T with these 
 args". The problem is it's not very intuitive in usage.

 Perhaps "call" for functions and "make" for objects are better. 
 If we add these to std.experimental.functional and 
 std.experimental.conv and they get a lot of usage we might make 
 a small change to the language.


 Andrei
would'nt it be possible to take the type as a runtime param and support struct, class and all callables? e.g. (&fun).args!(a=>5, b=>6); auto st = SomeStruct().args!(a=>3); auto sc = new SomeClass().args!(x => 20, y => 50); (&sc.fun).args!(x => 6); or named and positional args (&sc.fun).args!(x => 9)(3, 6);
Jun 11 2016
parent reply Vladimir Panteleev <thecybershadow.lists gmail.com> writes:
On Saturday, 11 June 2016 at 11:15:43 UTC, ArturG wrote:
 On Saturday, 11 June 2016 at 09:07:43 UTC, Andrei Alexandrescu 
 wrote:

 No, both are nice to have. If one name is needed for both, 
 "args" is indeed a good commonality. "Invoke function f with 
 these args" and "Construct an object of type T with these 
 args". The problem is it's not very intuitive in usage.

 Perhaps "call" for functions and "make" for objects are 
 better. If we add these to std.experimental.functional and 
 std.experimental.conv and they get a lot of usage we might 
 make a small change to the language.


 Andrei
would'nt it be possible to take the type as a runtime param and support struct, class and all callables? e.g. (&fun).args!(a=>5, b=>6);
Taking an address creates a function pointer, which loses the argument names. (Doesn't it?)
Jun 11 2016
parent ArturG <var.spool.mail700 gmail.com> writes:
On Saturday, 11 June 2016 at 15:46:59 UTC, Vladimir Panteleev 
wrote:

 Taking an address creates a function pointer, which loses the 
 argument names. (Doesn't it?)
unfortunatly yes, but it works as a struct or class initializer https://dpaste.dzfl.pl/6aad852aea90
Jun 11 2016
prev sibling parent reply John <johnch_atms hotmail.com> writes:
On Friday, 27 May 2016 at 18:10:59 UTC, Vladimir Panteleev wrote:
 This is by far the most appealing way to implement named 
 arguments that I've seen so far:

 https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
Another cool thing this enables: object initializers. T init(T, Args...)() { import std.traits : FunctionTypeOf; static struct DummyType {} static if (is(T == class)) T r = new T; else T r; foreach (arg; Args) { alias fun = arg!DummyType; static if (is(FunctionTypeOf!fun PT == __parameters)) { enum name = __traits(identifier, PT); foreach (m; __traits(allMembers, T)) { static if (name == m) __traits(getMember, r, m) = fun(DummyType.init); } } } return r; } struct Point { int x, y; } void main() { auto pt = init!(Point, x => 123, y => 456); assert(pt.x == 123 && pt.y == 456); }
Jun 08 2016
parent Vladimir Panteleev <thecybershadow.lists gmail.com> writes:
On Wednesday, 8 June 2016 at 09:19:46 UTC, John wrote:
 On Friday, 27 May 2016 at 18:10:59 UTC, Vladimir Panteleev 
 wrote:
 This is by far the most appealing way to implement named 
 arguments that I've seen so far:

 https://github.com/CyberShadow/ae/blob/master/utils/meta/args.d
Another cool thing this enables: object initializers.
Already in the link you quoted, FYI.
Jun 10 2016
prev sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Monday, 23 May 2016 at 19:00:40 UTC, Adam D. Ruppe wrote:
 Have I gone completely mad?!?!

 ---
 void main() {
         import std.stdio;
         writeln(obj!(
                 foo => "bar",
                 baz => 12
         ));
 }
 ---

 Prints out:

 {
         foo: bar
         baz: 12
 }
Pretty snazzy :) Like enums, except not...
May 31 2016