1

Trying to understand a piece of code in Perl as follows

    my @a = qw(A B C D E F F A K O N D);
    my @b = qw(S F S T F F S);

$s=sprintf "%s %s %d %d:%d:%d %d: %s" ,   sub { ($a[$_[6]], $b[$_[4]], $_[3], $_[2], $_[1], $_[0], $_[5]+1900) }->(localtime), $s;

This code is in a method in a package which has been called with three arguments, arrays a and b have been defined in the method above in this code and s is a defined string.

All I can understand is that it is an anonymous subroutine which is expected to return multiple values due to localtime function but I just cannot understand the mechanism that this code follows. I assume I have given enough information to ask this question if not let me know.

3
  • An example of the values in @a and @b and $s would help. I can tell you what it's doing, but not why. Also, how is this used? On its own the return value is lost. Is this assigned to something, or is it the return value of another function? Commented Sep 25, 2017 at 14:06
  • I have added a few more lines to make it more meaningful , hope that helps Commented Sep 25, 2017 at 14:15
  • 1
    edited.thanks for spotting Commented Sep 25, 2017 at 14:20

1 Answer 1

3

The syntax is an unusual way of encapsulating the modification of several values at once inside of one statement.

This code is probably used at the end of a subroutine, as it returns a list of values. The real code might look like this.

sub foo {
    my @a = qw(Sun Mon Tue Wed Thu Fri Sat);
    my @b = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);

    my $s = q{foobar};

    #                  wday         month    mday   hour   min    sec    year
    return sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
        ->(localtime), $s;
}

Let's go through that. I added the return to make it more clear what's happening.

There is an anonymous subroutine sub { ... }, which is defined and directly being called sub { ... }->(). This is like a lambda function. The arrow operator -> in this form is used for calling a code reference. It differs from the direct object syntax (e.g. $foo->frobnicate()) in that there is no method name after the arrow.

my $code = sub { print "@_" };
$code->(1, 2, 3);

This will output:

1, 2, 3

The localtime return value in list context is passed. That's a bunch of values for the current time, as mentioned in the documentation.

#     0    1    2     3     4    5     6     7     8
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                                            localtime(time);

Without arguments, localtime implicitly uses time as its argument. Inside of the anonymous subroutine, those values end up in @_, which is the argument list of the sub. So $_[0] is the seconds part of the current local date.

Then there is @a and @b, which are really badly chosen names. If it's supposed to be short, I'd at least have called them @d and @m for weekdays and months.

The anonymous sub returns a list of values. It's the current weekday, as looked up in the @a array, where Sunday is index 0, the current month as looked in @b, the day, hour, minute and second, and the year as a four-digit number (hence the + 1900).

The overall function foo returns this list, and another value $s as the last element of its return value list.

If you were to print all of that, you would get:

print join q{ }, foo();

__END__
Mon Sep 25 16 10 33 2017 foobar

In the context of the sprintf, this becomes more clear. It's likely for adding a timestamp to a log entry.

sub log {
    my $s = shift;

    my @a = qw(Sun Mon Tue Wed Thu Fri Sat);
    my @b = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);

    #             0  1  2  3  4  5  6   7
    $s = sprintf "%s %s %d %d:%d:%d %d: %s",
        #        wday        month        mday   hour   min    sec    year
        #        0           1            2      3      4      5      6
        sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
        #              7
        ->(localtime), $s;

    return $s;
}

print &log('stuff happened'); # we need the & because CORE::log

I've marked each value with the position of the argument inside the sprintf pattern. $s seems to be the log message, which is replaced by the new string with the timestamp, and itself as the last argument.

However, this is a very unusual implementation, given that localtime will return the exact same thing in scalar context.

print &log("stuff happened\n");
print ''.localtime . ": stuff happened\n";

__END__
Mon Sep 25 16:24:40 2017: stuff happened
Mon Sep 25 16:24:40 2017: stuff happened

It's the return value of ctime(3). I first thought maybe it's a reimplementation because on the target system that kind of timestamp does not exist, but the localtime docs also say that this is not system-dependent.

The format of this scalar value is not locale-dependent but built into Perl. For GMT instead of local time use the gmtime builtin. See also the Time::Local module (for converting seconds, minutes, hours, and such back to the integer value returned by time), and the POSIX module's strftime and mktime functions.

The implementation with the sub is elegant, though not very Perl-ish. However, it's completely useless because Perl already brings this exact same format.

You could easily replace my log function with this.

sub log {
    my $s = shift;

    $s = localtime . " $s";

    return $s;
}

But then again, if @a and @b are maybe localized, this makes makes sense. You give them as letter, which is probably not your real code, but I could see this being used as for example translating it to German like this.

use utf8;

sub log {
    my $s = shift;

    my @a = qw(So Mo Di Mi Do Fr Sa);
    my @b = qw(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez);

    #             0  1  2  3  4  5  6   7
    $s = sprintf "%s %s %d %d:%d:%d %d: %s",
        #        wday        month        mday   hour   min    sec    year
        #        0           1            2      3      4      5      6
        sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
        #              7
        ->(localtime), $s;

    return $s;
}

print &log("stuff happened\n");

Now it makes a lot more sense.

Sign up to request clarification or add additional context in comments.

4 Comments

As far as I know -> is used for method calls with the caller as the first argument to the called function , so here how that work s? it is like sub{}->(localtime), I did not completely understand this syntax do have something that I can refer to understand this syntax ?
I saw your edit , thanks, so its like a function variable calling itself with some arguments function variable-->(arguments). That creates more questions if sub in above code had a name say func , would it have been correct to make a call like this sub func {} ; func->(localtime). Also in the above code why sub has not been called like this sub{}->(localtime()); since localtime is a function ? And how does variable $code in your edit hold a function as a value how does Perl understand it.
@Vicky you are mixing things up. It is a code reference that is constructed from an anonymous function. It's not assigned to a variable, but called directly. It will never be registered as a sub with a name in the current package, hence it's anonymous. A named sub inside of the scope of another function like you just showed is not valid syntax. You could create an actual sub with a name, then call it, but then you would not have the two arrays visible in the function. Regarding the parentheses, those are always optional in Perl. localtime is a builtin, not a function.
Thank you so much , that helps a lot !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.