October 07, 2012
On Sunday, 7 October 2012 at 09:27:54 UTC, Dmitry Olshansky wrote:
> Ehem.. I've been pushing for DIP9 a lot of time. But then I find out that it is already here (and been for some time).
>
> Like I said use a special overload of toString that is exactly writeTo.
>
> Just define method with this signature:
> void toString(scope void delegate(const(char)[]) sink)
>
> And bingo! It's used in all of formatting stuff like write(f)(ln), format, formattedWrite etc.
>
> e.g. before posting I played with this:
>
> import std.stdio, std.format;
>
> struct A{
> 	int k;
> 	void toString(scope void delegate(const(char)[]) sink)
> 	{
> 		formattedWrite(sink, "[%d]", k);
> 	}
> }
>
> void main(){
> 	A a = A(90);
> 	writeln(a);
> }

Nice! I didn't know that. I thought this toString signature was only used by BigInt, and not taken advantage of by other routines.

To implement it fully we would still want to change toString for classes, and probably do something like providing a UFCS function in object.toString or some other relevant location for convenience and backwards compatibility.

October 07, 2012
On Sunday, October 07, 2012 11:37:30 Jakob Ovrum wrote:
> To implement it fully we would still want to change toString for classes, and probably do something like providing a UFCS function in object.toString or some other relevant location for convenience and backwards compatibility.

Well, considering that we're looking at removing toString, toHash, opCmp, and opEquals from Object entirely, that probably won't be necessary. But that particular plan hasn't gotten past the initial discussions AFAIK, so who knows exactly how it's going to go or when it's going to happen.

- Jonathan M Davis
October 07, 2012
On Sunday, 7 October 2012 at 09:58:54 UTC, Jonathan M Davis wrote:
> Well, considering that we're looking at removing toString, toHash, opCmp, and
> opEquals from Object entirely, that probably won't be necessary. But that
> particular plan hasn't gotten past the initial discussions AFAIK, so who knows
> exactly how it's going to go or when it's going to happen.
>
> - Jonathan M Davis

This is definitely something I would like to happen.

October 07, 2012
On Sun, Oct 7, 2012 at 11:15 AM, Dmitry Olshansky <dmitry.olsh@gmail.com> wrote:
> import std.stdio, std.format;
>
> struct A{
>         int k;
>
>         void toString(scope void delegate(const(char)[]) sink)
>         {
>                 formattedWrite(sink, "[%d]", k);
>         }
> }

I see, thanks. And if the string in formattedWrite is a complex beast, created through lots of if's and while's, I use Appender in its place?
October 07, 2012
On Sunday, 7 October 2012 at 15:36:52 UTC, Philippe Sigaud wrote:
> I see, thanks. And if the string in formattedWrite is a complex beast,
> created through lots of if's and while's, I use Appender in its place?

Ideally, you just write to the sink directly, piece by piece – but if for some reason you _need_ to have one big format string, then yes, prefer using Appender over string concatenation.

David
October 07, 2012
On 10/07/12 17:24, Philippe Sigaud wrote:
> On Sun, Oct 7, 2012 at 11:15 AM, Dmitry Olshansky <dmitry.olsh@gmail.com> wrote:
>> import std.stdio, std.format;
>>
>> struct A{
>>         int k;
>>
>>         void toString(scope void delegate(const(char)[]) sink)
>>         {
>>                 formattedWrite(sink, "[%d]", k);
>>         }
>> }
> 
> I see, thanks. And if the string in formattedWrite is a complex beast, created through lots of if's and while's, I use Appender in its place?

You can write to the sink directly, eg

   struct EnumBits(alias E) {
      E e;
      alias e this;

      void toString(DG, FT)(scope DG sink, in FT fmt) const {
         import std.format;
         sink(E.stringof ~ "{");
         int wrote;
         foreach (member; __traits(allMembers, E))
            if (__traits(getMember, E, member) & e){
               if (wrote++) sink("|");
               sink(member);
            }
         if (!wrote)
            sink("/*No " ~ E.stringof ~ "*/");
         sink("}");
      }
   }

It gets a bit more tricky when you for example need to print the contents of containers w/o copying the data; but you can then just call their toString method directly:

   struct PTR_LIST(T, bool TAGGED=0) {
      c_int nr;
      PTR_LIST *prev;
      PTR_LIST *next;
      // ...
      void toString(DG, FT)(scope DG sink, in FT fmt) const {
         import std.format;
         sink(typeof(cast()this).stringof ~ "{[");
         formatValue(sink, nr, fmt); sink("]");
         foreach (p; this) {
            p.toString(sink, fmt);
            sink(", ");
         }
         sink("}");
      }

      static struct Anchor {
         PTR_LIST* list;
         // ...
         void toString(DG, FT)(scope DG sink, in FT fmt) const {
            import std.format;
            // Cannot 'formatValue(sink, *list, fmt);' as the struct is passed
            // by value and that messes up the list (head pointer doesn't match).
            list.toString(sink, fmt);
         }
      }
   }

   /* Obviously, i wasn't trying to avoid allocations, hence the presence of '~'s. */

artur
October 07, 2012
Artur and David:
> You can write to the sink directly, eg
(snip nice example)

