Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add print #3971

Closed
wants to merge 2 commits into from
Closed

Add print #3971

wants to merge 2 commits into from

Conversation

andralex
Copy link
Member

@andralex andralex commented Feb 4, 2016

Suggested by a user frustrated by the OOB experience, which echoes a long-time annoyance of mine while debugging using writeln. Here be Another Epic Debate.

/**

Writes its arguments in text format to the file separated pairwise by a
space (or custom `separator`), followed by a newline (or custom `eol`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Params:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example(s):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples should be taken care of by the ddoc'd unittest(s).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I always forget that feature when looking at code.

@schveiguy
Copy link
Member

Wow, this is not how I would have expected it to work. Is the mixin required? Seems like you could just call write(arg, " ") for each parameter, no?

{
void printCSVRow(Data...)(Data data)
{
print!", "(data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wouldn't actually be legal csv.... but it might be if the print function could also take a per-item transformation function to quote it properly. csv technically shouldn't have a space after the comma either.

The transformation function might just be interesting though, but it might also complicate it more than is worth doing. Some kinda of dg(item.asString) that defaults to identity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Echoing my statement above, the unit test should not print to stdout. The unit tests should either

  1. Replace stdout with a temp file (temporarily)
  2. Use a temp file directly.

You could also have a unit test that just verified the call could compile. Wouldn't make a good example though.

@schveiguy
Copy link
Member

Seems like you could just call write(arg, " ") for each parameter, no?

Heh, I swear I didn't look at this first: https://forum.dlang.org/post/mailman.4272.1454600460.22025.digitalmars-d-learn@puremagic.com

@adamdruppe
Copy link
Contributor

This is a tangential thought but something that might be interesting is to consider how structs are written. writeln by default does something like Struct(value, value) which is often quite nice, but often unreadable too.

Is the goal of print to make something pretty for quick eyeball inspection? If so, when it sees a struct, it might want to consider formatting it differently too with indentation and line breaks.

Maybe that's outside the scope of this function though.

@burner
Copy link
Member

burner commented Feb 4, 2016

@adamdruppe IMO this is a function for the user that does not want to type:

int a, b;
writeln(a, ' ', b);

but rather have it the python way

a = 0
b = 0
print(a,b);

nothing more nothing less

@andralex
Copy link
Member Author

andralex commented Feb 4, 2016

@burner correct. @schveiguy the mixin is for efficiency.

@adamdruppe
Copy link
Contributor

hmm, all right. One request though: make it call flush at the end, so it just works out of the box for IDE users too (their stdout is block buffered because the runtime detects it as being a pipe rather than a terminal)

@burner
Copy link
Member

burner commented Feb 4, 2016

IMO the name print will lead to people mistaking this function as the D idomatic way of writing data to files, instead of write or output ranges. Therefore, I would like the function to be renamed to something like dump. Something that makes it somewhat clear that this is a convenient function to "dump" out data.

@schveiguy
Copy link
Member

the mixin is for efficiency.

Meh. this is for debugging right?

I only bring up the point because the function you have there is really hard to debug, understand, read, explain. Artur's version is much easier.

@JackStouffer
Copy link
Member

To put @schveiguy's point in a different way, because this is for debugging, you would want compile times to be as fast as possible.

@dnadlinger
Copy link
Member

I'm not sure about this. If we are talking about adding a debugging aid, what about something similar to Julia's @show macro instead, which also prints the expression that generates the value in a formatted way? Obviously, we don't have AST macros, but we can always do something like stdio.dump!("a", "b + c");

@schveiguy
Copy link
Member

@klickverbot I like that idea. One of the reasons I find myself not needing something like print is that I do things like: writeln("a = ", a, ", b = ", b);. So I don't need to use an extra arg to put spaces, there's already a string to put a space in.

Having something like dump!("a", "b") automatically do that would be awesome.

@dnadlinger
Copy link
Member

@schveiguy: Yes, that's precisely the idea. I was surprised how useful the feature actually was in debugging Julia code.

Edit: Sorry, hit the wrong button.

@dnadlinger dnadlinger closed this Feb 4, 2016
@dnadlinger dnadlinger reopened this Feb 4, 2016
@adamdruppe
Copy link
Contributor

On Thu, Feb 04, 2016 at 10:03:11AM -0800, Steven Schveighoffer wrote:

Having something like dump!("a", "b") automatically do that would be awesome.

If they are taken as alias arguments, you don't even need to quote them.

I've done this with function calls and such, and structs formatted with indentation to make it far more readable. Pretty nice.

Throws: $(D Exception) if the file is not opened.
$(D ErrnoException) on an error writing to the file.
*/
void print(string separator = " ", string eol = "\n", S...)(S args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like putting this inside File. It's yet another method that does something another method already does, except different. What about move this to module scope and take File as first argument, so that we can make use of UFCS?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, formatting a list of items delimited by some string isn't specific to file I/O; what about something in std.format that does this generically, returning an input range of char (or whatever) so that it can be reused for other stuff that doesn't involve file I/O?

@dnadlinger
Copy link
Member

@adamdruppe:

If they are taken as alias arguments, you don't even need to quote them.

That wouldn't work for complex expressions, though, or have things changed on that front lately?

@adamdruppe
Copy link
Contributor

On Thu, Feb 04, 2016 at 10:28:15AM -0800, David Nadlinger wrote:

That wouldn't work for complex expressions, though, or have things changed on that front?

I don't think strings would either though because they wouldn't be mixed into the same scope...

@dnadlinger
Copy link
Member

I don't think strings would either though because they wouldn't be mixed into the same scope...

… and of course you are right. Have been using too many languages recently, I guess…

@jacob-carlborg
Copy link
Contributor

This is a tangential thought but something that might be interesting is to consider how structs are written. writeln by default does something like Struct(value, value) which is often quite nice, but often unreadable too.

I agree. I think the Ruby debugger Pry outputs a pretty good structure. This is an example:

=> #<TmGrammar::Node::Match::And:0x007feced8a4238
 @left=
  #<TmGrammar::Node::Match::RuleReference:0x007fecee0011e8
   @containing_pattern=
    #<TmGrammar::Node::Pattern:0x007feced916bf8
     @begin_captures={},
     @capture_number=0,
     @captures={},
     @end_captures={},
     @grammar=
      #<TmGrammar::Node::Grammar:0x007feced9443c8
       @comment="Foo language",
       @file_types=["d", "di"],
       @first_line_match="^#!.*\\bg?dmd\\b.",
       @key_equivalent="^~D",
       @name="Foo",
       @patterns=[],
       @repository=

@tofuninja
Copy link

mixin template dump(Names ... )
{
    auto _unused_dump = {
        import std.stdio : writeln, write; 
        foreach(i,name; Names)
        {
            write(name, " = ", mixin(name), (i<Names.length-1)?", ": "\n");
        }
        return false;
    }();
}

unittest{
    int x = 5;
    int y = 3;
    int z = 15;

    mixin dump!("x", "y");  // x = 5, y = 3
    mixin dump!("z");       // z = 15
    mixin dump!("x+y");     // x+y = 8
    mixin dump!("x+y < z"); // x+y < z = true
}

@adamdruppe
Copy link
Contributor

On Thu, Feb 04, 2016 at 06:26:10PM -0800, Joseph Emmons wrote:

auto _unused_dump = {
}();

hmm, that's clever!

@JakobOvrum
Copy link
Member

When args all have a common type, this is functionally the same as writefln("%-(%s%| %)", only(args)) or writefln("%-(%s%| %)", tuple(args).range) (with #3198). So I guess print has the advantage of supporting heterogeneous types and a smaller syntactic footprint.

Would be nice if we could do something like write(staticMap!(…)) instead of yet another black box string mixin.

@CyberShadow
Copy link
Member

@schveiguy the mixin is for efficiency.

Wait, why would this make a difference? The loop will be unrolled during compilation so the ternary will be folded away, no?

@andralex
Copy link
Member Author

andralex commented Feb 6, 2016

Hey guys, I'm all for adding a dump separately but this is not it. It's just writing stuff out with spaces/separators in between. That may or may not be used for debugging and its speed may or may not matter.

@adamdruppe I'm considering adding flush. How much of a problem is that for IDE users?

@andralex
Copy link
Member Author

andralex commented Feb 6, 2016

@CyberShadow I meant mixin as opposed to multiple calls to write().

@adamdruppe
Copy link
Contributor

On Sun, Feb 07, 2016 at 10:07:22AM -0800, David Nadlinger wrote:

@adamdruppe: It's potentially
writeln!", "(a, b, c, d);
vs.
writeln(a, ", ", b, ", ", c, ", " d);.

Yeah, but even then, you could do a .join(", ") or whatever...

That being said, there might be an argument for a "just works"-type function. However, I'm quite convinced that the stated purpose of such a function should be dump the arguments in a human-readable form for debugging, instead of being a helper for a certain particular style of formatting.

I agree. Even if it isn't great in the first pass, if we say "this is for a debug dump, meant to be pretty whatever that may be" then it keeps the door open to change it later.

@andralex
Copy link
Member Author

andralex commented Feb 7, 2016

Yeah, but even then, you could do a .join(", ") or whatever...

No, you can't if the items have different types etc. @adamdruppe I think your counterpoint overall doesn't stand. There are ways to do what print does. Neither is easy and brief.

Overall: sigh. Yet Another Great Debate. Regarding this question by @klickverbot:

What is the use case where such a separate print function is the appropriate design choice?

Let's not get all self-important. This is not a "design choice" as much as - do we want to provide this convenience function or not. There's no reasoning or algorithm that takes us to the decision. I disagree that the addition would be confusing. It's just a different function that does a different thing.

Regarding its usefulness, I have plenty in the code I'm writing. The way I look at it, it's almost always an error to print two integers next to each other. yet writeln is liable to do exactly that. Why, then, not offer a function that doesn't do the wrong thing out of the box. Etc.

I'm in favor of adding it, but honest the more churn in this discussion the more my enthusiasm drops.

About writeln!" "... ionno. It's an idea. Possible. Doable. Maybe not super pretty. Perhaps the path of least resistance.

@CyberShadow
Copy link
Member

Yep, let's merge and move on. The goal here is to save some keystrokes for a very common case, and some edit-build-run cycles in those long debugging nights when you forget to add the " ",, so a writeln parameter misses the point (without an alias that is, but that's overdesign I think).

👍

@dnadlinger
Copy link
Member

Overall: sigh. Yet Another Great Debate.

What would you expect? Would you prefer me just saying: No, let's not do it?

Adding what amounts to a slightly modified copy of something else to the standard library is a questionable proposition to begin with. There can be reasons to do so, but the reasons for it'd better be clear (cf. the reduce problem, where you by the way also tried to find a different solution first).

I disagree that the addition would be confusing.

If you see write, writeln and print being used in a program, what is the difference (without having looked up the functions)? Does print output a newline? If so, why is it not called println?

The way I look at it, it's almost always an error to print two integers next to each other. […] Why, then, not offer a function that doesn't do the wrong thing out of the box. Etc.

What about two strings though? Etc., etc.

I'm in favor of adding it, but honest the more churn in this discussion the more my enthusiasm drops.

I'm glad I can be of help to avoid rash decisions. If it was a great addition your enthusiasm, at least for the thing itself, would stay.

There might be not a huge design space to explore here, but it's definitely more than the simple yes/no choice you make it out to be. Do we offer separate print/println versions (I'd say: no), do we declare it as a debugging helper and remove the formatting arguments (I'd say: yes), etc.

@adamdruppe
Copy link
Contributor

On Sun, Feb 07, 2016 at 03:23:16PM -0800, Andrei Alexandrescu wrote:

No, you can't if the items have different types etc. @adamdruppe I think your counterpoint overall doesn't stand. There are ways to do what print does. Neither is easy and brief.

I think we're more on the same side than not.. I think the writeln!", " option fails on the easy or brief metric (compared to the competition).

Let's not get all self-important. This is not a "design choice" as much as - do we want to provide this convenience function or not.

I actually wrote briefly about this thread in TWID today because I think it reflects a Phobos philosophy. This individual function isn't a big deal, but I do think it indicates a bigger picture that we ought to have a conversation about. I'm sure many of the other commentators feel the same way.

Regarding its usefulness, I have plenty in the code I'm writing.

I'd find a debug dump function to be enormously useful too.

The way I look at it, it's almost always an error to print two integers next to each other. yet writeln is liable to do exactly that. Why, then, not offer a function that doesn't do the wrong thing out of the box. Etc.

What's the difference between substr and substring in Javascript?

I fear write vs print in D could be a similar FAQ down the line... especially since writef is evolved from printf.

So are we going to be in a situation where "don't use printf, writef is better oh but wait don't use writef, print is better, well, except for those cases where you don't want the space."

@jmdavis
Copy link
Member

jmdavis commented Feb 8, 2016

The way I look at it, it's almost always an error to print two integers next to each other. yet writeln is liable to do exactly that.

Personally, I was very surprised when I found out that writeln would take more than one argument. I would have assumed that you needed to use writefln with a format string to deal with multiple arguments.

Overall: sigh. Yet Another Great Debate.

Of course. You're proposing a function that's purely a convenience function. What else was going to happen? Personally, it's not something that I've ever felt a need for. I'd just use writefln, and I'd want to identify each item being printed out - e.g. writefln("a: %s, b: %s", a, b); - rather than just putting spaces or commas or whatever between them, but even if I didn't, writefln("%s %s", a, b); isn't exactly hard to write instead of print(a, b);.

So, I don't personally see any real value in this, I will never use it, and if it were purely up to me, I wouldn't add it, but if you want it, I don't care enough to fight against it. Having a single way to do things vs having some convenience functions for certain use cases is always highly subjective and ripe for debate. The primary cost to this in the long run is going to be that there's yet one more printing function added to the list for D programmers to learn and distinguish, which isn't exactly a good thing. But maybe the convenience it provides is worth it for those folks who would use it. Ultimately, it's a judgement call.

@JakobOvrum
Copy link
Member

Would be nice if we could do something like write(staticMap!(…)) instead of yet another black box string mixin.

As a follow-up on this, even though the source code turned out quite a bit nicer, I wasn't able to make this work. Maybe with future compiler improvements it will be possible, or maybe I was missing something. If a string mixin is the only way to accomplish this and the performance benefit is worth it, then LGTM.

@andralex
Copy link
Member Author

Implementation is now a lot simpler, owing to the forum discussion.

@andralex
Copy link
Member Author

So are we going to be in a situation where "don't use printf, writef is better oh but wait don't use writef, print is better, well, except for those cases where you don't want the space."

The way I see it is their charters are different, one is not better or worse than the other. Why would one advise to prefer print over writef? Why would this become an FAQ additional to their respective documentations? This is not http://stackoverflow.com/questions/3745515/what-is-the-difference-between-substr-and-substring!

@schveiguy
Copy link
Member

Implementation looks much better!

@wilzbach
Copy link
Member

Some comments:

@andralex

  1. It currently produces two line break - maybe use writef instead?
  2. I agree that tests shouldn't produce output
  3. I also fear that print and writeln might be pretty confusing.
    How should a newcomer know that print is mostly aimed at convenience?

Especially because in Python it's the default output method and stuff like print(*myFancyArray, sep=',', file=outFile) is quite common to write csv.

Ideas to resolve this

  1. How about using a different name to state it's purpose like echo, message, say, writes (I would prefer writes as it keeps it in the write family)

About writeln!" "... ionno. It's an idea. Possible. Doable. Maybe not super pretty. Perhaps the path of least resistance. (@andralex)

Yep as far as I understood it that seems to be general consensus here. Is there anyone really opposing this idea?

@tofuninja I would love to see more discussions on dump (seem very useful to me) - on a separate PR maybe?

@ixid
Copy link

ixid commented Mar 21, 2016

Can this move forward now into Phobos?

@wilzbach
Copy link
Member

Can this move forward now into Phobos?

@ixid A couple of reviewers and myself don't really like the confusion generated by introducing print for sheer convenience. Imho it should be okay as compile-time argument or a rather unknown verb like echo or writes. Moreover there are still some issues with this PR.
See my review above ;-)

