www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - phobos / tango / ares

reply Henning Hasemann <hhasemann web.de> writes:
I'm having little difficulties understanding the relationship of those 3.
Are they all alternatives to each other?

What I know is that phobos is the standard library that provides writeln etc,
also tango seems to have IO capabilities so could one compile D programs
with tango instead of phobos?
When to use which of these?

Henning
Feb 07 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Henning Hasemann wrote:
 I'm having little difficulties understanding the relationship of those 3.
 Are they all alternatives to each other?
 
 What I know is that phobos is the standard library that provides writeln etc,
 also tango seems to have IO capabilities so could one compile D programs
 with tango instead of phobos?
 When to use which of these?

Phobos is the "default" standard library, provided with DMD (GDC has a variation of it as well, gphobos). Tango is a recently released library based partly on Ares and Mango with influences from some other projects, IIRC. Which one to use is hard to say at this point. I've been trying out Tango since its release and I like it but I sometimes miss some parts of Phobos. Whether this is because Phobos is just more familiar to me or actually better is hard to say... If I switch back to Phobos I'll probably miss some parts of Tango ;).
Feb 07 2007
parent reply Lars Ivar Igesund <larsivar igesund.net> writes:
Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi Dancing the Tango
Feb 07 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Lars Ivar Igesund wrote:
 Frits van Bommel wrote:
 
 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

Well, for one thing: the text formatting routines don't seem to support binary output. At least not with "{0:b}", which seemed to me to be the most logical format string for it, since "{0:x}" formats to hexadecimal. Phobos' writefln() does, and the first program I tried to port happened to use it... By the way, is there some documentation for what's allowed in format strings that I missed? If so, it IMHO needs to be linked more thoroughly from the documentation for things that accept them...
Feb 07 2007
parent Lars Ivar Igesund <larsivar igesund.net> writes:
Frits van Bommel wrote:

 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:
 
 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

Well, for one thing: the text formatting routines don't seem to support binary output. At least not with "{0:b}", which seemed to me to be the most logical format string for it, since "{0:x}" formats to hexadecimal. Phobos' writefln() does, and the first program I tried to port happened to use it... By the way, is there some documentation for what's allowed in format strings that I missed? If so, it IMHO needs to be linked more thoroughly from the documentation for things that accept them...

Yes, documentation is not complete on all aspects, hopefully this particular aspect will be improved by the next release. In theory though, what works in C# using .Net should either work in Tango, or be noted as having missing implementation or being a bug. Since Tango is neither .Net nor is D C#, there may very well be exceptions to this rule. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi Dancing the Tango
Feb 08 2007
prev sibling next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Lars Ivar Igesund wrote:
 Frits van Bommel wrote:
 
 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I'm having trouble understanding first why Tango had to be made mutually exclusive to Phobos (is it just changes to object.d? were those really necessary?) and if object.d differences are the only reason, then I still don't really get why most Phobos code can't still be imported as is or with trivial changes. For example, phobos' std.path doesn't seem to have any direct dependencies on the gc API or on a particular version of object.d. What's the issue? --bb
Feb 07 2007
parent reply Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I'm having trouble understanding first why Tango had to be made mutually exclusive to Phobos (is it just changes to object.d? were those really necessary?)

The Tango runtime code contains quite a few differences compared to Phobos, but the bulk of these are hidden from the user. Some of the more visible differences are that Error has been dropped, Exception reworked, Object.toString() changed to Object.toUtf8(), and the Thread object has a slightly different interface. During development, both in Ares/Mango and now in Tango, no effort was made to either deliberately mimic or to differ from Phobos. Rather, a design was chosen that simply made the most sense. That said, a great deal of effort has been made to avoid changing anything that feels like a language feature, and it was sometimes difficult to determine where the line should be drawn. The Object.toString() issue sits pretty squarely on that line, and a great deal of discussion took place before that particular change was agreed upon.
 and if object.d differences are the only reason, then I
 still don't really get why most Phobos code can't still be imported as 
 is or with trivial changes.  For example, phobos' std.path doesn't seem 
 to have any direct dependencies on the gc API or on a particular version 
 of object.d.  What's the issue?

Most Phobos code can be imported as is or with trivial changes, it's simply a matter of taking the time to do so. Frank actually did this a while back for his own use, and the project now lives on dsource as tango.phobos. I am sure it could do with some dedicated maintainers -- developing one library is quite enough for me :-) Sean
Feb 07 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Sean Kelly wrote:
 Bill Baxter wrote:
 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some 
 parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I'm having trouble understanding first why Tango had to be made mutually exclusive to Phobos (is it just changes to object.d? were those really necessary?)

The Tango runtime code contains quite a few differences compared to Phobos, but the bulk of these are hidden from the user. Some of the more visible differences are that Error has been dropped, Exception reworked, Object.toString() changed to Object.toUtf8(), and the Thread object has a slightly different interface. During development, both in Ares/Mango and now in Tango, no effort was made to either deliberately mimic or to differ from Phobos. Rather, a design was chosen that simply made the most sense. That said, a great deal of effort has been made to avoid changing anything that feels like a language feature, and it was sometimes difficult to determine where the line should be drawn. The Object.toString() issue sits pretty squarely on that line, and a great deal of discussion took place before that particular change was agreed upon. > and if object.d differences are the only reason, then I
 still don't really get why most Phobos code can't still be imported as 
 is or with trivial changes.  For example, phobos' std.path doesn't 
 seem to have any direct dependencies on the gc API or on a particular 
 version of object.d.  What's the issue?

Most Phobos code can be imported as is or with trivial changes, it's simply a matter of taking the time to do so. Frank actually did this a while back for his own use, and the project now lives on dsource as tango.phobos. I am sure it could do with some dedicated maintainers -- developing one library is quite enough for me :-)

Ok. Thanks for the explanation. There were some comments before about "porting phobos to tango" that just made it sound like a bigger job that it apparently is in fact. As many people have noticed, phobos doesn't really change very quickly <g> so I suspect it wouldn't be too much work to maintain compatibility once achieved. Patches seem like a good way to go. If you have a patch against phobos then applying it to new versions of phobos would likely just work, since the changes to phobos are few. --bb
Feb 07 2007
next sibling parent Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 
 As many people have noticed, phobos doesn't really change very quickly 
 <g> so I suspect it wouldn't be too much work to maintain compatibility 
 once achieved.  Patches seem like a good way to go.  If you have a patch 
 against phobos then applying it to new versions of phobos would likely 
 just work, since the changes to phobos are few.

Yup. The average DMD release recently has touched maybe 2 or 3 files in std. Not a huge effort to manage for someone with a diff tool. Sean
Feb 07 2007
prev sibling parent reply Lars Ivar Igesund <larsivar igesund.net> writes:
Bill Baxter wrote:

 Sean Kelly wrote:
 Bill Baxter wrote:
 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some
 parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I'm having trouble understanding first why Tango had to be made mutually exclusive to Phobos (is it just changes to object.d? were those really necessary?)

The Tango runtime code contains quite a few differences compared to Phobos, but the bulk of these are hidden from the user. Some of the more visible differences are that Error has been dropped, Exception reworked, Object.toString() changed to Object.toUtf8(), and the Thread object has a slightly different interface. During development, both in Ares/Mango and now in Tango, no effort was made to either deliberately mimic or to differ from Phobos. Rather, a design was chosen that simply made the most sense. That said, a great deal of effort has been made to avoid changing anything that feels like a language feature, and it was sometimes difficult to determine where the line should be drawn. The Object.toString() issue sits pretty squarely on that line, and a great deal of discussion took place before that particular change was agreed upon. > and if object.d differences are the only reason, then I
 still don't really get why most Phobos code can't still be imported as
 is or with trivial changes.  For example, phobos' std.path doesn't
 seem to have any direct dependencies on the gc API or on a particular
 version of object.d.  What's the issue?

Most Phobos code can be imported as is or with trivial changes, it's simply a matter of taking the time to do so. Frank actually did this a while back for his own use, and the project now lives on dsource as tango.phobos. I am sure it could do with some dedicated maintainers -- developing one library is quite enough for me :-)

Ok. Thanks for the explanation. There were some comments before about "porting phobos to tango" that just made it sound like a bigger job that it apparently is in fact.

I believe someone mentioned implementing the phobos API using Tango.
 
 As many people have noticed, phobos doesn't really change very quickly
 <g> so I suspect it wouldn't be too much work to maintain compatibility
 once achieved.  Patches seem like a good way to go.  If you have a patch
 against phobos then applying it to new versions of phobos would likely
 just work, since the changes to phobos are few.
 
 --bb

