shenme has asked for the wisdom of the Perl Monks concerning the following question:

I can work-around this - the wisdom sought is "why"?   Why is sprintf refusing to work with "sprintf @args" when printf is quite happy with "printf @args".

In some routines I needed to replace printf's with something to capture output into an array.   So I substituted a routine that used sprintf and then saved the output string away.   And started to see output strings of '1', '2', '5', etc.   After flailing about with increasing paranoia I finally decided to read the docs absolutely literally, and changed the routine from

sub xprintf { my( @args ) = @_; push @aglobal, sprintf @args; }
to something like
sub xprintf { my( $fmt, @args ) = @_; push @aglobal, sprintf $fmt, @args; }
and boggled that that worked.

sprintf usage is documented as "sprintf FORMAT, LIST" as opposed to, say, "sprintf LIST".   The same form is documented for printf, "printf FORMAT, LIST".   I had supposed this was merely for explication, to make clear that the first value is the format string.   And if I code something like

@args = ( "We see '%s'\n", 'wowza' ); printf @args;
it works just fine.   However, using the same form with sprintf the following produces the string '2',
@arghs = ( "We see '%s'\n", 'wowza' ); $s = sprintf @arghs;
Very strange that sprintf should be so much more pedantic than printf, eh?

[ One of the CB'ots thought there was a discussion of this same oddity somewhere hereabouts, but searching on "sprintf printf format", "sprintf array", "sprintf format", and so on didn't find it. ]

Replies are listed 'Best First'.
Re: sprintf format parameter required
by davido (Cardinal) on Oct 31, 2003 at 05:31 UTC
    Per perldoc -f sprintf:
    Unlike printf, sprintf does not do what you probably mean when you pass it an array as your first argument. The array is given scalar context, and instead of using the 0th element of the array as the format, Perl will use the count of elements in the array as the format, which is almost never useful.

    I can't comment on why it's that way (though I speculate that the difference is related to different internal prototypes), but that's the way it is, and it's documented. I'm sure someone can tell us why they were implemented identically in almost every way except this.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
      Don't know how I missed it.   Sheesh, 'printf', 'sprintf', 'array', 'format'.   They really tried to reach me!
Re: sprintf format parameter required
by demerphq (Chancellor) on Oct 31, 2003 at 09:33 UTC

    Youv'e just been bitten by perls prototyping system:

    D:\>perl -le "print prototype 'CORE::sprintf'" $@ D:\>perl -le "print prototype 'CORE::printf'"

    As you can see sprintf is prototyped to expect a scalar followed by a list, whereas printf is a "special" built in and is handled seperately (by the lexer I believe) because of the funky indirect object syntax for handling printing to a filehandle.

    Incidentally this is a really good illustration of why its generally a bad idea to use prototypes in your own code. More often than not in the end you wind up getting bitten.


    ---
    demerphq

      First they ignore you, then they laugh at you, then they fight you, then you win.
      -- Gandhi


      I fail to see how this is a good example of prototypes as a bad idea. Granted, the fact that you get weird results instead of a warning that this is a prototype violation is annoying, but still. I'd solve this problem by using the same prototype for xprintf as for sprintf. While the consequences are probably less in Perl than in C, making it harder to omit the format string and pass something not intended as such as the first argument is something you might be thankful for one day.

        I fail to see how this is a good example of prototypes as a bad idea

        %99.99 of the time passing to a subroutine a list of arguments ($x,$y,$z) and an array containing the same values @a=($x,$y,$z), are exactly equivelent. When you utilize prototypes this ceases to be true, and as such can end up requiring bizarre workarounds (but only after some head scratching).

        Granted, the fact that you get weird results instead of a warning that this is a prototype violation is annoying, but still.

        You admit its annoying, yet you piffle it away. No doubt you have quite a bit of experience with perl, and prototyping, and even still I bet when you bump into this annoyance you spend more time figuring out the weird behaviour than you needed. A beginner, or maintenance programmer working on your code probably wont have your skills or experience, and as such may end up wasting a lot of time figuring out what is going on. This I think is a Bad Thing, especially as its avoidable.

        I'd solve this problem by using the same prototype for xprintf as for sprintf.

        Ah yes, and lose the print to filehandle ability?

        While the consequences are probably less in Perl than in C, making it harder to omit the format string and pass something not intended as such as the first argument is something you might be thankful for one day.

        No, actually I've never been thankful for this and I suspect I never will, in fact more often than not ive been annoyed by it. Consider the keys() function. I rarely need the keys of a named hash, I very often need the keys of a reference to a hash. The fact that I have to deref my hashrefs only to have the internals transparently convert it back into a reference to work with it, goes against the common case, and is annoying. Not only that but using prototypes doesnt do what you think it does. Consider that a prototype of

        perl -e "sub foo($$$){print @_} @x=(1,2,3); @y=(1,2); @z=(1); foo(@x,@ +y,@z);" 321

        doesnt prevent me from providing three arrays there, (or frankly almost any other kind of object), but it does prevent me from passing my values elegantly from an array, and all kinds of other standard stuff. So I dont actually get the type of protection you suggest I do, while at the same time losing all the power and flexibility of perls variadic argument passing.

        Basically operating under the misconception that prototypes are the same as function signatures is going to bite you and the users of your code. To paraphrase tilly on this subject if you know all this and still want to code that way then I probably dont want to work with you. Its most likely that our mental model of how perl should behave are so different that we would just end up tripping over each others code. I certainly would go mad with frustration if you prototyped your subs (with a few very specific exceptions).

        When to use Prototypes? has a bunch of links to discussion on this subject. But the general consensus of those who understand prototypes in perl well, seems to be that they are generally a bad idea, and should only be used iff you understand exactly why it is that they are a bad idea, and still can justify it. (Such as to provide map style interfaces.)


        ---
        demerphq

          First they ignore you, then they laugh at you, then they fight you, then you win.
          -- Gandhi


Re: sprintf format parameter required
by Abigail-II (Bishop) on Oct 31, 2003 at 10:05 UTC
    The reason is that sprintf can be more pedantic about its arguments than printf. The first argument of sprintf is always a format, therefore, it has $@ as prototype, putting the first argument of sprintf in scalar context.

    printf is more difficult, as it has an optional first argument: a filehandle. This is a non-expressable prototype which makes that printf @array works, there's no way for Perl to force scalar context on the first argument.

    Abigail

Re: sprintf format parameter required
by Roger (Parson) on Oct 31, 2003 at 06:22 UTC
    sprintf is not the same as printf in perl. It emulates the way how C sprintf behave. The workaround is just to supply element 0 (the format string) in the list to sprintf as the first argument, and the rest of the elements in the list as a slice...
    @arghs = ( "We see '%s' and '%s'\n", 'wowza', 'foo' ); $s = sprintf $arghs[0], @arghs[1..$#arghs]; print "$s\n";
    And the output is -
    We see 'wowza' and 'foo'
Re: sprintf format parameter required
by BrowserUk (Patriarch) on Oct 31, 2003 at 06:14 UTC

    Total mis-information withdrawn to prevent confusion. I was thinking C not perl!