<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Belden</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/belden/" />
    <link rel="self" type="application/atom+xml" href="http://blogs.perl.org/users/belden/atom.xml" />
    <id>tag:blogs.perl.org,2009-11-03:/users/belden//1196</id>
    <updated>2013-02-15T15:19:11Z</updated>
    
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type Pro 4.38</generator>

<entry>
    <title>Prototype changes across Perl release boundaries</title>
    <link rel="alternate" type="text/html" href="http://blogs.perl.org/users/belden/2013/02/prototype-changes-across-perl-release-boundaries.html" />
    <id>tag:blogs.perl.org,2013:/users/belden//1196.4291</id>

    <published>2013-02-15T14:52:22Z</published>
    <updated>2013-02-15T15:19:11Z</updated>

    <summary>At a previous employer, one of the things I ran into from time to time was the need to get the keys of a hash back out in the same order they were provided. Yes, Tie::IxHash exists to solve this...</summary>
    <author>
        <name>Belden</name>
        <uri>http://twitter.com/belden</uri>
    </author>
    
    
    <content type="html" xml:lang="en" xml:base="http://blogs.perl.org/users/belden/">
        <![CDATA[<p>At a previous employer, one of the things I ran into from time to time was the need to get the keys of a hash back out in the same order they were provided. Yes, <a href="https://metacpan.org/module/Tie::IxHash">Tie::IxHash</a> exists to solve this problem: but it always felt like a heavy-weight solution to a simple problem. Why bother adding a new dependency to the system when I could just swap my %hash out for a @hashlike array?</p>

<pre><code>
    my @hashlike = (1..10);    # generally the return value of a subroutine

<p>    while (my ($k, $v) = splice @hashlike, 0, 2) {<br />
        ...  # do something with $k, $v<br />
    }<br />
</code></pre><br />
</p>]]>
        <![CDATA[<p>This has the possibly undesired side effect of destroying @hashlike; by the time the loop is done, @hashlike is empty. As far as processing tuples in some orderly fashion it works pretty well though.</p>

<p>Recently while tidying up <a href="https://metacpan.org/module/Hash::MostUtils">Hash::MostUtils</a> for CPAN, I realized that the functions lkeys() and lvalues() [which return the "keys" and "values" of a list, respectively] were missing a companion: leach(). Since I work best with tests to guide me, I wrote one for leach():</p>

<pre><code>
    my @hashlike = (1..10);
    my @collected;

<p>    while (my ($k, $v) = leach @hashlike) {<br />
        push @collected, $k, $v;<br />
    }</p>

<p>    is_deeply( \@collected, [1..10], 'collected all tuples' );<br />
    is_deeply( \@hashlike, \@collected, 'did not destroy @hashlike' );<br />
</code></pre></p>

<p>And here's what leach() looks like:</p>

<pre><code>
    {
        my %end;

<p>        sub leach (+) {<br />
            my $data = shift;</p>

<p>            my $ident = "$data";<br />
            my $n = 2;</p>

<p>            return () if $#{$data} < ($end{$ident} || 0);</p>

<p>            $end{$ident} += $n;<br />
            return @{$data}[$end{$ident} - $n .. $end{$ident} - 1];<br />
        }<br />
    }<br />
</code></pre></p>

<p>The protoype '(+)' is the interesting bit of this implementation; it comes straight from splice():</p>

<pre>
    blyman@blyman-lt:~$ perl -de 1
      DB<1> p prototype 'CORE::splice'
    0  '+;$$@'
</pre>

<p>I guessed that the '+' meant something like, 'Make this thingy into a reference and let me deal with that reference', and <a href="https://metacpan.org/module/RJBS/perl-5.16.2/pod/perlsub.pod#Prototypes">indeed that's how it behaves</a>. The next morning I pushed my repo from my laptop, pulled on my desktop, and went through the familiar old steps to prepare a module for CPAN: run tests, make a tarball, upload it.</p>

<pre>
    blyman@skretting:perl-hash-mostutils$ prove t/leach.t 
    t/leach.t .. Illegal character in prototype for Hash::MostUtils::leach : + at lib/Hash/MostUtils.pm line 110.
    Malformed prototype for Hash::MostUtils::leach : + at t/leach.t line 110.
    # Looks like your test exited with 2 before it could output anything.
</pre>

<p>Yikes! Sure enough, asking the debugger what my desktop's notion of splice()'s prototype is gave a very different answer:</p>

<pre>
    blyman@skretting:~$ perl -de 1
      DB<1> p prototype 'CORE::splice'
    0  '\@;$$@'
</pre>

<p>The versions of perl between my desktop and my laptop are different: my laptop has system perl 5.14 installed, where (+) is the correct prototype for leach() to use, whereas my desktop has system perl 5.10 installed, where (\@) is the correct prototype to use. So there's the problem; now what's the solution? Somehow I need to apply a different prototype to leach() depending on the version of perl that I can detect? I tried to imagine my reaction if some colleague were to introduce this to our codebase:</p>

<pre><code>
    sub _leach { ...basically what you saw earlier ... }

<p>    if ($] > $some_cutoff_version) {<br />
        eval 'sub leach (+) { goto &_leach }';<br />
    } else {<br />
        eval 'sub leach (\\@) { goto &_leach }';<br />
    }<br />
</code></pre></p>

<p>"Ugh," I heard myself say. "Why not use Scalar::Util::set_prototype instead?"</p>

<pre><code>
    use Scalar::Util qw(set_prototype);

<p>    sub leach { ... }</p>

<p>    if ($] > $some_cutoff_version) {<br />
        set_prototype \&leach, '+';<br />
    } else {<br />
        set_prototype \&leach, '\\@';<br />
    }<br />