If someone wants to help mantaining the tango.phobos project, they are welcome to do so. The project both has a Trac environment and a forum in phpBB. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi Dancing the Tango
Feb 08 2007
parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Lars Ivar Igesund wrote:
 
 If someone wants to help mantaining the tango.phobos project, they are
 welcome to do so. The project both has a Trac environment and a forum in
 phpBB.

How can I find the trac environment? It is not listed under dsource/projects.
Feb 08 2007
parent Lars Ivar Igesund <larsivar igesund.net> writes:
Lutger wrote:

 Lars Ivar Igesund wrote:
 
 If someone wants to help mantaining the tango.phobos project, they are
 welcome to do so. The project both has a Trac environment and a forum in
 phpBB.

How can I find the trac environment? It is not listed under dsource/projects.

The adress is http://www.dsource.org/projects/tango.phobos I believe all project tracs can be found be exchanging the name part of the link. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi Dancing the Tango
Feb 08 2007
prev sibling next sibling parent reply "Ralf Schneider" <ralfs72_at_ gmx.net> writes:
"Lars Ivar Igesund" <larsivar igesund.net> schrieb im Newsbeitrag 
news:eqdcp0$r0s$1 digitaldaemon.com...
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I prefer the string function of phobos over the one provided by Tango. A D implementation of the Python string functions (with the same names) I would find great. - Ralf
Feb 08 2007
next sibling parent Sean Kelly <sean f4.ca> writes:
Ralf Schneider wrote:
 "Lars Ivar Igesund" <larsivar igesund.net> schrieb im Newsbeitrag 
 news:eqdcp0$r0s$1 digitaldaemon.com...
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

part of the feedback we would like.

I prefer the string function of phobos over the one provided by Tango. A D implementation of the Python string functions (with the same names) I would find great.

Have you looked at tango.text.Util? Naming aside (some names are being changed IIRC), is there functionality you feel is mising, etc? Sean
Feb 08 2007
prev sibling parent Lars Ivar Igesund <larsivar igesund.net> writes:
Ralf Schneider wrote:

 
 "Lars Ivar Igesund" <larsivar igesund.net> schrieb im Newsbeitrag
 news:eqdcp0$r0s$1 digitaldaemon.com...
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I prefer the string function of phobos over the one provided by Tango. A D implementation of the Python string functions (with the same names) I would find great. - Ralf

Whereas there is much good in the python api, I personally don't think mimicking it in D for the sake of it is the correct approach. Inspiration is another matter. Tango have many string functions, and they may be added to if needs be, but we will do our best to make sure that they are efficient and consistent with the rest of the naming. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi Dancing the Tango
Feb 09 2007
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
Lars Ivar Igesund wrote:
 Frits van Bommel wrote:
 
 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

Okay -- I'm really sorry if any of this seems to have a negative tone. I hesitate to write this since I have a lot of respect for the Tango design in general, but there are a couple of friction points I've noticed. 1. writefln / format replacements Concerning standard output and string formatting, in phobos I can do these operations: writefln("%s %s %s", a, b, c); format("%s %s %s", a, b, c); How do I do these in Tango? The change to "{0} {1}" stuff is fine with me, in fact I like it, but this syntax: Stdout.formatln("{0} {1} {2}", a, b, c); Format!(char).convert("{0} {1} {2}", a, b, c); Is awkward. And these statements are used *all the time*. In a recent toy project I wrote, I used Stdout 15 times, compared to using "foreach" only 8 times. I also use the "format to string" idiom a lot (oddly enough, not in that project), and it's even more awkward. That's why I think phobos really did the "Right Thing" by keeping those down to one token. Second, the fact that the second one does exactly what the first does but you need to build a template, etc, is annoying. I kept asking myself if I was doing the right thing because it seemed like I was using too much syntax for this kind of operation (I'm still not sure it's the best way to go -- is it?) I know about Cout as a replacement for the first one, but as far as I can tell it doesn't take parameters, and usually I need some. When people ask "why D", I tell them that simpler syntax, better defaults and better garbage collection, each gain us a 50 % reduction in code, and when all three apply to a problem, D can each C++'s lunch. Let's not throw away the simpler syntax. (I'm not talking about architecture changes, just wrappers with standardized short names that can become familiar to all D users.) 2. toString and toUtf8 (collisions) The change of the terminology is actually okay with me. But phobos has a way of using toString as both a method and a top-level function name, all over the place. This gets really clumsy because you can never use the top level function names when writing a class unless you fully qualify them. For example, std.cpuid.toString(), always has to be fully qualified when called from a class, and seems nondescriptive anyway. All the std.conv.toString() functions are nice but it's easy to accidentally call the in-class toString() by accident. For the utf8 <--> utf16 and similar, it's frustrating to have to do this: dchar[] x32 = ...; char[] x8 = tango.text.convert.Utf.toUtf8(x32); But you have to fully qualify if you are writing code in any class or struct. If these were given another name, like makeUtf8, then these collisions would not happen. Actually, if it wasn't already out there, I would want to go through all of phobos and remove all the common collisions. They are much less trouble in an "import" system than in an "include" system, but every time there is a collision it requires an additional "edit-compile" cycle, and/or a fully qualified name. And if you forget to import all the right modules, its can impact the correctness angle, because you pick up someone else's "toString" from who knows where. I'm just saying, ideally tango should not be duplicating this with toUtf8 etc. Kevin
Feb 08 2007
next sibling parent Johan Granberg <lijat.meREM OVE.gmail.com> writes:
Kevin Bealer wrote:

 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:
 
 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

Okay -- I'm really sorry if any of this seems to have a negative tone. I hesitate to write this since I have a lot of respect for the Tango design in general, but there are a couple of friction points I've noticed. 1. writefln / format replacements Concerning standard output and string formatting, in phobos I can do these operations: writefln("%s %s %s", a, b, c); format("%s %s %s", a, b, c); How do I do these in Tango? The change to "{0} {1}" stuff is fine with me, in fact I like it, but this syntax: Stdout.formatln("{0} {1} {2}", a, b, c); Format!(char).convert("{0} {1} {2}", a, b, c); Is awkward. And these statements are used *all the time*. In a recent toy project I wrote, I used Stdout 15 times, compared to using "foreach" only 8 times. I also use the "format to string" idiom a lot (oddly enough, not in that project), and it's even more awkward. That's why I think phobos really did the "Right Thing" by keeping those down to one token. Second, the fact that the second one does exactly what the first does but you need to build a template, etc, is annoying. I kept asking myself if I was doing the right thing because it seemed like I was using too much syntax for this kind of operation (I'm still not sure it's the best way to go -- is it?) I know about Cout as a replacement for the first one, but as far as I can tell it doesn't take parameters, and usually I need some. When people ask "why D", I tell them that simpler syntax, better defaults and better garbage collection, each gain us a 50 % reduction in code, and when all three apply to a problem, D can each C++'s lunch. Let's not throw away the simpler syntax. (I'm not talking about architecture changes, just wrappers with standardized short names that can become familiar to all D users.) 2. toString and toUtf8 (collisions) The change of the terminology is actually okay with me. But phobos has a way of using toString as both a method and a top-level function name, all over the place. This gets really clumsy because you can never use the top level function names when writing a class unless you fully qualify them. For example, std.cpuid.toString(), always has to be fully qualified when called from a class, and seems nondescriptive anyway. All the std.conv.toString() functions are nice but it's easy to accidentally call the in-class toString() by accident. For the utf8 <--> utf16 and similar, it's frustrating to have to do this: dchar[] x32 = ...; char[] x8 = tango.text.convert.Utf.toUtf8(x32); But you have to fully qualify if you are writing code in any class or struct. If these were given another name, like makeUtf8, then these collisions would not happen. Actually, if it wasn't already out there, I would want to go through all of phobos and remove all the common collisions. They are much less trouble in an "import" system than in an "include" system, but every time there is a collision it requires an additional "edit-compile" cycle, and/or a fully qualified name. And if you forget to import all the right modules, its can impact the correctness angle, because you pick up someone else's "toString" from who knows where. I'm just saying, ideally tango should not be duplicating this with toUtf8 etc. Kevin

I agree with all that is written above but had not had time to write it up yet.
Feb 08 2007
prev sibling next sibling parent reply Max Samukha <samukha voliacable.com> writes:
On Fri, 09 Feb 2007 01:40:00 -0500, Kevin Bealer
<kevinbealer gmail.com> wrote:

Okay -- I'm really sorry if any of this seems to have a negative tone. 
I hesitate to write this since I have a lot of respect for the Tango 
design in general, but there are a couple of friction points I've noticed.

1. writefln / format replacements

Concerning standard output and string formatting, in phobos I can do 
these operations:

   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);

