Thread overview
Re: D rawkz! -- custom writefln formats
Jan 17, 2013
Andrej Mitrovic
Jan 17, 2013
Miles Stoudenmire
Jan 17, 2013
H. S. Teoh
Jan 17, 2013
H. S. Teoh
Jan 17, 2013
Andrej Mitrovic
Jan 17, 2013
H. S. Teoh
Jan 18, 2013
Russel Winder
January 17, 2013
*Great!* Thanks for posting this!

I think it will be much easier to get a critical mass of enthusiastic D developers if we have some sort of "Effective D" (like Scott Meyer's "Effective C++") book/conf presentations/wiki page/whatever.

This kind of stuff would fit in the "talk proposal for someone else" I sent back in November, just after DConf was successfully funded:

-----

TITLE: D Programming in D (Or: Writing idiomatic D code)

ABSTRACT: Every language has its own sanctified idioms. Beginners learning a new language tend to use the idioms of the languages they already know in the new language. This is no different for someone learning D, so we have people doing, say, "C++ programing in D" or "Lua programming in D". This is not the best way to go. Learning the D idioms is a very important step to move from "I can write D programs" to "I know D". This talk presents a survey of the most important idioms commonly used by the D community. Hopefully, it will help you to start doing "D programming in D".

SPEAKER BIO: < Your bio here :-) >

-----

Cheers,

LMB

On Wed, Jan 16, 2013 at 4:13 PM, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
> It's been a while since the last "D rocks!" post. So here's one.
>
> I'm guessing that most D users don't realize extent of the flexibility of std.format -- I know I didn't until I discovered this little gem hidden in the docs (and then only implicitly!).
>
> But first, a motivating example. Suppose you have some kind of data structure, let's call it S, and at some point in your program, you want to output it. The most obvious way, of course, is to implement a toString() method:
>
>         struct S {
>                 ... // my sooper sekret data here!
>                 string toString() const pure @safe {
>                         // Typical implementation to minimize overhead
>                         // of constructing string
>                         auto app = appender!string();
>                         ... // transform data into string
>                         return app.data;
>                 }
>         }
>
>         void main() {
>                 auto s = S();
>                 ... // do wonderful stuff with s
>                 writeln(s);
>         }
>
> This is the "traditional" implementation, of course. A slight optimization that's possible is to realize that there's an alternative signature of toString() that alleviates the overhead of doing any string allocations at all:
>
>         struct S {
>                 // This method now takes a delegate to send data to.
>                 void toString(scope void delegate(const(char)[]) sink) const
>                 {
>                         // So you can write your data piecemeal to its
>                         // destination, without having to construct a
>                         // string and then return it.
>                         sink("prelude");
>                         sink(... /* beautiful prologue */);
>                         sink("concerto");
>                         sink(... /* beautiful body */);
>                         sink("finale");
>                         sink(... /* beautiful trailer */);
>
>                         // Look, ma! No string allocations needed!
>                 }
>         }
>
> So far so good. This is (or should be) all familiar ground.
>
> But suppose now you want to write your data to, say, a backup file in one format, but output your data to the user in another format. How would you do this?
>
> You could make toString() output one format, say the on-disk format,
> then add another method, say toUserReadableString() for outputting the
> other format. But this is ugly and non-extensible. What if you have a
> whole bunch of other formats that need to be output? You'd be drowning
> in toNetworkString(), toDatabaseString(), toHtmlEscapedString(), etc.,
> etc., which bloats your data's API and isn't very maintainable to boot.
>
> Here's where a little known feature of std.format comes in. Note that when you write:
>
>         S s;
>         writeln(s);
>
> This actually ultimately gets translated to the equivalent of:
>
>         S s;
>         writefln("%s", s);
>
> Where the %s specifier, of course, means "convert to the standard string representation". What is less known, though, is that this actually translates to something like this:
>
>         Writer w = ... /* writer object that outputs to stdout */
>         FormatSpec!Char fmt = ... /* object representing the meaning of "%s" */
>         s.toString((const(char)[] s) { w.put(s); }, fmt);
>
> In human language, this means that "%s" gets translated into a FormatSpec object containing "s" in its .spec field (and if you write, say, "%10s", the 10 gets stored in the .width field, etc.), and then this FormatSpec object gets passed to the toString method of the object being formatted, if it is defined with the correct signature. To see this in action, let's do this:
>
>         struct S {
>                 void toString(scope void delegate(const(char)[]) sink,
>                         FormatSpec!char fmt) const
>                 {
>                         // This is for probing how std.format works
>                         // under the hood.
>                         writeln(fmt.spec);
>                 }
>         }
>         void main() {
>                 S s;
>
>                 // Wait -- what? What on earth are %i, %j, %k, and %l?!
>                 writeln("%i", s);       // Hmm, prints "i"!
>                 writeln("%j", s);       // Hmm, prints "j"!
>                 writeln("%k", s);       // Hmm, prints "k"!
>                 writeln("%l", s);       // Hmm, prints "l"!
>         }
>
> Do you see what's going on? The format specifiers are not hard-coded into the library! You can invent your own specifiers, and they get passed into the toString method. This allows us to do this:
>
>         struct S {
>                 void toString(scope void delegate(const(char)[]) sink,
>                         FormatSpec!char fmt) const
>                 {
>                         switch(fmt.spec) {
>                         // Look, ma! I invented my own format specs!
>                         case "i":
>                                 // output first format to sink
>                                 break;
>                         case "j":
>                                 // output second format to sink
>                                 break;
>                         case "k":
>                                 // output third format to sink
>                                 break;
>                         case "l":
>                                 // output fourth format to sink
>                                 break;
>                         case "s":
>                                 // output boring default string format
>                                 break;
>                         default:
>                                 throw new Exception(
>                                         "Unknown format specifier: %" ~
>                                         fmt.spec);
>                         }
>                 }
>         }
>
> Of course, FormatSpec contains much more than just the letter that defines the specifier. It also contains field width, precision, etc.. So you can implement your own handling for all of these parameters that are specifiable in a writefln format string.
>
> Here's a somewhat silly example to show the flexibility conferred:
>
>         import std.format;
>         import std.stdio;
>
>         struct BoxPrinter {
>                 void toString(scope void delegate(const(char)[]) sink,
>                         FormatSpec!char fmt) const
>                 {
>                         if (fmt.spec == 'b') {
>                                 // Draws a starry rectangle
>                                 foreach (j; 0..fmt.precision) {
>                                         foreach (i; 0..fmt.width) {
>                                                 sink("*");
>                                         }
>                                         sink("\n");
>                                 }
>                         } else {
>                                 // Boring old traditional string representation
>                                 sink("BoxPrinter");
>                         }
>                 }
>         }
>
>         void main() {
>                 BoxPrinter box;
>                 writefln("%s", box);
>                 writefln("%6.5b", box);
>                 writefln("%3.2b", box);
>                 writefln("%7.4b", box);
>         }
>
> Here's the output:
>
>         BoxPrinter
>         ******
>         ******
>         ******
>         ******
>         ******
>
>         ***
>         ***
>
>         *******
>         *******
>         *******
>         *******
>
>
> As you can see, the width and precision parts of the custom %b specifier has been reinterpreted into the dimensions of the box that will be printed.  And when you specify %s, a traditional innocent-looking string is printed instead. In effect, we have implemented our own custom format specifier.
>
> D rocks!!
>
> Oh, and did I mention that D rocks?
>
>
> T
>
> --
> Do not reason with the unreasonable; you lose by definition.
January 17, 2013
On 1/16/13, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
> 		// Wait -- what? What on earth are %i, %j, %k, and %l?!
> 		writeln("%i", s);	// Hmm, prints "i"!
> 		writeln("%j", s);	// Hmm, prints "j"!