</code></pre></p>

<p>This is a little better, but not all versions of Scalar::Util provide set_prototype(). [Granted, you have to be on a pretty old system perl not to have this function available.]</p>

<p>Since 'use' is just a 'require' then 'import', we can write our own module to choose between two separate modules, and re-export functions:</p>

<pre><code>
    use strict;
    use warnings;
    package Hash::MostUtils::leach;
    use base qw(Exporter);

<p>    our @EXPORT = qw(leach);</p>

<p>    sub import {<br />
        my ($class) = @_;</p>

<p>        if ($] > $some_cutoff_version) {<br />
            require Hash::MostUtils::leach::prototype_is_plus;<br />
            Hash::MostUtils::leach::prototype_is_plus->import;<br />
        } else {<br />
            require Hash::MostUtils::leach::prototype_is_backslash_at;<br />
            Hash::MostUtils::leach::prototype_is_backslash_at->import;<br />
        }<br />
    }</p>

<p>    1;<br />
</code></pre></p>

<p>Now we can just implement and @EXPORT leach() in each of those two modules:</p>

<pre><code>
    use strict;
    use warnings;
    package Hash::MostUtils::leach::prototype_is_plus;
    use base qw(Exporter);

<p>    our @EXPORT = qw(leach);</p>

<p>    sub leach (+) { ...what you saw before... }</p>

<p>    1;<br />
</code></pre></p>

<p>Err, and, what - we'll just copy-paste that module, change the package name, filename, and prototype to make the other version? Again, me-the-coworker didn't like that. It's fair to gather similar functionality together in Hash::MostUtils::leach.pm, like so:</p>

<pre><code>
    use strict;
    use warnings;
    package Hash::MostUtils::leach;
    use base qw(Exporter);

<p>    our @EXPORT = qw(leach);</p>

<p>    sub import { ...you've already seen this... }</p>

<p>    sub _leach { ...and you've already seen this... }</p>

<p>    1;<br />
</code></pre></p>

<p>And now both the ::prototype_is_plus and ::_prototype_is_backslash_at classes write themselves:</p>

<pre><code>
    use strict;
    use warnings;
    package Hash::MostUtils::leach::prototype_is_plus;
    use base qw(Exporter);

<p>    our @EXPORT = qw(leach);</p>

<p>    require Hash::MostUtils::leach;</p>

<p>    sub leach (+) { goto &Hash::MostUtils::leach::_leach }</p>

<p>    1;<br />
</code></pre></p>

<p>(You can imagine what ::_prototype_is_backslash_at.pm looks like.)</p>

<p>All that was left at this point was figuring out what the $some_cutoff_version is that I kept glossing over in Hash::MostUtils::leach->import(). <a href="https://metacpan.org/author/LOGIE">Logan Bell</a> gave me a painless introduction to <a href="http://perlbrew.pl">perlbrew</a>, and I found the cutoff point: for $] >= 5.013000, I need to use '(+)'; for versions lower, I need to use '(\@)'.</p>

<p>Well, not quite "all that was left." I decided to write this up, and in the course of doing so decided that maybe someone else is going to face this same problem - so I wrote <a href="https://metacpan.org/module/provide">provide.pm</a> to gloss away the small details inside Hash::MostUtils::leach->import. Here's what that module looks like now:</p>

<pre><code>
    use strict;
    use warnings;
    package Hash::MostUtils::leach;

<p>    use provide (<br />
        if => ge => 5.013000 => 'Hash::MostUtils::leach::v5_013',<br />
        else                 => 'Hash::MostUtils::leach::v5_008',<br />
    );</p>

<p>    {<br />
        my %end;</p>

<p>        sub _leach {<br />
            my $data = shift;</p>

<p>            my $ident = "$data";<br />
            my $n = 2;</p>

<p>            return () if $#{$data} < ($end{$ident} || 0);</p>

<p>            $end{$ident} += $n;<br />
            return @{$data}[$end{$ident} - $n .. $end{$ident} - 1];<br />
        }<br />
    }</p>

<p>    1;<br />
</code></pre></p>]]>
    </content>
</entry>

</feed>