How do I do these in Tango?  The change to "{0} {1}" stuff is fine with 
me, in fact I like it, but this syntax:

   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);

Is awkward.  And these statements are used *all the time*.  In a recent 
toy project I wrote, I used Stdout 15 times, compared to using "foreach" 
only 8 times.  I also use the "format to string" idiom a lot (oddly 
enough, not in that project), and it's even more awkward.

That's why I think phobos really did the "Right Thing" by keeping those 
down to one token.  Second, the fact that the second one does exactly 
what the first does but you need to build a template, etc, is annoying. 
  I kept asking myself if I was doing the right thing because it seemed 
like I was using too much syntax for this kind of operation (I'm still 
not sure it's the best way to go -- is it?)

I know about Cout as a replacement for the first one, but as far as I 
can tell it doesn't take parameters, and usually I need some.

When people ask "why D", I tell them that simpler syntax, better 
defaults and better garbage collection, each gain us a 50 % reduction in 
code, and when all three apply to a problem, D can each C++'s lunch. 
Let's not throw away the simpler syntax.

(I'm not talking about architecture changes, just wrappers with 
standardized short names that can become familiar to all D users.)


2. toString and toUtf8 (collisions)

The change of the terminology is actually okay with me.

But phobos has a way of using toString as both a method and a top-level 
function name, all over the place.  This gets really clumsy because you 
can never use the top level function names when writing a class unless 
you fully qualify them.

For example, std.cpuid.toString(), always has to be fully qualified when 
called from a class, and seems nondescriptive anyway.  All the 
std.conv.toString() functions are nice but it's easy to accidentally 
call the in-class toString() by accident.

For the utf8 <--> utf16 and similar, it's frustrating to have to do this:

dchar[] x32 = ...;
char[] x8 = tango.text.convert.Utf.toUtf8(x32);

But you have to fully qualify if you are writing code in any class or 
struct.  If these were given another name, like makeUtf8, then these 
collisions would not happen.

Actually, if it wasn't already out there, I would want to go through all 
of phobos and remove all the common collisions.  They are much less 
trouble in an "import" system than in an "include" system, but every 
time there is a collision it requires an additional "edit-compile" 
cycle, and/or a fully qualified name.

And if you forget to import all the right modules, its can impact the 
correctness angle, because you pick up someone else's "toString" from 
who knows where.

I'm just saying, ideally tango should not be duplicating this with 
toUtf8 etc.

Kevin

If you really want the 'writefln' and 'format' with Tango, you could do the following: import tango.io.Stdout; import tango.text.convert.Format; Format!(char) format; typeof(&Stdout.formatln) writefln; static this() { writefln = &Stdout.formatln; format = new Format!(char); } void main() { auto str = format("Test {0}: {1}", 1, "Passed"); writefln(str); writefln("Test {0}: {1}", 2, "Passed"); }
Feb 09 2007
parent Kevin Bealer <kevinbealer gmail.com> writes:
Max Samukha wrote:
 On Fri, 09 Feb 2007 01:40:00 -0500, Kevin Bealer
 <kevinbealer gmail.com> wrote:
 
 Okay -- I'm really sorry if any of this seems to have a negative tone. 
 I hesitate to write this since I have a lot of respect for the Tango 
 design in general, but there are a couple of friction points I've noticed.

 1. writefln / format replacements

 Concerning standard output and string formatting, in phobos I can do 
 these operations:

   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);

 How do I do these in Tango?  The change to "{0} {1}" stuff is fine with 
 me, in fact I like it, but this syntax:

   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);

 Is awkward.  And these statements are used *all the time*.  In a recent 
 toy project I wrote, I used Stdout 15 times, compared to using "foreach" 
 only 8 times.  I also use the "format to string" idiom a lot (oddly 
 enough, not in that project), and it's even more awkward.

 That's why I think phobos really did the "Right Thing" by keeping those 
 down to one token.  Second, the fact that the second one does exactly 
 what the first does but you need to build a template, etc, is annoying. 
  I kept asking myself if I was doing the right thing because it seemed 
 like I was using too much syntax for this kind of operation (I'm still 
 not sure it's the best way to go -- is it?)

 I know about Cout as a replacement for the first one, but as far as I 
 can tell it doesn't take parameters, and usually I need some.

 When people ask "why D", I tell them that simpler syntax, better 
 defaults and better garbage collection, each gain us a 50 % reduction in 
 code, and when all three apply to a problem, D can each C++'s lunch. 
 Let's not throw away the simpler syntax.

 (I'm not talking about architecture changes, just wrappers with 
 standardized short names that can become familiar to all D users.)


 2. toString and toUtf8 (collisions)

 The change of the terminology is actually okay with me.

 But phobos has a way of using toString as both a method and a top-level 
 function name, all over the place.  This gets really clumsy because you 
 can never use the top level function names when writing a class unless 
 you fully qualify them.

 For example, std.cpuid.toString(), always has to be fully qualified when 
 called from a class, and seems nondescriptive anyway.  All the 
 std.conv.toString() functions are nice but it's easy to accidentally 
 call the in-class toString() by accident.

 For the utf8 <--> utf16 and similar, it's frustrating to have to do this:

 dchar[] x32 = ...;
 char[] x8 = tango.text.convert.Utf.toUtf8(x32);

 But you have to fully qualify if you are writing code in any class or 
 struct.  If these were given another name, like makeUtf8, then these 
 collisions would not happen.

 Actually, if it wasn't already out there, I would want to go through all 
 of phobos and remove all the common collisions.  They are much less 
 trouble in an "import" system than in an "include" system, but every 
 time there is a collision it requires an additional "edit-compile" 
 cycle, and/or a fully qualified name.

 And if you forget to import all the right modules, its can impact the 
 correctness angle, because you pick up someone else's "toString" from 
 who knows where.

 I'm just saying, ideally tango should not be duplicating this with 
 toUtf8 etc.

 Kevin

If you really want the 'writefln' and 'format' with Tango, you could do the following: import tango.io.Stdout; import tango.text.convert.Format; Format!(char) format; typeof(&Stdout.formatln) writefln; static this() { writefln = &Stdout.formatln; format = new Format!(char); } void main() { auto str = format("Test {0}: {1}", 1, "Passed"); writefln(str); writefln("Test {0}: {1}", 2, "Passed"); }

Right - this is good. But almost everyone will eventually do this, so What I'm also suggesting though is that this be done in the module, so that everyone who imports the module doesn't need to cut and paste or invent something like the above in their code. If it's done in the module it helps readability of all user code because you don't need to see what particular identifier is used by each coder. Sort of like in C++ where you see this all the time: Int1, Uint1, Int2, Uint2, Int4, Uint4, Int8, Uint8 // label=#bytes in another project they will use int8_t, uint8_t, ... int64_t, uint64_t // label=#bits If you use a library from ncbi, another from GTK, another from Qt, etc, you eventually have a dozen types with a three or four synonyms for each one. Kevin
Feb 10 2007
prev sibling next sibling parent reply Lars Ivar Igesund <larsivar igesund.net> writes:
Kevin Bealer wrote:

 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:
 
 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

Okay -- I'm really sorry if any of this seems to have a negative tone. I hesitate to write this since I have a lot of respect for the Tango design in general, but there are a couple of friction points I've noticed. 1. writefln / format replacements Concerning standard output and string formatting, in phobos I can do these operations: writefln("%s %s %s", a, b, c); format("%s %s %s", a, b, c); How do I do these in Tango? The change to "{0} {1}" stuff is fine with me, in fact I like it, but this syntax: Stdout.formatln("{0} {1} {2}", a, b, c); Format!(char).convert("{0} {1} {2}", a, b, c);

I can't give you an immediate solution for the "length" of the Stdout.formatln, but usually one would use tango.text.convert.Sprint for what you use the formatter for. If you only want to print values, there are non-formatting ways to do that. We may be able to give you quicker/easier help over our forum if you have followups? May make it easier for us to integrate solutions into our documentation too if needed. -- Lars Ivar Igesund blog at http://larsivi.net DSource & #D: larsivi Dancing the Tango
Feb 09 2007
parent reply Kevin Bealer <kevinbealer gmail.com> writes:
Lars Ivar Igesund wrote:
 Kevin Bealer wrote:
 
 Lars Ivar Igesund wrote:
 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

part of the feedback we would like.

I hesitate to write this since I have a lot of respect for the Tango design in general, but there are a couple of friction points I've noticed. 1. writefln / format replacements Concerning standard output and string formatting, in phobos I can do these operations: writefln("%s %s %s", a, b, c); format("%s %s %s", a, b, c); How do I do these in Tango? The change to "{0} {1}" stuff is fine with me, in fact I like it, but this syntax: Stdout.formatln("{0} {1} {2}", a, b, c); Format!(char).convert("{0} {1} {2}", a, b, c);

I can't give you an immediate solution for the "length" of the Stdout.formatln, but usually one would use tango.text.convert.Sprint for what you use the formatter for. If you only want to print values, there are non-formatting ways to do that. We may be able to give you quicker/easier help over our forum if you have followups? May make it easier for us to integrate solutions into our documentation too if needed.

In the future I'll post this kind of thing on the tango forum, but since this thread is already here: I looked at this but doesn't Sprint require something like: char[] foo = (new Sprint!(char))("hello {0}", 123); 1. A template instance is needed. 2. It creates a new class object (Sprint) -- you can't use a single global one in real code because according to the docs it owns the internal buffer and is thus not thread safe. 3. That object contains a new buffer which must be allocated. 4. As far as I can tell, it requires almost as much syntax as Format!(char) ( my previous example didn't include the 'new' as shown here: ) char[] one = (new Format!(char)).convert("{0}", 123); char[] foo = (new Sprint!(char))("hello {0}", 123); I haven't looked at the design of std.format but I would think it only suffers from #3 above, which is not too surprising since you would expect a string generation to require a new buffer. Sprint seems like a good solution in something like an XML formatting loop, because you can build one object and its in a function call so the thread safety issue is not critical, you can reuse it many times, but as a drop in for 'std.format' it seemed kind of awkward to me. Kevin
Feb 10 2007
parent kris <foo bar.com> writes:
Kevin Bealer wrote:
 Lars Ivar Igesund wrote:
 
 Kevin Bealer wrote:

 Lars Ivar Igesund wrote:

 Frits van Bommel wrote:

 Which one to use is hard to say at this point. I've been trying out
 Tango since its release and I like it but I sometimes miss some 
 parts of
 Phobos. Whether this is because Phobos is just more familiar to me or
 actually better is hard to say...