Ah, OK. I use the sink throughout.

Thanks.
October 08, 2012
On Sat, Oct 6, 2012 at 1:32 PM, Dmitry Olshansky <dmitry.olsh@gmail.com> wrote:
>
> Cool, does it work with BigInt?
>

No it doesn't work with BigInt, but I did look into it today briefly. There are issues with BigInt that I'm not sure what to do about:

1.  To convert a BigInt to floating-point one needs to convert it to the built-in integer types first.  If you go beyond the limits of the built-in types (long), then what's the point?  You might as well be using int or long.

2.  The functions in std.math don't support BigInt, and most likely never will.  Even if they did, you would still need multi-precision floating point to store the irrational numbers.  If you decided to use the built-in types, then again what's the point?  You might as well go with int or long.

> Also I think there is better version of toString that has signature:
> void toString(scope void delegate(const(char)[]) sink)
>
> this toString just use functions like formattedWrite to write chars to sink. It thus totally avoids overhead of allocating strings.
>
> Your current code may have some bad impact on performance:
> return "(" ~ to!string(res.numerator) ~ "/" ~ to!string(res.denominator) ~
> ")";
>
> Allocates 4 times. ~ operator is convenient shortcut to get job done but it's unsuitable for building strings and formatted output.
>
>

Thanks, I'll fix it.
October 08, 2012
On 2012-44-08 06:10, Arlen <arlen.ng@gmx.com> wrote:

> On Sat, Oct 6, 2012 at 1:32 PM, Dmitry Olshansky <dmitry.olsh@gmail.com> wrote:
>>
>> Cool, does it work with BigInt?
>>
>
> No it doesn't work with BigInt, but I did look into it today briefly.
> There are issues with BigInt that I'm not sure what to do about:
>
> 1.  To convert a BigInt to floating-point one needs to convert it to
> the built-in integer types first.  If you go beyond the limits of the
> built-in types (long), then what's the point?  You might as well be
> using int or long.

I thought (part of) the point of Rational was to use it when it would
be more fitting than floating-point. If it's only there to be converted
to floating-point, I don't know what it's there for.

As for a workaround, right-shift num and den to reasonable values
(which fit in a long), divide to get a float, and multiply by
2.0^^(log2(num)-log2(den)). Oughta work.


> 2.  The functions in std.math don't support BigInt, and most likely
> never will.  Even if they did, you would still need multi-precision
> floating point to store the irrational numbers.  If you decided to use
> the built-in types, then again what's the point?  You might as well go
> with int or long.

The only function you use from std.math is abs, right? That should be
fairly easy to implement for BigInt.

It'd mean you'd have to specialize a bit for BigInt, though (just
create your own abs function that calls the correct one).

Another option: lobby for BigInt support in std.math. :p


Also, irrational numbers? How'd you get those in a *Rational* library? :p


-- 
Simen
October 08, 2012
On Mon, Oct 08, 2012 at 09:08:57AM +0200, Simen Kjaeraas wrote:
> On 2012-44-08 06:10, Arlen <arlen.ng@gmx.com> wrote:
> 
> >On Sat, Oct 6, 2012 at 1:32 PM, Dmitry Olshansky <dmitry.olsh@gmail.com> wrote:
> >>
> >>Cool, does it work with BigInt?
> >>
> >
> >No it doesn't work with BigInt, but I did look into it today briefly. There are issues with BigInt that I'm not sure what to do about:
> >
> >1.  To convert a BigInt to floating-point one needs to convert it to the built-in integer types first.  If you go beyond the limits of the built-in types (long), then what's the point?  You might as well be using int or long.
> 
> I thought (part of) the point of Rational was to use it when it would be more fitting than floating-point. If it's only there to be converted to floating-point, I don't know what it's there for.

I'd like to chime in to state that I'd love to have Rational support BigInt. If necessary, use an arbitrary-width floating point format for floating point conversions (BigFloat?).


[...]
> It'd mean you'd have to specialize a bit for BigInt, though (just create your own abs function that calls the correct one).
> 
> Another option: lobby for BigInt support in std.math. :p

+1.


> Also, irrational numbers? How'd you get those in a *Rational* library? :p
[...]

Ironically enough, if Rational supports BigInt, then it makes it possible to implement a representation of quadratic irrationals in a straightforward way (by storing them in the form a+b*sqrt(r) where a and b are Rationals and r is a square-free Rational). The reason BigInt is necessary is that while these things are closed under field operations, any non-trivial work with them tends to overflow built-in integer types very quickly.

BigInt becomes even more necessary if you want to represent quadratic irrationals of *two* distinct square roots, which can be represented as a+b*sqrt(r)+c*sqrt(s)+d*sqrt(rs), where a,b,c,d are Rationals and r,s are square-free Rationals. Believe it or not, these little monsters are also closed under field operations. But they also overflow built-in integers *very* fast. With BigInt support, however, you can do *exact* arithmetic with these irrational numbers, something very useful for applications in computational geometry, where the lack of exact arithmetic makes many problems intractible or prone to wrong results.


T

-- 
I'm still trying to find a pun for "punishment"...