I wish we could write string specifiers rather than char specifiers. You can invent your own char specifiers but they're still going to be hard to understand at the call site. I would prefer:

> 		writefln("{fullname}", s);
> 		writefln("{type}", s);

And have:

void toString(scope void delegate(const(char)[]) sink,
                  FormatSpec!string fmt) const
{
        switch(fmt.spec)
        {
            case "fullname"
                break;
            case "type":
                break;
        }
}

We could invent this in our own library but it won't be usable with existing format and write functions.
January 17, 2013
Posted to reddit: http://www.reddit.com/r/programming/comments/16riz9/creating_custom_print_format_specifiers_in_d/

Wanted to credit H.S. Teoh but wasn't sure how...

On 17 January 2013 10:12, Andrej Mitrovic <andrej.mitrovich@gmail.com> wrote:
> On 1/16/13, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
>>               // Wait -- what? What on earth are %i, %j, %k, and %l?!
>>               writeln("%i", s);       // Hmm, prints "i"!
>>               writeln("%j", s);       // Hmm, prints "j"!
>
> I wish we could write string specifiers rather than char specifiers. You can invent your own char specifiers but they're still going to be hard to understand at the call site. I would prefer:
>
>>               writefln("{fullname}", s);
>>               writefln("{type}", s);
>
> And have:
>
> void toString(scope void delegate(const(char)[]) sink,
>                   FormatSpec!string fmt) const
> {
>         switch(fmt.spec)
>         {
>             case "fullname"
>                 break;
>             case "type":
>                 break;
>         }
> }
>
> We could invent this in our own library but it won't be usable with existing format and write functions.



-- 
   -=Miles Stoudenmire=-
   miles.stoudenmire@gmail.com
   estouden@uci.edu
   http://itensor.org/miles/
