Новый взгляд на привычные, казалось бы, вещи. Речь пойдёт об тернарном условном операторе.
опубликован: 2024-08-04 22:57
последняя редакция: 2024-08-07 13:51

Красивая эмуляция конструкции вида switch-case

Вначале напомню, что в Перле конструкция вида if - else - endif может быть заменена сокращённой записью. Например, выражение:
if ($x >= 0) {
    print 'positive';
}
else {
    print 'negative';
}

плюс-минус эквивалентно записи:
print $x >= 0 ? 'postitve' : 'negative';

Перловики сплошь и рядом вставляют подобные конструкции в свои программы, поскольку очень удобно для коротких выражений и не хочется развешивать "лапшу".
С другой стороны, в перле отсутствуют штатный способ создать конструкцию наподобие switch - case. Можно заменить кончено на if - elsif - elsif - ..., но получается немного коряво. По крайней мере, мне кажется что коряво.
И вот, на днях, просматривая какой-то западный ролик на ютубе с одной перловой конференции, заметил использование классной конструкции для switch - case, с использованием упомянутого условного тернарного оператора. Уже поделился находкой где только можно, приведу и здесь. Наслаждайтесь:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';

my $x = 2;

say
!$x      ? 'zero'
: $x == 1 ? 'one'
: $x == 2 ? 'two'
: $x == 3 ? 'three'
:           'too many'
;

Конечно, в конечном счёте всё элементарно, но почему-то не приходило в голову. Видимо мы зашорены раз и навсегда усвоенными шаблонами..

Продолжение истории
В телеграм-канале Modern::Perl прошло плодотворное обсуждение различных вариантов эмуляции switch-case в Перле. Помимо приведённой в этой заметке конструкции, было предложено ещё несколько вариантов, а заодно составлен тестовый бенчмарк для каждого из этих вариантов. Листинг теста и результаты на моём рабочем компе привожу ниже.
#!/usr/bin/perl
use strict;
use warnings;
use v5.10;
use feature 'say';
use Benchmark qw(:all);


my $sparam = 1;

cmpthese(100_000, +{
    "?:" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        say
        !$x      ? 'zero'
        : $x == 1 ? 'one'
        : $x == 2 ? 'two'
        : $x == 3 ? 'three'
        :           'too many'
        ;
    },

    "&& ||" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        say !$x      && 'zero'
        ||   $x == 1 && 'one'
        ||   $x == 2 && 'two'
        ||   $x == 3 && 'three'
        ||              'too many'
        ;

    },

    "and or" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        say(!$x      and 'zero'
        or   $x == 1 and 'one'
        or   $x == 2 and 'two'
        or   $x == 3 and 'three'
        or               'too many'
        );

    },

    "elsif" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        say do {
            if   (  !$x  ) { 'zero'     }
            elsif($x == 1) { 'one'      }
            elsif($x == 2) { 'two'      }
            elsif($x == 3) { 'three'    }
            else           { 'too many' }
        };
    },

    "sub return if ..." => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        say((sub {
            return 'zero'     if     !$x;
            return 'one'      if 1 == $x;
            return 'two'      if 2 == $x;
            return 'three'    if 3 == $x;
            return 'too many';
        })->());

    },

    "given" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        use feature qw/switch/;
        #no warnings qw/deprecated::smartmatch/;

        say do {
            given($x) {
                'zero'  when !$_;
                'one'   when 1;
                'two'   when 2;
                'three' when 3;
                default { 'too many' }
            }
        };
    },

    "switch" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        use Switch '__';

        say((sub {
            switch($x) {
                case !__ { return 'zero'     }
                case 1   { return 'one'      }
                case 2   { return 'two'      }
                case 3   { return 'three'    }
                else     { return 'too many' }
            }
        })->());

    },

    "{}" => sub {
        state $init;
        $init//=srand($sparam);
        my $x = int rand 5;

        # Тут выражения вычисляются все, а не последовательно,
        # поэтому раскрывают частный случай
        say +{
            0 => 'zero',
            1 => 'one',
            2 => 'two',
            3 => 'three',
        }->{$x} // 'too many';

    },
});

Ну, и мои результаты, если интересно
(warning: too few iterations for a reliable count)
                      Rate switch sub return if ...   {} and or elsif given && ||   ?:
switch             54645/s     --              -78% -81%   -82%  -83%  -86%  -87% -88%
sub return if ... 250000/s   357%                -- -12%   -17%  -20%  -37%  -42% -45%
{}                285714/s   423%               14%   --    -6%   -9%  -29%  -34% -37%
and or            303030/s   455%               21%   6%     --   -3%  -24%  -30% -33%
elsif             312500/s   472%               25%   9%     3%    --  -22%  -28% -31%
given             400000/s   632%               60%  40%    32%   28%    --   -8% -12%
&& ||             434783/s   696%               74%  52%    43%   39%    9%    --  -4%
?:                454545/s   732%               82%  59%    50%   45%   14%    5%   --