Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses

Way of the Spinner (repost)

by !unlike (Beadle)
on Jan 17, 2003 at 11:49 UTC ( #227652=perlquestion: print w/replies, xml ) Need Help??

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

For those of a nervous disposition, reasurance your Perl code is actually processing something is a good thing. When writing code executed from the command line a spinner is a popular choice of providing this reassurance. When I first thought about doing this (many moons ago) I produced the following code:
my $i = 0; my @spinner = ("|\r", "/\r", "-\r", "\\\r"); while (<>) { # or any other looping structure print STDERR $spinner[$i]; $i = $i++ % @spinner; # do stuff }
After a while, and much learning, I realised I could shorten this to print STDERR $spinner[$i++ % @spinner]. I then went away happy thinking that was the best anyone could do. Until the other day. Out of the blue the following code "popped" into my head:
sub spinner { my $i; my @chars = defined @_ ? @_ : ("|\r", "/\r", "-\r", "\\\r"); return sub { "$chars[$i++ % @chars]" }; } my $spinner = spinner(); while (<>) { print STDERR $spinner->(); # do stuff }
I see this as an improvement on my last attempt for the following reasons:
1) It's more modular so I can have multiple spinners within one script, all giving different output (not sure when you'd want this mind!)
2) proves I have at least got a reasonable grip on how closures work
3) it is only fractionally slower than just printing $spinner[$i++ % @spinner]
I'd be interested in anyone elses thoughts on this code and any suggestions on how to do this differently/"better"/whatever

"The price if ignorance, which is of course that you must learn from those with knowledge." Scorates (paraphrased)

Replies are listed 'Best First'.
Re: Way of the Spinner (repost)
by Callum (Chaplain) on Jan 17, 2003 at 12:04 UTC
    There has been some discussion of this both recently and in the past, in particular you may want to look at the Tie::Cycle module.
Re: Way of the Spinner (repost)
by demerphq (Chancellor) on Jan 17, 2003 at 16:23 UTC
    My personal favorite (which is basically a cleaner implementation of what you have above) is
    use Tie::Cycle; tie my $spinner, 'Tie::Cycle', [map "\b$_",qw(\ | / -)]; print $spinner while (1);
    Please do a super search on "spinner". You will find a variety of solutions. :-)

    Its your call as to whether yours or mine or anyone elses is better. Probably depends on the circumstances. IMO however its hard to improve on the Tie::Cycle version, if only because it generalizes the entire thing to about as abstract a level as can be done, which of course maximizes the possibility of code reuse.


    --- demerphq
    my friends call me, usually because I'm late....

      Or if, like most geeks, you have an aversion to a visible tie, you can make the interface much cleaner with:
      use Attribute::Handlers; use Tie::Cycle; sub UNIVERSAL::Spinner : ATTR(SCALAR) { tie ${$_[2]}, 'Tie::Cycle', [map $_."\b" x length, @{$_[4]||[qw(\ | / -)]}] }

      And then whip up custom spinners very elegantly, as the need arises:

      $|=1; my $spinner : Spinner; print $spinner for (1..10000); my $morse_blinker : Spinner( qw(! : .) ); print $morse_blinker for (1..10000); my $wobbler : Spinner( "\)", "\(" ); print $wobbler for (1..10000); my $flexer : Spinner( '< ', ' >' ); print $flexer for (1..10000);
        TheDamian that was exactly what I ultimately wanted to achieve but had no idea how. A definate ++ to you. The Attribute::Handlers is a new module to me so I'll go and look it up.


        "The price if ignorance, which is of course that you must learn from those who know." Scorates (paraphrased)

        Although I admit until now ive been one of those geeks that doesnt mind the explicit Tie.


        --- demerphq
        my friends call me, usually because I'm late....

      Any way to keep this from spinning like a top?? Tried it out and on my 2.4GHZ system, it is almost a white circle.


        You can use the select() function to do short sleeps. If your system doesn't support a four-argument select(), you can use the Time::HiRes module. For the select function, the last argument reflects the time to sleep. This comes right out of O'Reilly's Perl Cookbook:
        while (<>) { select(undef, undef, undef, 0.25); print; }
        Using Time::HiRes, we'd write it as:
        use Time::HiRes qw(sleep); while (<>) { sleep(0.25); print; }
        Funny thing is that I used nearly the same method of making a spinner a while back...and I had the exact same question about slowing it down. Hope this helps!
        Funny you ask that. My origianl version was something like the following (adjust $delay according to the speed of your system)
        use Tie::Cycle; my $delay=100; tie my $spinner, 'Tie::Cycle', [map {("\b$_") x $delay} qw(\ | / -)];
        which is an approach I prefer to sleeping, as normally you put a spinner in a time sensitive loop so that you know its doing something... Also when ive used this the actual work happening in the loop is complicated (ie slow enough) that the spinner turns at a reasonable speed. In fact from a diagnostic POV its probably better to let it spin freely with no delay at all. That way you can observe the underlying task slow down or speed up (for whatever reason). For example you might use it when reading a file into a hash. When the hash starts having to resize itself you can observe the spinner stop briefly. Or when doing a massive DB update you can see the load on your network and DB server....


        --- demerphq
        my friends call me, usually because I'm late....

Re: Way of the Spinner (repost)
by BrowserUk (Patriarch) on Jan 17, 2003 at 16:55 UTC

    A slight variation. Use a string and substr rather than an array. I also use a backspace rather than \r so that you can hang the spinner on the end of a line with out having to reprint the whole line each time. Stops the whole line flickering.

    #! perl -sw use strict; $|++; sub spinner{ my ($s, $n) = (shift,0); return sub{ substr( $s, $n++ % length $s,1) . "\cH"; } } my $spinner = spinner '-\|/'; print "Working "; print $spinner->() for 1 .. 10000;

    Examine what is said, not who speaks.

    The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

      #!/usr/bin/perl -w use strict; sub make_spinner{ my $s = shift; my $i = 0; my $l = length $s; return sub{ unpack("x".($i++ % $l) . "A1", $s) . "\cH"; } } my $spinner = make_spinner '-\|/'; $|++; print "Working "; print $spinner->() for 1 .. 10000; print "\n";

      Makeshifts last the longest.


      I have to admit that while this solution is ok when the spinner is a single char I still prefer the other approach as being more flexible

      use Tie::Cycle; tie my $spinner, 'Tie::Cycle', [map "\b$_",qw(> -> --> ---> ----> ---> + --> -> >)]; print $spinner while (1);
      Aint animation fun!


      --- demerphq
      my friends call me, usually because I'm late....

Re: Way of the Spinner (repost)
by Anonymous Monk on Jan 20, 2003 at 18:57 UTC

    Everyone seem to have focused so much on making a thing spinn, that they haven't discovered the bug in your code. Take this as a good thing. ;)

    Anyhow, the bug is that @_ always is defined in your subroutine. (The exception would be if someone called it with &spinner; from a place where @_ wasn't defined.) That in its turn makes @chars stay empty if called without arguments, resulting in a modulus by zero error in the closure.

    There's also another issue with the closure, and that is that it quotes its return value. There's absolutely no reason for that. In fact, if spinner was abused to hold objects then it would break. Do not quote when you don't need to!

    In the spirit of TMTOWTDI; the closure can be rewritten to   sub { push @chars, shift @chars; $chars[0] } Not that I recommend that. :)


Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://227652]
Approved by tye
Front-paged by tye
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others chanting in the Monastery: (3)
As of 2023-06-07 16:58 GMT
Find Nodes?
    Voting Booth?
    How often do you go to conferences?

    Results (29 votes). Check out past polls.