@ixid
Copy link

ixid commented Mar 21, 2016

Could you present anything to back up this supposed confusion? It's an extremely weak argument without any apparent evidence or examples from other communities. There are already many different functions that write or print, it's not as if this is a pristine area that will be muddied. There are also many languages that use print to print and no one I've seen appears to be particularly confused by that.

@wilzbach
Copy link
Member

Could you present anything to back up this supposed confusion? It's an extremely weak argument without any apparent evidence or examples from other communities.

It's not as many of the previous speakers have explained (see e.g. @klickverbot's post the problem is not that print is a bad name or used by other functions, it's that print is also usually used in other standard libraries instead of write. So adding print will require all newbies to read up the difference between print and write, because both will now be listed in std.stdio and they don't know which is the right one to use. Of course "good" documentation or books can solve that, but it still a complication for the majority with little gain. I am not aware of any other standard library that provides such a ambivalent stdio API. In Python for example print (screen, can be for files) and write (files) are in different modules.

Besides the main use case of this function is to dump some data during development, which should be done via a (to-be-added) dump command.

@ixid
Copy link

ixid commented Mar 21, 2016

Newbies could use print out of the gate without needing to read the docs, that's the point. They certainly would need to read them to use write, writef writefl, writeln and understand what percent symbols they are supposed to be using and how to get a new line at the end as well as what on earth the current four write versions are supposed to do (fln is not a great mnemonic). I would hope tutorials would fairly quickly move towards 'use print unless you want to do something more complex with formatting in which case read these docs for those functions'. This is lowering the barrier for newbies, not increasing it. Print is much easier to remember than echo or dump as those are not as closely associated with generating output in most people's minds.