Note that what you miss that you feel you have in Phobos, is very much part of the feedback we would like.

I hesitate to write this since I have a lot of respect for the Tango design in general, but there are a couple of friction points I've noticed. 1. writefln / format replacements Concerning standard output and string formatting, in phobos I can do these operations: writefln("%s %s %s", a, b, c); format("%s %s %s", a, b, c); How do I do these in Tango? The change to "{0} {1}" stuff is fine with me, in fact I like it, but this syntax: Stdout.formatln("{0} {1} {2}", a, b, c); Format!(char).convert("{0} {1} {2}", a, b, c);

I can't give you an immediate solution for the "length" of the Stdout.formatln, but usually one would use tango.text.convert.Sprint for what you use the formatter for. If you only want to print values, there are non-formatting ways to do that. We may be able to give you quicker/easier help over our forum if you have followups? May make it easier for us to integrate solutions into our documentation too if needed.

In the future I'll post this kind of thing on the tango forum, but since this thread is already here: I looked at this but doesn't Sprint require something like: char[] foo = (new Sprint!(char))("hello {0}", 123); 1. A template instance is needed. 2. It creates a new class object (Sprint) -- you can't use a single global one in real code because according to the docs it owns the internal buffer and is thus not thread safe. 3. That object contains a new buffer which must be allocated. 4. As far as I can tell, it requires almost as much syntax as Format!(char) ( my previous example didn't include the 'new' as shown here: ) char[] one = (new Format!(char)).convert("{0}", 123); char[] foo = (new Sprint!(char))("hello {0}", 123);

Inside Format.d, there's a static instance called Formatter. This is what's used by Stdout, so it will remain in the library. Thus it's legit to use that instead of new Format!(char) ... e.g. your example becomes # auto one = Formatter ("{0}", 123); There's also a sprint() method on the Format instance, so you can do this where you need to avoid heap activity altogether: # char[16] tmp; # auto one = Formatter.sprint (tmp, "{0}", 123); Instantiating templates is fine from the perspective of something like a server, but it's definately too noisy/verbose for something like pedestrian usage; as you have been pointing out. Sean gave some examples of wrappers in a post today, which may prove fruitful.
 
 I haven't looked at the design of std.format but I would think it only 
 suffers from #3 above, which is not too surprising since you would 
 expect a string generation to require a new buffer.
 
 Sprint seems like a good solution in something like an XML formatting 
 loop, because you can build one object and its in a function call so the 
 thread safety issue is not critical, you can reuse it many times, but as 
 a drop in for 'std.format' it seemed kind of awkward to me.

Probably :)
Feb 10 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
Kevin Bealer wrote:
 
 Okay -- I'm really sorry if any of this seems to have a negative tone. I 
 hesitate to write this since I have a lot of respect for the Tango 
 design in general, but there are a couple of friction points I've noticed.
 
 1. writefln / format replacements
 
 Concerning standard output and string formatting, in phobos I can do 
 these operations:
 
   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);
 
 How do I do these in Tango?  The change to "{0} {1}" stuff is fine with 
 me, in fact I like it, but this syntax:
 
   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);
 
 Is awkward.  And these statements are used *all the time*.  In a recent 
 toy project I wrote, I used Stdout 15 times, compared to using "foreach" 
 only 8 times.  I also use the "format to string" idiom a lot (oddly 
 enough, not in that project), and it's even more awkward.

