Day 8: .comb your constraints

We have hit the point where the previous gifts are useful for the current gifts. Today is a dual set: the comb method and the idea of constraints.

Similar to the static types previous defined, constraints allow fine control in writing subroutines and methods. In many other programming languages, you have to pass parameters into a subroutine and then validate the input that comes in. With constraints, you can do the validation right in the declaration.

Take this basic example. If the integer is even, I don’t want to deal with this subroutine. In Perl 5, it would be written something similar to this:

sub very_odd
{
    my $odd = shift;
    unless ($odd % 2)
    {
        return undef;
    }
    # Process the odd number here.
}

In Perl 6, this can be simplified thusly:

sub very_odd(Int $odd where {$odd % 2})
{
    # Process the odd number here
}

If you attempt to call very_odd with an even number, you will get an error. Do not fret though: you can use the multi sub functionality to give even numbers a chance to shine…maybe. ;)

multi sub very_odd(Int $odd where {$odd % 2})
{
    # Process the odd number here
}
multi sub very_odd(Int $odd) { return Bool::False; }

These constraints can be useful when paired with the .comb method. What exactly is .comb? For those that brush their own hair, you generally use a comb to get the strands you want and settle them somewhere on your head. For those that like using .split, it’s the opposite: instead of separating a Str by what you don’t want, you separate it by what you do. This simple piece of code should demonstrate that:

say "Perl 6 Advent".comb(/<alpha>/).join('|');
say "Perl 6 Advent".comb(/<alpha>+/).join('|');

Regex patterns will most likely be covered another day, but a quick preview won’t hurt. The first line will print P|e|r|l|A|d|v|e|n|t: it gets every alphabetic character and puts it into a temporary array. It is then joined together with the pipe character. The second line is similar, only it grabs as many alphabetic characters as it can, resulting in Perl|Advent.

The power of .comb is much more, however. Once you have combed out what you wanted, you can manipulate the strands. If you have a basic string of ASCII hex characters, you can use the hyperoperators to change each piece into the ASCII equivalent!

say "5065726C36".comb(/<xdigit>**2/)».fmt("0x%s")».chr
# Outputs "Perl6"

For those intimidated by that, you can find also use the .map method.

say "5065726C36".comb(/<xdigit>**2/).map: { chr '0x' ~ $_ } ;
# Outputs "Perl6"

Remember, this is Perl. There is more than one way to do it. ☺

With all of the gifts that have been presented today, I now have a challenge for all of you. With the assistance of Kyle Hasselbacher, I was able to make a decent version of the ancient Caesar Cipher using constraints, .comb, and the old style .map.

use v6;

sub rotate_one( Str $c where { $c.chars == 1 }, Int $n ) {
    return $c if $c !~~ /<alpha>/;
    my $out = $c.ord + $n;
    $out -= 26 if $out > ($c eq $c.uc ?? 'Z'.ord !! 'z'.ord);
    return $out.chr;
}

sub rotate(Str $s where {$s.chars}, Int $n = 3)
{
    return ($s.comb.map: { rotate_one( $_, $n % 26 ) }).join( '' );
}

die "Usage:\n$*PROGRAM_NAME string number_for_rotations" unless @*ARGS == 2;

my Str $mess = @*ARGS[0];
my Int $rotate = @*ARGS[1].Int;

say qq|"$mess" rotated $rotate characters gives "{rotate($mess,$rotate)}".|;

I would like to see how the rest of you can code this algorithm using Perl 6 and the gifts so far. After all, the language can only get better with more usage.

11 thoughts on “Day 8: .comb your constraints

  1. This might be cheating, since it doesn’t use items from the current list of ‘presents’. ‘trans’ can be very powerful for this type of thing.

    use v6;

    my Str $mess = @*ARGS[0];
    my Int $rotate = (@*ARGS[1] // 0).Int % 26;

    my $trans = chr(‘a’.ord+$rotate) ~ ‘..za..’ ~ chr(‘a’.ord+($rotate-1)%26);
    $trans ~= $trans.uc;

    say “$mess rotated $rotate characters gives {$mess.=trans(‘a..zA..Z’ => $trans)}.”;

    1. Perl has a long tradition of allowing programmers to be productive no matter what their skill level, from beginner to wizard. In this case, perl6lurker is using mostly beginner to intermediate concepts, but combining them in a more advanced way, because it is, well, “neat”.

      That said, Perl 6 has significantly raised the upper limit of supported wizardry (by design), so I expect there will be a number of times when I come across code that has a rather koan-like demeanor and effect. :-)

  2. This is pretty much like the example. I just moved the alpha test to rotate and checked for overflow a different way:

    use v6;

    sub rotate_one( Str $c where { $c.chars == 1 }, Int $n ) {
    my $a = $c.ord;
    $a -= 26 if ($a +& 0x1F) + $n > 26;
    return ($a + $n).chr;
    }

    sub rotate(Str $s where { $s.chars }, Int $n = 3)
    {
    return ($s.comb.map:
    { $_ = rotate_one( $_, $n) if //; $_ } ).join( ” );
    }

    my Str $mess = @*ARGS[0];
    my Int $rotate = @*ARGS[1].Int;

    say qq|”$mess” rotated $rotate characters gives “{rotate($mess,$rotate)}”.|;

    I know I’m using a “magic number” (0x1A) but there’s already a magic number in the code (26).

  3. Posting mangled my code: The line reading { $_ = rotate_one( $_, $n) if //; $_ } ).join( ” ); should be:
    { $_ = rotate_one( $_, $n) if //; $_ } ).join( ” );

  4. Ok, it’s the angle brackets. The comment software removed angle brackets and anything between them.

Leave a reply to Lee Wenzbauer Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.