waffle

Waffle was a weblog that ran for nine years and five days from 2003 to 2012.
The last post has been written and comments will be closed by the end of March 2012.
The author of Waffle, some guy in Sweden, also occasionally writes stmts.net.

(If anything will ever succeed or revive Waffle, it will be announced in this location, and in the feeds.)

Perl 6 Feature Solving a Real Problem I

Perl 6 is finally here, for some value of here and most values of finally. I’ve been following Perl 6 in some capacity since I was using the Windows XP beta (I’m not kidding), but there’s a highly successful Advent Calendar and a promise to deliver a useful subset of an implementation within six months. What’s more, I’ve actually written some code in it during the past week. Perl 6 has started its transition from a theoretical ha-ha, Apple Tablet, Duke Nukem Forever-ish exercise to an actual hacksaw. Half the handle’s missing, but you go to war with the army you have.

If Perl 6 is a real tool, here, then, is a real world problem. Programs need to be translated. Actually, first they need to be internationalized, which means that you stop to think whether the entire world actually uses Fahrenheit, start their weeks on Sundays or know intuitively whether facepalm constitutes an indication of error, and then they need to be localized, which means that you provide alternate content where appropriate.

At its core, the way translation is done is to simply swap strings. There’s more to it owing to language rules and inflections, but not much; actual translation systems use a combination of formatting strings and a careful choice of strings to solve the problem. At this point, one problem is that of supplying the key in this magical lookup table.

Today’s combination of Perl 6 features solves this neatly:

use v6;

our %_Strings = { "SEVEN" => "sju" };

role _[$localizationkey] {
    method lockey {
        $localizationkey
    }
}

sub prefix:<€>(_ $loc --> Str) {
    my $localized = %_Strings{$loc.lockey};
    return $localized.defined ?? $localized !! "<<unknown: {$loc}>>"; # the ?: operator with funny syntax
}

sub sayboth(Str $string) {
    say "Default: {$string}";
    say "Localized: {€$string}";
}

sayboth "Seven" but _["SEVEN"];
sayboth "Six" but _["SIX"]; # no such entry in the localization hash

Let’s take it from the top. v6 is the version indicator of a Perl 6 program. %_Strings is a bog standard hash, defined at the top scope.

A role is a new way in Perl 6 to stitch on functionality. A role can take parameters that can be both types and values. The _ role is parametrized to swallow a localization key. (_ is traditionally used for brevity in localization macros.) Next, there’s a custom operator , providing a localized version of the string by looking in the globally scoped hash; it takes a value fulfilling the _ role, since such values can provide a key for the hash. (I chose because it’s unused, short and suitably witty and explanatory when contrasted to $, the Perl sigil for a scalar.)

The role is now used by the strings; Perl 6 provides a way to stitch on role implementations onto individual values. In this way you could also specify fallback values to enums. In this way, the value can carry around extra metadata without the mental weight of a subclass that the data has to be extracted from. Methods that take strings can take strings and methods that want localizable data can take values following the role.

What’s more, with another Perl 6 feature, implementing mandatory localization becomes easy:

# $_, as always in Perl, means "the value in question";
# ~~ means "matching", and _ is the name of the role:
subset LocStr of Str where { $_ ~~ _ } 
class Button {
  has LocStr $.localizedLabel; # an instance variable
  ...
}

This uses Perl 6 subsets, where you can conjure up names for existing values of the right type that conforms to a few extra checks. “Duck typing labels” might be a good name. Let’s apply it to our previous example:

subset LocStr of Str where { $_ ~~ _ }

sub sayboth(LocStr $string) {
    say "Default: {$string}";
    say "Localized: {€$string}";
}

sayboth "Seven" but _["SEVEN"];
sayboth "Six" but _["SIX"];
sayboth "Five"; # this won't work; just a normal string is not sufficient

The more I look at it, the more I think Perl 6 has exactly the right idea for what a type system and generics are supposed to do: allow you the creativity you need, including the tools to keep you in check.

All of this code can be run today with Rakudo.

« Newer posts · Older posts »