The conversion modules seem to have slightly spotty API documentation, but I think this will work for the common case: Formatter( "{0} {1} {2}", a, b, c ); The Stdout design is the result of a lengthy discussion involving overload rules and expected behavior. I believe two of the salient points were that the default case should be the simplest to execute, and that the .format method call provided a useful signifier that an explicit format was being supplied. That said, I believe that the default output format can be called via: Stdout( a, b, c ); or the "whisper" syntax: Stdout( a )( b )( c );
 That's why I think phobos really did the "Right Thing" by keeping those 
 down to one token.  Second, the fact that the second one does exactly 
 what the first does but you need to build a template, etc, is annoying. 
  I kept asking myself if I was doing the right thing because it seemed 
 like I was using too much syntax for this kind of operation (I'm still 
 not sure it's the best way to go -- is it?)

Do you consider the Formatter instance to be sufficient or would it be more useful to wrap this behavior in a free function? I'll admit that, being from a C++ background I'm quite used to customizing the library behavior to suit my particular use style, but I can understand the desire for "out of the box" convenience.
 2. toString and toUtf8 (collisions)
 
 The change of the terminology is actually okay with me.
 
 But phobos has a way of using toString as both a method and a top-level 
 function name, all over the place.  This gets really clumsy because you 
 can never use the top level function names when writing a class unless 
 you fully qualify them.
 
 For example, std.cpuid.toString(), always has to be fully qualified when 
 called from a class, and seems nondescriptive anyway.  All the 
 std.conv.toString() functions are nice but it's easy to accidentally 
 call the in-class toString() by accident.
 
 For the utf8 <--> utf16 and similar, it's frustrating to have to do this:
 
 dchar[] x32 = ...;
 char[] x8 = tango.text.convert.Utf.toUtf8(x32);
 
 But you have to fully qualify if you are writing code in any class or 
 struct.  If these were given another name, like makeUtf8, then these 
 collisions would not happen.

One aspect of the Mango design that has carried forward into Tango is that similar functions are typically intended to live in their own namespace for the sake of clarity. Previously, most/all of the free functions were declared in structs simply to prevent collisions, but this had code bloat issues so the design was changed. Now, users are encouraged to use the aliasing import to produce the same effect: import Utf = tango.text.convert.Utf; Utf.toUtf8( x32 ); I'll admit it's not as convenient as simply importing and using the functions, but it does make the origin of every function call quite clear. I personally avoid "using" in C++ for exactly this reason--if I'm using an external routine I want to know what library it's from by inspection. Sean
Feb 09 2007
next sibling parent reply torhu <fake address.dude> writes:
Sean Kelly wrote:
That said, I believe that the 
 default output format can be called via:
 
 Stdout( a, b, c );
 
 or the "whisper" syntax:
 
 Stdout( a )( b )( c );
 

One thing that surprised me when trying out this, was that the buffer is never flushed automatically. Not even when outputting a '\n'. Not for small outputs anyway. I'm used to printf's unbuffered output, at least on windows. Stdout.formatln() does flush, so it might be safer to stick with that than to risk forgetting to flush when doing some 'printf debugging'. Just a thought. (I know about .newline and .flush.)
Feb 09 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
torhu wrote:
 Sean Kelly wrote:
 That said, I believe that the default output format can be called via:

 Stdout( a, b, c );

 or the "whisper" syntax:

 Stdout( a )( b )( c );

One thing that surprised me when trying out this, was that the buffer is never flushed automatically. Not even when outputting a '\n'. Not for small outputs anyway. I'm used to printf's unbuffered output, at least on windows. Stdout.formatln() does flush, so it might be safer to stick with that than to risk forgetting to flush when doing some 'printf debugging'. Just a thought. (I know about .newline and .flush.)

For Cout and Stdout, .opCall with no arguments is equivalent to .flush(). It provides for a quite clean syntax to specify "please flush now". Not perfect but quite usable. I don't think there's way to determine, when using whisper syntax, when an appropriate time would be to flush except if explicitly requested. On a related note, one of the things that bothers /me/ is that no flush is performed at the end of the program. That causes some or all of the output to be missing if you don't explicitly flush after the last output. I'd suggest adding the following to tango.io.Console: --- static ~this () { Cout.flush(); Cerr.flush(); } --- That would fix it, I think. (Well, I could probably write some code that maintains indirect references to Cout/Cerr from modules not importing tango.io.Console, but above addition should fix it for *most* cases)
Feb 10 2007
parent reply torhu <fake address.dude> writes:
Frits van Bommel wrote:
 On a related note, one of the things that bothers /me/ is that no flush 
 is performed at the end of the program. That causes some or all of the 
 output to be missing if you don't explicitly flush after the last output.
 I'd suggest adding the following to tango.io.Console:
 ---
 static ~this ()
 {
          Cout.flush();
          Cerr.flush();
 }
 ---
 
 That would fix it, I think.

I considered this, but I'll try to explain why I didn't suggest it. Basically, it hides that fact that you have forgotten to flush. Pretend that this example is a big piece of code, that sometimes crashes. To debug, you print some numbers, and where the numbers stop should be were the program crashes. --- import tango.io.Stdout; import tango.stdc.stdlib; import tango.stdc.time; void main() { // some code here Stdout("1"); // silly example of 'sometimes crash' if (time(null) & 1) free(cast(void*)1); Stdout("2"); // more code here, etc Stdout("3"); } --- The problem is that with auto flush at program end, it will print '123' when it doesn't crash, and nothing at all when it does crash. So the flushing happens at the 'wrong' time, unless you're aware of the inner workings of Tango. A crash, exit by calling exit() or abort(), or an uncaught exception will cause the auto flush not to happen. Which seems a bit inconsistent. How realistic or valid this concern is, I'm not sure. But it's at least it made me not suggest auto flushing. By the way, is there a particular reason why Cerr is buffered?
Feb 10 2007
parent Kevin Bealer <kevinbealer gmail.com> writes:
torhu wrote:
 Frits van Bommel wrote:
 On a related note, one of the things that bothers /me/ is that no 
 flush is performed at the end of the program. That causes some or all 
 of the output to be missing if you don't explicitly flush after the 
 last output.
 I'd suggest adding the following to tango.io.Console:
 ---
 static ~this ()
 {
          Cout.flush();
          Cerr.flush();
 }
 ---

 That would fix it, I think.

I considered this, but I'll try to explain why I didn't suggest it. Basically, it hides that fact that you have forgotten to flush. Pretend that this example is a big piece of code, that sometimes crashes. To debug, you print some numbers, and where the numbers stop should be were the program crashes. --- import tango.io.Stdout; import tango.stdc.stdlib; import tango.stdc.time; void main() { // some code here Stdout("1"); // silly example of 'sometimes crash' if (time(null) & 1) free(cast(void*)1); Stdout("2"); // more code here, etc Stdout("3"); } --- The problem is that with auto flush at program end, it will print '123' when it doesn't crash, and nothing at all when it does crash. So the flushing happens at the 'wrong' time, unless you're aware of the inner workings of Tango. A crash, exit by calling exit() or abort(), or an uncaught exception will cause the auto flush not to happen. Which seems a bit inconsistent. How realistic or valid this concern is, I'm not sure. But it's at least it made me not suggest auto flushing. By the way, is there a particular reason why Cerr is buffered?

But forgetting to flush is a bug in user code *because* the system does not flush. If the system flushed, then the user code is correct. Put another way, this design decision (to not flush) helps programs that crash (the output is more deterministic), but it hurts programs that do not crash. If a program would have been correct (on a system that flushes stdout), it now isn't because this system doesn't. Kevin
Feb 11 2007
prev sibling next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Sean Kelly wrote:
 Kevin Bealer wrote:
 Okay -- I'm really sorry if any of this seems to have a negative tone. 
 I hesitate to write this since I have a lot of respect for the Tango 
 design in general, but there are a couple of friction points I've 
 noticed.

 1. writefln / format replacements

 Concerning standard output and string formatting, in phobos I can do 
 these operations:

   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);

 How do I do these in Tango?  The change to "{0} {1}" stuff is fine 
 with me, in fact I like it, but this syntax:

   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);

 Is awkward.  And these statements are used *all the time*.  In a 
 recent toy project I wrote, I used Stdout 15 times, compared to using 
 "foreach" only 8 times.  I also use the "format to string" idiom a lot 
 (oddly enough, not in that project), and it's even more awkward.

The conversion modules seem to have slightly spotty API documentation, but I think this will work for the common case: Formatter( "{0} {1} {2}", a, b, c ); The Stdout design is the result of a lengthy discussion involving overload rules and expected behavior. I believe two of the salient points were that the default case should be the simplest to execute, and that the .format method call provided a useful signifier that an explicit format was being supplied.

Is there any reason not to make the format item's index also optional? So that Formatter("{} {} {}", a, b, c); can be used? I mean making it more like %s? The meaning would just be "use the index (1+ the last one that appeared)" or 0 if it's the first to appear. And then if you go there, it might be nice to have a way to say "same as the last item" or "last item +/- some index". Maybe use +/- numbers. So Formatter("{1} {+0} {-1}",a,b); would be equal to Formatter("{1} {1} {0}",a,b); I can't really think of when I'd use that though. The {} I'd use for sure though. Anyway, the positional references are great, and really a must have for any serious I18N usage, but in the original language the app is written in, things tend to appear in the order of the arguments. --bb
Feb 10 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Bill Baxter wrote:
 Is there any reason not to make the format item's index also optional? 
 So that
    Formatter("{} {} {}", a, b, c);
 can be used?  I mean making it more like %s?
 
 The meaning would just be "use the index (1+ the last one that 
 appeared)" or 0 if it's the first to appear.
 
 And then if you go there, it might be nice to have a way to say "same as 
 the last item" or "last item +/- some index".  Maybe use +/- numbers.  So
    Formatter("{1} {+0} {-1}",a,b);
 would be equal to
    Formatter("{1} {1} {0}",a,b);
 I can't really think of when I'd use that though.  The {} I'd use for 
 sure though.
 
 Anyway, the positional references are great, and really a must have for 
 any serious I18N usage, but in the original language the app is written 
 in, things tend to appear in the order of the arguments.

An argument against that would be: Don't you think it'd be easier on the translators if they could just pick the argument number out of the untranslated string without having to keep a running count of which argument they're at? I'd very much like the "{} {} {}" syntax though, especially for anything quick-and-dirty. The "relative" argument numbers I don't see much use for either.
Feb 10 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Frits van Bommel wrote:
 Bill Baxter wrote:
 Is there any reason not to make the format item's index also optional? 
 So that
    Formatter("{} {} {}", a, b, c);
 can be used?  I mean making it more like %s?

 The meaning would just be "use the index (1+ the last one that 
 appeared)" or 0 if it's the first to appear.

 And then if you go there, it might be nice to have a way to say "same 
 as the last item" or "last item +/- some index".  Maybe use +/- 
 numbers.  So
    Formatter("{1} {+0} {-1}",a,b);
 would be equal to
    Formatter("{1} {1} {0}",a,b);
 I can't really think of when I'd use that though.  The {} I'd use for 
 sure though.

 Anyway, the positional references are great, and really a must have 
 for any serious I18N usage, but in the original language the app is 
 written in, things tend to appear in the order of the arguments.

An argument against that would be: Don't you think it'd be easier on the translators if they could just pick the argument number out of the untranslated string without having to keep a running count of which argument they're at?

Good point. But strings for translation are usually extracted by a text processing tool of some sort (like poedit). So it would be easy for that tool to also fill in the numbers while extracting. --bb
Feb 10 2007
prev sibling next sibling parent "Frank Benoit (keinfarbton)" <benoit tionex.removethispart.de> writes:
I like the idea of {}.
Feb 10 2007
prev sibling next sibling parent Derek Parnell <derek psych.ward> writes:
On Sat, 10 Feb 2007 23:43:43 +0900, Bill Baxter wrote:


 Is there any reason not to make the format item's index also optional? 
 So that
     Formatter("{} {} {}", a, b, c);
 can be used?  I mean making it more like %s?