The use case for this function is similar to that for writeln but easier to use. This quibbling about names seems more obstructive than productive or insightful. The cognitive load is tiny to non-existent.

Just to add- some of the suggested alternative names are fine like 'say' if that is the sticking point.

@wilzbach
Copy link
Member

Just to add- some of the suggested alternative names are fine like 'say' if that is the sticking point.

I would be prefer say. Would does @andralex say? (needs the @andralex tag)


Throws: $(D Exception) if the file is not opened.
$(D ErrnoException) on an error writing to the file.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

writeln and print should refer to each other via See_Also, to reduce confusion.

@wilzbach
Copy link
Member

Could someone add the @andralex label? Thanks!

@CyberShadow CyberShadow added the @andralex Approval from Andrei is required label Apr 10, 2016
@CyberShadow
Copy link
Member

OK, batlight's on :)

@CyberShadow
Copy link
Member

I would be prefer say.

What other languages use say? print is pretty common.

@PetarKirov
Copy link
Member

What about a compromise between @tofuninja's dump and @andralex's print:

void print(Vars...)(string separator = " ", string eol = "\n");

struct P { int x, y; }
P point = P(2, 3);
int x = 5;
double y = 4.2;

print!(x, y, point, "3 + 7 * 2", () => point.x, "[1, 2, 3] ~ 4")(",\n");

Output:

#0: { x = 5 },
#1: { y = 4.2 },
#2: { point = P(2, 3) },
#3: { "3 + 7 * 2" = 17 },
#4: { () => 2 },
#5: { "[1, 2, 3] ~ 4" = [1, 2, 3, 4] }

http://dpaste.dzfl.pl/4b2a10546c0b

?

@wilzbach
Copy link
Member

@ZombineDev - I really like this! How about submitting a new PR? Probably the name 'dump' is still better fit your function though..

{
import std.range : repeat;
import std.string : join;
static immutable fmt = repeat("%s", S.length).join(separator) ~ eol;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is over engineering. A static foreach loop for args and default FormatSpec!char along with formatValue would be faster to compile and faster to execute, and looks clear.

@andralex
Copy link
Member Author

I'll close this as too controversial.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@andralex Approval from Andrei is required
Projects
None yet
Development

Successfully merging this pull request may close these issues.