January 17, 2013
On Thu, Jan 17, 2013 at 09:33:54AM -0200, Leandro Motta Barros wrote:
> *Great!* Thanks for posting this!
> 
> I think it will be much easier to get a critical mass of enthusiastic D developers if we have some sort of "Effective D" (like Scott Meyer's "Effective C++") book/conf presentations/wiki page/whatever.
[...]

There's a wiki page that covers topics related to this:

	http://wiki.dlang.org/Tutorials

Esp. the Common Idioms section.


T

-- 
Lottery: tax on the stupid. -- Slashdotter
January 17, 2013
On Thu, Jan 17, 2013 at 07:12:56PM +0100, Andrej Mitrovic wrote:
> On 1/16/13, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
> > 		// Wait -- what? What on earth are %i, %j, %k, and %l?!
> > 		writeln("%i", s);	// Hmm, prints "i"!
> > 		writeln("%j", s);	// Hmm, prints "j"!
> 
> I wish we could write string specifiers rather than char specifiers. You can invent your own char specifiers but they're still going to be hard to understand at the call site. I would prefer:
> 
> > 		writefln("{fullname}", s);
> > 		writefln("{type}", s);
> 
> And have:
> 
> void toString(scope void delegate(const(char)[]) sink,
>                   FormatSpec!string fmt) const
> {
>         switch(fmt.spec)
>         {
>             case "fullname"
>                 break;
>             case "type":
>                 break;
>         }
> }
> 
> We could invent this in our own library but it won't be usable with existing format and write functions.

What would *really* be nice is if we extended the current format specifiers to be able to deal with named parameters and custom modifiers, for example:

	struct Actor {
		string name;
		void toString(scope void delegate(const(char)[]) sink,
				FormatSpec!string fmt) const
		{
			switch (fmt.spec)
			{
				case "thename":
					sink("the ");
					sink(name);
					break;
				case "Thename":
					sink("The ");
					sink(name);
					break;
				case "name":
					sink(name);
					break;
				case "Name":
					assert(name.length > 0);
					sink(toUpper(name[0]));
					if (name.length > 1)
						sink(name[1..$]);
					break;
				default:
					...
			}
		}
	}

	auto x = Actor("man");
	auto y = Actor("dog");
	auto z = Actor("cats");

	writeln("%{subj:Thename} is feeding %{obj:thename}.", [
		"subj": x,
		"obj": y
	]);
	// Outputs: "The man is feeding the dog."

	writeln("%{subj:Name} don't like %{obj:thename}.", [
		"subj": z,
		"obj": x
	]);
	// Outputs: "Cats don't like the man."

	writeln("But %{subj:thename} likes %{obj:name}.", [
		"subj": y,
		"obj": z
	]);
	// Outputs: "But the dog likes cats."


T

-- 
A mathematician is a device for turning coffee into theorems. -- P. Erdos
January 17, 2013
On 1/16/13, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
> I'm guessing that most D users don't realize extent of the flexibility of std.format

There's a problem:

module test;
import std.stdio;

class C1 { }
class C2 : C1
{
    void toString(scope void delegate(const(char)[]) put)
    {
        put("Subclass");
    }
}

void main()
{
    C1 c = new C2();
    writeln(c);  // writes 'test.C2'
}

I think this might be a problem of having the Object root class implement toString.
January 17, 2013
On Thu, Jan 17, 2013 at 08:20:49PM +0100, Andrej Mitrovic wrote:
> On 1/16/13, H. S. Teoh <hsteoh@quickfur.ath.cx> wrote:
> > I'm guessing that most D users don't realize extent of the flexibility of std.format
> 
> There's a problem:
> 
> module test;
> import std.stdio;
> 
> class C1 { }
> class C2 : C1
> {
>     void toString(scope void delegate(const(char)[]) put)
>     {
>         put("Subclass");
>     }
[...]

Hmm. The fact that this method didn't give a compiler warning/error about missing an "override" keyword indicates the root of the problem: you need to define toString in C1 and override it in C2, otherwise the toString method will not be visible via a C1 reference. (I've run into this before.)

Ideally the toString variants should be put in Object, but I'm not sure if it will work well (in particular, if you overload toString with template arguments on character type, it will not be virtual so there may be some use cases that won't work).


T

-- 
"Computer Science is no more about computers than astronomy is about telescopes." -- E.W. Dijkstra
January 18, 2013
On Thu, 2013-01-17 at 11:18 -0800, H. S. Teoh wrote:
[…]
> What would *really* be nice is if we extended the current format specifiers to be able to deal with named parameters and custom modifiers, for example:
[…]

Isn't there a way of handling this with closure capture. Python, Groovy, etc. handle all this with dynamic lookup. Scala, etc. handle it with value capture.

(I haven't worked this through properly, it is just a quick reaction to reading the code. I may therefore have missed the point.)

-- 
Russel. ============================================================================= Dr Russel Winder      t: +44 20 7585 2200   voip: sip:russel.winder@ekiga.net 41 Buckmaster Road    m: +44 7770 465 077   xmpp: russel@winder.org.uk London SW11 1EN, UK   w: www.russel.org.uk  skype: russel_winder