Seem to be a great idea. With that we have the choice between positional and/or indexed tokens in the format string. -- Derek Parnell Melbourne, Australia "Justice for David Hicks!" skype: derek.j.parnell
Feb 10 2007
prev sibling next sibling parent Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 Sean Kelly wrote:
 Kevin Bealer wrote:
 Okay -- I'm really sorry if any of this seems to have a negative 
 tone. I hesitate to write this since I have a lot of respect for the 
 Tango design in general, but there are a couple of friction points 
 I've noticed.

 1. writefln / format replacements

 Concerning standard output and string formatting, in phobos I can do 
 these operations:

   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);

 How do I do these in Tango?  The change to "{0} {1}" stuff is fine 
 with me, in fact I like it, but this syntax:

   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);

 Is awkward.  And these statements are used *all the time*.  In a 
 recent toy project I wrote, I used Stdout 15 times, compared to using 
 "foreach" only 8 times.  I also use the "format to string" idiom a 
 lot (oddly enough, not in that project), and it's even more awkward.

The conversion modules seem to have slightly spotty API documentation, but I think this will work for the common case: Formatter( "{0} {1} {2}", a, b, c ); The Stdout design is the result of a lengthy discussion involving overload rules and expected behavior. I believe two of the salient points were that the default case should be the simplest to execute, and that the .format method call provided a useful signifier that an explicit format was being supplied.

Is there any reason not to make the format item's index also optional? So that Formatter("{} {} {}", a, b, c); can be used? I mean making it more like %s?

Nope. That's a good idea. Sean
Feb 10 2007
prev sibling parent reply torhu <fake address.dude> writes:
Bill Baxter wrote:
 Is there any reason not to make the format item's index also optional? 
 So that
     Formatter("{} {} {}", a, b, c);
 can be used?  I mean making it more like %s?

I like it. :)
Feb 10 2007
parent kris <foo bar.com> writes:
torhu wrote:
 Bill Baxter wrote:
 
 Is there any reason not to make the format item's index also optional? 
 So that
     Formatter("{} {} {}", a, b, c);
 can be used?  I mean making it more like %s?

I like it. :)

it's in
Feb 10 2007
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
Sean Kelly wrote:
 Kevin Bealer wrote:
 Okay -- I'm really sorry if any of this seems to have a negative tone. 
 I hesitate to write this since I have a lot of respect for the Tango 
 design in general, but there are a couple of friction points I've 
 noticed.

 1. writefln / format replacements

 Concerning standard output and string formatting, in phobos I can do 
 these operations:

   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);

 How do I do these in Tango?  The change to "{0} {1}" stuff is fine 
 with me, in fact I like it, but this syntax:

   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);

 Is awkward.  And these statements are used *all the time*.  In a 
 recent toy project I wrote, I used Stdout 15 times, compared to using 
 "foreach" only 8 times.  I also use the "format to string" idiom a lot 
 (oddly enough, not in that project), and it's even more awkward.

The conversion modules seem to have slightly spotty API documentation, but I think this will work for the common case: Formatter( "{0} {1} {2}", a, b, c );

Okay, I didn't see this possibility, that actually looks like a decent syntax; I withdraw the paragraphs in question, subject to the (zig zag) example below. :)
 The Stdout design is the result of a lengthy discussion involving 
 overload rules and expected behavior.  I believe two of the salient 
 points were that the default case should be the simplest to execute, and 
 that the .format method call provided a useful signifier that an 
 explicit format was being supplied.  That said, I believe that the 
 default output format can be called via:
 
 Stdout( a, b, c );
 
 or the "whisper" syntax:
 
 Stdout( a )( b )( c );

Okay - there is a problem with new users who try to print strings with "%" somewhere in the string -- this solves that problem, which is nice.
 That's why I think phobos really did the "Right Thing" by keeping 
 those down to one token.  Second, the fact that the second one does 
 exactly what the first does but you need to build a template, etc, is 
 annoying.  I kept asking myself if I was doing the right thing because 
 it seemed like I was using too much syntax for this kind of operation 
 (I'm still not sure it's the best way to go -- is it?)


So am I, but in D I often don't have to, maybe I'm getting spoiled.
 Do you consider the Formatter instance to be sufficient or would it be 
 more useful to wrap this behavior in a free function?  I'll admit that, 
 being from a C++ background I'm quite used to customizing the library 
 behavior to suit my particular use style, but I can understand the 
 desire for "out of the box" convenience.

Hmmm.... given these two statements: 1. char[] zig = Formatter("{0} {1}", "ciao", "bella"); 2. char[] zag = Formatter("{0} {1}", "one", "two"); Questions: A. If these are done sequentially, will zig be affected by the processing of 'zag'? (I.e. because of buffer sharing.) B. Will doing 1 and 2 from different threads affect zig or zag? If the answer to A and B is both "NO", then I have no problem with using Formatter. I don't care about free function specifically (i.e. for getting a pointer or something), I just want safety, efficiency and clean syntax. Documentation for Sprint suggests that both 1 and 2 are dangerous, I don't know if Formatter is like Sprint in that regard.
 2. toString and toUtf8 (collisions)

 The change of the terminology is actually okay with me.

 But phobos has a way of using toString as both a method and a 
 top-level function name, all over the place.  This gets really clumsy 
 because you can never use the top level function names when writing a 
 class unless you fully qualify them.

 For example, std.cpuid.toString(), always has to be fully qualified 
 when called from a class, and seems nondescriptive anyway.  All the 
 std.conv.toString() functions are nice but it's easy to accidentally 
 call the in-class toString() by accident.

 For the utf8 <--> utf16 and similar, it's frustrating to have to do this:

 dchar[] x32 = ...;
 char[] x8 = tango.text.convert.Utf.toUtf8(x32);

 But you have to fully qualify if you are writing code in any class or 
 struct.  If these were given another name, like makeUtf8, then these 
 collisions would not happen.

One aspect of the Mango design that has carried forward into Tango is that similar functions are typically intended to live in their own namespace for the sake of clarity. Previously, most/all of the free functions were declared in structs simply to prevent collisions, but this had code bloat issues so the design was changed. Now, users are encouraged to use the aliasing import to produce the same effect: import Utf = tango.text.convert.Utf; Utf.toUtf8( x32 ); I'll admit it's not as convenient as simply importing and using the functions, but it does make the origin of every function call quite clear. I personally avoid "using" in C++ for exactly this reason--if I'm using an external routine I want to know what library it's from by inspection. Sean

This is not earth-shaking to me, so the current way is not a big deal, but what I want to avoid is what I think of as the Java naming effect, where you need to do this: System.out.print(foo); ... to print something. To me, the design of a programming language or library is like a natural language. In english we say "tin can" but we always say "can" when there is no ambiguity. You never say "I want to buy a tin can of beans". (I think that in the UK, they say "tin of beans" instead, but its the same idea.) My view is for the common things to be simple and the complex things to be as simple as possible. The extra formality of spelling out the full names of things is something that people find comfort in (*), but I would as soon do without in D. (*) I think people find comfort in it because they have been abused by other languages. In C and C++ land, I agree --- if you do a '#define binary 1' in an include file somewhere, you can kill an algorithm in another file that is a dozen includes up the chain -- I found exactly this definition in a file at my job, and it was an 'interesting' problem to debug. Working on large C and C++ projects breeds a kind of paranoia about symbol tables that I can completely relate to. Sometimes the combination of #include and #define is a lot like "come from" in the way that it messes with the debugging process. http://en.wikipedia.org/wiki/Come_from But again, sorry if I'm being nit picky. Kevin
Feb 10 2007
parent reply kris <foo bar.com> writes:
Kevin Bealer wrote:
 Sean Kelly wrote:
 
 Kevin Bealer wrote:

 Okay -- I'm really sorry if any of this seems to have a negative 
 tone. I hesitate to write this since I have a lot of respect for the 
 Tango design in general, but there are a couple of friction points 
 I've noticed.

 1. writefln / format replacements

 Concerning standard output and string formatting, in phobos I can do 
 these operations:

   writefln("%s %s %s", a, b, c);
   format("%s %s %s", a, b, c);

 How do I do these in Tango?  The change to "{0} {1}" stuff is fine 
 with me, in fact I like it, but this syntax:

   Stdout.formatln("{0} {1} {2}", a, b, c);
   Format!(char).convert("{0} {1} {2}", a, b, c);

 Is awkward.  And these statements are used *all the time*.  In a 
 recent toy project I wrote, I used Stdout 15 times, compared to using 
 "foreach" only 8 times.  I also use the "format to string" idiom a 
 lot (oddly enough, not in that project), and it's even more awkward.

The conversion modules seem to have slightly spotty API documentation, but I think this will work for the common case: Formatter( "{0} {1} {2}", a, b, c );

Okay, I didn't see this possibility, that actually looks like a decent syntax; I withdraw the paragraphs in question, subject to the (zig zag) example below. :)
 The Stdout design is the result of a lengthy discussion involving 
 overload rules and expected behavior.  I believe two of the salient 
 points were that the default case should be the simplest to execute, 
 and that the .format method call provided a useful signifier that an 
 explicit format was being supplied.  That said, I believe that the 
 default output format can be called via:

 Stdout( a, b, c );

 or the "whisper" syntax:

 Stdout( a )( b )( c );

Okay - there is a problem with new users who try to print strings with "%" somewhere in the string -- this solves that problem, which is nice.
 That's why I think phobos really did the "Right Thing" by keeping 
 those down to one token.  Second, the fact that the second one does 
 exactly what the first does but you need to build a template, etc, is 
 annoying.  I kept asking myself if I was doing the right thing 
 because it seemed like I was using too much syntax for this kind of 
 operation (I'm still not sure it's the best way to go -- is it?)


So am I, but in D I often don't have to, maybe I'm getting spoiled.
 Do you consider the Formatter instance to be sufficient or would it be 
 more useful to wrap this behavior in a free function?  I'll admit 
 that, being from a C++ background I'm quite used to customizing the 
 library behavior to suit my particular use style, but I can understand 
 the desire for "out of the box" convenience.

Hmmm.... given these two statements: 1. char[] zig = Formatter("{0} {1}", "ciao", "bella"); 2. char[] zag = Formatter("{0} {1}", "one", "two"); Questions: A. If these are done sequentially, will zig be affected by the processing of 'zag'? (I.e. because of buffer sharing.)

no
 B. Will doing 1 and 2 from different threads affect zig or zag?

no
 
 If the answer to A and B is both "NO", then I have no problem with using 
 Formatter.  I don't care about free function specifically (i.e. for 
 getting a pointer or something), I just want safety, efficiency and 
 clean syntax.
 
 Documentation for Sprint suggests that both 1 and 2 are dangerous, I 
 don't know if Formatter is like Sprint in that regard.

The doc says that each instance of Sprint should not be shared. Each thread can happily create it's own Sprint instance and use that. It's a nice solution when you're doing lots of fiddly formatting, or need to do some formatting for a logger, or whatnot. Once instantiated it doesn't hit the heap ... that's the only benefit. In fact, it's really just a thin wrapper around: # Formatter.sprint (char[] output, char[] format, ...) Another option for multi-threads is to synch on the Sprint object; but that's obviously somewhat less efficient.
 
 2. toString and toUtf8 (collisions)

 The change of the terminology is actually okay with me.

 But phobos has a way of using toString as both a method and a 
 top-level function name, all over the place.  This gets really clumsy 
 because you can never use the top level function names when writing a 
 class unless you fully qualify them.

 For example, std.cpuid.toString(), always has to be fully qualified 
 when called from a class, and seems nondescriptive anyway.  All the 
 std.conv.toString() functions are nice but it's easy to accidentally 
 call the in-class toString() by accident.

 For the utf8 <--> utf16 and similar, it's frustrating to have to do 
 this:

 dchar[] x32 = ...;
 char[] x8 = tango.text.convert.Utf.toUtf8(x32);

 But you have to fully qualify if you are writing code in any class or 
 struct.  If these were given another name, like makeUtf8, then these 
 collisions would not happen.

One aspect of the Mango design that has carried forward into Tango is that similar functions are typically intended to live in their own namespace for the sake of clarity. Previously, most/all of the free functions were declared in structs simply to prevent collisions, but this had code bloat issues so the design was changed. Now, users are encouraged to use the aliasing import to produce the same effect: import Utf = tango.text.convert.Utf; Utf.toUtf8( x32 ); I'll admit it's not as convenient as simply importing and using the functions, but it does make the origin of every function call quite clear. I personally avoid "using" in C++ for exactly this reason--if I'm using an external routine I want to know what library it's from by inspection. Sean

This is not earth-shaking to me, so the current way is not a big deal, but what I want to avoid is what I think of as the Java naming effect, where you need to do this: System.out.print(foo); ... to print something. To me, the design of a programming language or library is like a natural language. In english we say "tin can" but we always say "can" when there is no ambiguity. You never say "I want to buy a tin can of beans". (I think that in the UK, they say "tin of beans" instead, but its the same idea.)

They do - and a lot of extra large tins are consumed :)
 
 My view is for the common things to be simple and the complex things to 
 be as simple as possible.  The extra formality of spelling out the full 
 names of things is something that people find comfort in (*), but I 
 would as soon do without in D.

That's a tough call, as you note below. We were discussing options on this today, so we'll see what evolves?
 
 (*) I think people find comfort in it because they have been abused by 
 other languages.  In C and C++ land, I agree --- if you do a '#define 
 binary 1' in an include file somewhere, you can kill an algorithm in 
 another file that is a dozen includes up the chain -- I found exactly 
 this definition in a file at my job, and it was an 'interesting' problem 
 to debug.  Working on large C and C++ projects breeds a kind of paranoia 
 about symbol tables that I can completely relate to.
 
 Sometimes the combination of #include and #define is a lot like "come 
 from" in the way that it messes with the debugging process.
 
 http://en.wikipedia.org/wiki/Come_from
 
 But again, sorry if I'm being nit picky.

Not at all! Tango is in early Beta, and this is exactly what's needed to file off the rough edges. We may not implement *everything* that everyone suggests, but every bug-report and every little nit-pick is wholly welcomed; seriously :)
 Kevin
 

Feb 10 2007
parent reply Charles D Hixson <charleshixsn earthlink.net> writes:
kris wrote:
 Kevin Bealer wrote:
 Sean Kelly wrote:

 Kevin Bealer wrote:

 O...



The doc says that each instance of Sprint should not be shared. Each thread can happily create it's own Sprint instance and use that. It's a nice solution when you're doing lots of fiddly formatting, or need to do some formatting for a logger, or whatnot. Once instantiated it doesn't hit the heap ... that's the only benefit. In fact, it's really just a thin wrapper around: # Formatter.sprint (char[] output, char[] format, ...) Another option for multi-threads is to synch on the Sprint object; but that's obviously somewhat less efficient.

Ouch. That's ambiguous syntax. Do you mean the programs shouldn't share the buffer, or that the library should ensure that sharing doesn't happen? Either way fits the statement. I would have read it as meaning the first, but you seem to be saying that it's the second.
 
 

 Not at all!
 
 Tango is in early Beta, and this is exactly what's needed to file off 
 the rough edges. We may not implement *everything* that everyone 
 suggests, but every bug-report and every little nit-pick is wholly 
 welcomed; seriously :)
 
 Kevin


reporting your understanding. If you were quoting the documentation, I think it needs editing. P.S.: If I remember properly, in an earlier beta sharing the buffers in one's program resulted in run-time errors (buffer overwrites) that weren't detected...unless one wrote checks for the error. This may have been a slightly different case, however.
Feb 10 2007
parent reply kris <foo bar.com> writes:
Charles D Hixson wrote:
 I'm not sure whether you were quoting the documentation, or reporting 
 your understanding.  If you were quoting the documentation, I think it 
 needs editing.

From the doc: "Please note that the class itself is stateful, and therefore a single instance is not shareable across multiple threads." Thus, it is considered unwise to share a single instance of Sprint across multiple threads. However, multiple threads /can/ share a single instance if they follow this pattern: synchronized (GlobalSprint) GlobalSprint ("do my formatting", with, these, args); This is a fairly standard mechanism in D for sharing resources across threads, and it's what I had referred to. But there's no reason to do this kind of thing at all. The use-case for Sprint is to keep a handy formatter around for doing fast and convenient layout. Adding synchronized to the mix tends to defeat one of those desirable attributes, so we don't recommend it :) If you're content to stash layout content into a temporary buffer instead, there's Formatter.sprint() which takes an output array. The output array in such a case would typically be stack-based.
Feb 10 2007
next sibling parent reply Charles D Hixson <charleshixsn earthlink.net> writes:
kris wrote:
 Charles D Hixson wrote:
 I'm not sure whether you were quoting the documentation, or reporting 
 your understanding.  If you were quoting the documentation, I think it 
 needs editing.

From the doc: "Please note that the class itself is stateful, and therefore a single instance is not shareable across multiple threads." Thus, it is considered unwise to share a single instance of Sprint across multiple threads. However, multiple threads /can/ share a single instance if they follow this pattern: synchronized (GlobalSprint) GlobalSprint ("do my formatting", with, these, args); This is a fairly standard mechanism in D for sharing resources across threads, and it's what I had referred to. But there's no reason to do this kind of thing at all. The use-case for Sprint is to keep a handy formatter around for doing fast and convenient layout. Adding synchronized to the mix tends to defeat one of those desirable attributes, so we don't recommend it :) If you're content to stash layout content into a temporary buffer instead, there's Formatter.sprint() which takes an output array. The output array in such a case would typically be stack-based.

The problem that I ran into with this before didn't involve multiple threads...but it did involve writing out to a file and to a console. Unfortunately, It's been several months, so I don't remember the particulars. The resolution, however, was to allocate multiple buffers within the same routine. It might have involved buffering several different items within the same statement. I think that may have been where things went wrong. And my choices were to allocate several different buffers, or to break the statement into several different statements. That sounds right, but don't depend on it. As I said it's been several months.
Feb 10 2007
parent kris <foo bar.com> writes:
Charles D Hixson wrote:
 kris wrote:
 
 Charles D Hixson wrote:

 I'm not sure whether you were quoting the documentation, or reporting 
 your understanding.  If you were quoting the documentation, I think 
 it needs editing.

From the doc: "Please note that the class itself is stateful, and therefore a single instance is not shareable across multiple threads." Thus, it is considered unwise to share a single instance of Sprint across multiple threads. However, multiple threads /can/ share a single instance if they follow this pattern: synchronized (GlobalSprint) GlobalSprint ("do my formatting", with, these, args); This is a fairly standard mechanism in D for sharing resources across threads, and it's what I had referred to. But there's no reason to do this kind of thing at all. The use-case for Sprint is to keep a handy formatter around for doing fast and convenient layout. Adding synchronized to the mix tends to defeat one of those desirable attributes, so we don't recommend it :) If you're content to stash layout content into a temporary buffer instead, there's Formatter.sprint() which takes an output array. The output array in such a case would typically be stack-based.

Mmmm... The problem that I ran into with this before didn't involve multiple threads...but it did involve writing out to a file and to a console. Unfortunately, It's been several months, so I don't remember the particulars. The resolution, however, was to allocate multiple buffers within the same routine. It might have involved buffering several different items within the same statement. I think that may have been where things went wrong. And my choices were to allocate several different buffers, or to break the statement into several different statements. That sounds right, but don't depend on it. As I said it's been several months.

If you /retained/ a reference to the internal content, and then subsequently wrote over it via a second usage, then yes. You just witnessed a mutated alias at work. That's to be expected from any mutable shared state (multi-threaded or otherwise), unless you explicitly .dup the results before you stash them away (which then eliminates the alias). Still, if this little wrapper is causing such difficulties, perhaps it should be removed instead. Embedding the .dup in the return value is certainly one alternative, but then that would defeat the overall intent also :) - Kris
Feb 10 2007
prev sibling parent reply Sean Kelly <sean f4.ca> writes:
kris wrote:
 Charles D Hixson wrote:
 I'm not sure whether you were quoting the documentation, or reporting 
 your understanding.  If you were quoting the documentation, I think it 
 needs editing.

From the doc: "Please note that the class itself is stateful, and therefore a single instance is not shareable across multiple threads." Thus, it is considered unwise to share a single instance of Sprint across multiple threads. However, multiple threads /can/ share a single instance if they follow this pattern: synchronized (GlobalSprint) GlobalSprint ("do my formatting", with, these, args); This is a fairly standard mechanism in D for sharing resources across threads, and it's what I had referred to.

For what it's worth, Tango also provides thread-local storage, and while it isn't ideal from a semantic standpoint (a bona-fide 'local' storage class would be much nicer), it's an option worth considering. It could be used like so: auto TLSPrint = new ThreadLocal!(Sprint); // do this in each thread TLSPrint.val = new Sprint; // use the local Sprint instance TLSPrint.val()( "do my formatting", with, these, args ); Accessing a TLS value tyically amounts to a few pointer dereferences so it's far less costly than acquiring a mutex, which may or may not be important to you. As a side-note... Tango currently provides 64 TLS "slots". This amount is fixed mostly to keep performance as high as possible, but it could easily be increased if it turns out not to be enough. Sean
Feb 10 2007
parent reply Johan Granberg <lijat.meREM OVE.gmail.com> writes:
Sean Kelly wrote:

 kris wrote:
 Charles D Hixson wrote:
 I'm not sure whether you were quoting the documentation, or reporting
 your understanding.  If you were quoting the documentation, I think it
 needs editing.

From the doc: "Please note that the class itself is stateful, and therefore a single instance is not shareable across multiple threads." Thus, it is considered unwise to share a single instance of Sprint across multiple threads. However, multiple threads /can/ share a single instance if they follow this pattern: synchronized (GlobalSprint) GlobalSprint ("do my formatting", with, these, args); This is a fairly standard mechanism in D for sharing resources across threads, and it's what I had referred to.

For what it's worth, Tango also provides thread-local storage, and while it isn't ideal from a semantic standpoint (a bona-fide 'local' storage class would be much nicer), it's an option worth considering. It could be used like so: auto TLSPrint = new ThreadLocal!(Sprint); // do this in each thread TLSPrint.val = new Sprint; // use the local Sprint instance TLSPrint.val()( "do my formatting", with, these, args ); Accessing a TLS value tyically amounts to a few pointer dereferences so it's far less costly than acquiring a mutex, which may or may not be important to you. As a side-note... Tango currently provides 64 TLS "slots". This amount is fixed mostly to keep performance as high as possible, but it could easily be increased if it turns out not to be enough. Sean

What happens when you run out of slots? Would it be possible to have a dynamic amount of slots (doubling whenever there is to few)?
Feb 11 2007
parent Sean Kelly <sean f4.ca> writes:
Johan Granberg wrote:
 Sean Kelly wrote:
 
 kris wrote:
 Charles D Hixson wrote:
 I'm not sure whether you were quoting the documentation, or reporting
 your understanding.  If you were quoting the documentation, I think it
 needs editing.

therefore a single instance is not shareable across multiple threads." Thus, it is considered unwise to share a single instance of Sprint across multiple threads. However, multiple threads /can/ share a single instance if they follow this pattern: synchronized (GlobalSprint) GlobalSprint ("do my formatting", with, these, args); This is a fairly standard mechanism in D for sharing resources across threads, and it's what I had referred to.

it isn't ideal from a semantic standpoint (a bona-fide 'local' storage class would be much nicer), it's an option worth considering. It could be used like so: auto TLSPrint = new ThreadLocal!(Sprint); // do this in each thread TLSPrint.val = new Sprint; // use the local Sprint instance TLSPrint.val()( "do my formatting", with, these, args ); Accessing a TLS value tyically amounts to a few pointer dereferences so it's far less costly than acquiring a mutex, which may or may not be important to you. As a side-note... Tango currently provides 64 TLS "slots". This amount is fixed mostly to keep performance as high as possible, but it could easily be increased if it turns out not to be enough.

What happens when you run out of slots?

An exception is thrown. Please note that 64 slots doesn't mean each thread occupies a separate slot however, but rather than there is storage reserved for 64 different thread-local variables. For a typical application, I think this is more than sufficient.
 Would it be possible to have a dynamic amount of slots (doubling whenever
 there is to few)?

Not without involving a mutex. When a TLS slot is deleted, the algorithm visits each thread and nulls out the appropriate slot. With a fixed-size array this just works, but if the array can be resized then there's a chance it may be resized at exactly the wrong moment and you end up with a segfault. The ideal scenario would be to use the operating system's built-in thread-local storage mechanism, but there is no efficient way to make the GC aware of these locations (at least not without some clever hacking based on low-level knowledge of how each OS actually stored this information, and it is different for every OS). The current approach was chosen simply because it was the most general, efficient, and reliable. Sean
Feb 11 2007
prev sibling parent torhu <fake address.dude> writes:
Henning Hasemann wrote:
 I'm having little difficulties understanding the relationship of those 3.
 Are they all alternatives to each other?
 
 What I know is that phobos is the standard library that provides writeln etc,
 also tango seems to have IO capabilities so could one compile D programs
 with tango instead of phobos?
 When to use which of these?

Tango is a replacement standard library, which you would use instead of Phobos. It's a combination of ares and mango, plus some new stuff. Mango can be used together with phobos, to achieve some of the Tango functionality. If you want to be compatible with both Tango and phobos, you can use version (Tango) in your app to detect which one you're compiling with. http://dsource.org/projects/tango http://dsource.org/projects/mango http://dsource.org/projects/ares
Feb 07 2007