Mojolicious Perl Style

    I want to describe the style of programming in Perl, which I aspire to, and which is mainly adopted from the modern web-framework Mojolicious , but probably a lot where similar is used. It seems to me that working out the right coding style is very important.

    Example 1:
    Single line methods.
    If each function argument is accessed only once, and the order of their application in the code corresponds to the order of the arguments passed, it is proposed to retrieve them using the standard shift function , which, if called without arguments, works by default with the @_ array in which all the arguments passed to the function, pops the first argument from the array and returns it.
    sub node { shift->tree->[0] }
    #
    sub parse { shift->_delegate(parse => shift) }
    #
    sub _tag { shift->new->tree(shift)->xml(shift) }
    

    Example 2:
    First we extract the first parameter, the name of the class, for example, we transfer all other arguments to another function and let it process them.
    sub tree { shift->_delegate(tree => @_) } 
    # т.е. может превратиться в это _delegate(tree => [], deep => 5) или это _delegate(tree => [], 5, 0) 
    sub log { shift->emit('message', lc shift, @_) }
    

    Example 3:
    Also for a one line method.
    Here, the same argument is accessed as much as 3 times, therefore, to access the argument, a direct access to the element of the array of arguments $ _ [0] is used.
    sub _instance { ref $_[0] ? $_[0] : $_[0]->singleton }
    #
    sub find { $_[0]->_collect(@{$_[0]->_css->select($_[1])}) }
    


    Example 4: You can
    declare a variable in a conditional single-line block if , else if or unless , and then if the condition is not met, the code will continue to execute and the new declared variable can then be used there.
    sub at {
      my $self = shift;
      return undef unless my $result = $self->_css->select_one(@_);
      return _tag($self, $result, $self->xml);
    }
    

    Example 5:
    Declaring a variable and then using it in a condition.
    return '' if (my $tree = $self->tree)->[0] ne 'tag';
    return $tree->[1] unless $type;
    #
    return if !(my $expires = delete $session->{expires}) && $expiration;
    

    Example 6:
    A very useful Mojo :: EventEmitter module, by the way, is exactly the same in Node JS, it is also one of the key modules there, it performs the same functionality, only in my opinion in Mojolicious its code looks much simpler and shorter. You can use it in your projects that do not even use this framework. It must be made basic for its module, and with its help you can add methods for events, and inside some method you can call them using emit .
    package Cat;
    use Mojo::Base 'Mojo::EventEmitter';
    # Emit events
    sub poke {
      my $self = shift;
      $self->emit(roar => 3);
    }
    package main;
    # Subscribe to events
    my $tiger = Cat->new;
    $tiger->on(roar => sub {
      my ($tiger, $times) = @_;
      say 'RAWR!' for 1 .. $times;
    });
    $tiger->poke;
    

    Example 7:
    Repair a memory leak. For example, in the Mojo :: EventEmitter module, there is a once method that could have caused a memory leak if it hadn’t used the weaken function .
    If you have a link to a function that itself somehow uses the variable that refers to it (directly or indirectly), then weaken (Scalar :: Util) must be used , otherwise there will be a memory leak!
    sub once {
      my ($self, $name, $cb) = @_;
      weaken $self;
      my $wrapper;
      $wrapper = sub {
        $self->unsubscribe($name => $wrapper);
        $cb->(@_);
      };
      $self->on($name => $wrapper);
      weaken $wrapper;
      return $wrapper;
    }
    # или вот
    sub timeout {
      my $self = shift;
      #
      weaken $self;
      $self->{timer} = $reactor->timer($timeout => sub { $self->emit_safe('timeout')->close });
      return $self;
    }
    

    Example 8:
    Slice an array.
    $_ eq $child ? last : $i++ for @$parent[$i .. $#$parent];
    

    Example 9:
    You can perform the same action for several variables on the same line, specifying them separated by commas in a for loop .
    s/\%20/\+/g for $name, $value;
    

    Example 10:
    Assigning one value to several variables at once. In addition, it is very convenient to assign the value of a class variable or a hash value to a simple variable, which is written shorter and then used in a function or block, for example, it is easier to use it when dereferencing array or hash references without unnecessary curly braces.
    my $params = $self->{params} = [];
    return $params unless length $str;
    

    Example 11:
    If the code is simple from one expression in if , else if, or else blocks , then it is better to write both the condition and the block in one line.
    if ($params->[$i] eq $name) { splice @$params, $i, 2 }
    else { $i += 2 }
    

    Example 12:
    Using Mojo :: Base as the base for your module. Includes by default a number of pragmas (modules) such as strict , warnings , utf8 and Perl 5.10 innovations.
    Adds a has method to our class , a similar style from Moose, which you can use to create class variables in the following way.
    If you pass a hash to the constructor of the class, as in the last line of the example, then it initializes the variables of the object.
    package Foo;
    use Mojo::Base -base;
    has [qw(kept_alive local_address original_remote_address remote_port)];
     # сразу несколько не инициализированных переменных класса в одной строке
    has req => sub { Mojo::Message::Request->new };
    has max => 4; # только числа и строки можно указывать без анонимной функции
    has str => 'Hi!';
    has list => sub { [] }; # ссылка на массив только через анонимную функцию
    has dict => sub { {} };  # ссылка на хэш только через анонимную функцию
    has hint => <new({kept_alive =>$kept_alive, local_address => $local_address});
    

    Example 13:
    A base class method that must be overridden in a child.
    use Carp 'croak';
    sub server_write { croak 'Method "server_write" not implemented by subclass' }
    

    Example 14:
    Constants, several or one.
    use constant {
      PC_BASE => 36,
      PC_TMIN => 1,
      PC_TMAX => 26
    };
    use constant PC_BASE => 36;
    

    Example 15: A
    string containing a variable inside and double quotes is best written as follows.
    die qq{Unknown command "$name", maybe you need to install it?\n}
      unless $module;
    #
    sub quote {
      my $str = shift;
      $str =~ s/(["\\])/\\$1/g;
      return qq{"$str"};
    }
    

    Example 16:
    Creating a hash variable and then passing it to a function. It is better to create a large hash separately, and not directly in the anonymous argument, it’s more convenient.
    my $options = {
      domain => $self->cookie_domain,
      expires => $session->{expires},
      httponly => 1,
      path => $self->cookie_path,
      secure => $self->secure
    };
    $c->signed_cookie($self->cookie_name, $value, $options);
    

    Example 17:
    If a variable is created in a single-line block-condition and it is planned to use it in the body-block, then this will not work, it will be unavailable, therefore it is necessary to declare the variable before the block.
    In the second case, in the example, when using the created variable, curly brackets are used in the block; an example is also shown there of extracting the value from the hash by key, if possible the hash has not yet been created.
    // - operator that returns true if one of the expressions is defined, i.e. not equal to undef .
    my $new;
    unless ($new = $self->_class($old, $field)) { return !!defined $new }
    #
    if (my $code = ($tx->req->error // {})->{advice}) { $res->code($code) }
    #
    my $stash = {};
    if (my $sub = $tx->can('stash')) { ($stash, $tx) = ($tx->$sub, $tx->tx) }
    

    Example 18:
    Performing the same function with different arguments, where the values ​​of the array are simple words, it is better to use the standard qw function, you do not need to separate each element with a comma, so it is shorter and more beautiful.
    In the second case, in the example, the use of the qw function if too many elements of the array must be set manually, it is better to divide it into several lines, separate it with a comma and call this function on each line.
    $self->plugin($_)
      for qw(HeaderCondition DefaultHelpers TagHelpers EPLRenderer EPRenderer);
    #
    my %NORMALCASE = map { lc($_) => $_ } (
      qw(Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges),
      qw(Allow Authorization Cache-Control Connection Content-Disposition),
    # ...
    );
    # если необходимо использовать созданную переменную с блоке
    for my $name (qw(app flash param stash session url_for validation)) {
      $app->helper($name => sub { shift->$name(@_) });
    }
    

    Example 19:
    Such a condition construct is often used if the first is true and the second is false so that the second condition is not met if the first is false. In the second expression of the example, the variable $ class is created , if it is not empty, and the second expression is false, then we exit the function, if the second expression is true, then continue execution and in this code we can use this variable.
    return $self->emit(read => $chunk)
      unless $self->is_compressed && $self->auto_relax;
    #
    return unless (my $class = ref $self || $self) && $attrs;
    #
    return unless $self->cleanup && defined(my $path = $self->path);
    

    Example 20:
    Writing single-line expressions that don't even fit on two lines.
    warn
      qq/Problem loading URL "@{[$tx->req->url->to_abs]}". ($err->{message})\n/
      if $err && !$err->{code};
    #
    $app->helper($_ => $self->can("_$_"))
      for qw(accepts content content_for csrf_token current_route delay),
      qw(inactivity_timeout is_fresh url_with);
    #
    return $self->error(
      {message => 'Maximum buffer size exceeded', advice => 400})
      if $self->content->is_limit_exceeded;
    

    Example 21:
    Using eval to check for the existence of a module.
    eval "use Mojolicious::Lite; 1" or die $@;
    #
    use constant IPV6 => $ENV{MOJO_NO_IPV6}
      ? 0
      : eval 'use IO::Socket::IP 0.20 (); 1';
    

    Example 22:
    Adding a new method for a class, no strict and no warnings act only locally, in a function or block. It is also worth noting that symbolic links only work with global variables, which is what we need here.
    sub monkey_patch {
      my ($class, %patch) = @_;
      no strict 'refs'; # позволить использовать символические ссылки
      no warnings 'redefine'; # не выводим ворнинг, если происходит переопределение метода
      *{"${class}::$_"} = $patch{$_} for keys %patch;
    }
    

    Example 23:
    The ojo.pm module, created specifically to be used in the console, to write small cool single-line files. The -M option connects the module and the name of the framework is -Mojo. Some functions are collected there, in the import method, through the above function monkey_patch are added. And now, as shown at the end of the example, you can get the title of any site, for example, here we get the title of the official Mojolicious website or the post headers on the main habrahabr.ru.
    package ojo;
    #
    sub import {
    #
    monkey_patch $caller,
    # ...
    g => sub { _request($ua, 'GET',    @_) },
    # ...
    x => sub { Mojo::DOM->new(@_) };
    # из консоли, заголовок сайта
    perl -Mojo -E 'say g("mojolicio.us")->dom->at("title")->text'
    # заголовки постов на главной habrahabr.ru
    perl -Mojo -C -E 'g("habrahabr.ru")->dom->find("a.post_title")->each(sub{ say $_->text })'
    

    Example 24:
    A function call for each element of the collection, the element is available as the first argument of the function either through $ _ here, as in the example above with a list of post headers. I want to emphasize the peculiarity is that it is made so that each element is available through $ _ , this is convenient. Just like in the standard grep , map functions .
    package Mojo::Collection;
    #
    sub each {
      my ($self, $cb) = @_;
      return @$self unless $cb;
      my $i = 1;
      $_->$cb($i++) for @$self;
      return $self;
    }
    #
    $dom->find('p[id]')->each(sub { say $_->{id} });
    

    Example 25:
    Creating a random unique value among hash keys. Also creating a hash, if it has not yet been created, and as in the example above, assigning a simple local variable, in order to make it easier to use it in a block or function.
    my $timers = $self->{timers} //= {};
    my $id;
    do { $id = md5_sum('t' . steady_time . rand 999) } while $timers->{$id};
    

    Example 26:
    Comment before an else or else if block .
    }
    # New socket
    else {
    

    Example 27:
    Using the shift function as a hash key, you need to call it with brackets, otherwise it will be interpreted as the string “shift” .
    Also, to remind you, if someone forgot, in this example, access immediately to several hash elements, delete several keys at once.
    sub reset { delete @{shift()}{qw(io poll timers)} }
    #
    sub get { (shift->{cache} || {})->{shift()} }
    

    Example 28:
    Overloading operations to work with this object, fallback => 1 is necessary if the overloaded operation is not defined, so that an error does not pop up and that there is a search for a suitable operation.
    package Mojo::Collection;
    use Mojo::Base -strict;
    use overload
      bool => sub { !!@{shift()} },
      '""' => sub { shift->join("\n") },
      fallback => 1;
    # пример использования перегруженных операций
    my $collection = Mojo::Collection->new(qw(just works));
    say $collection;
    say "empty" unless $collection;
    

    Example 29:
    In a for clause , there may be no reference to a hash.
    $self->_finish($_, 1) for keys %{$self->{connections} || {}};
    

    Example 30:
    Wrap a long expression with assignment to another line.
    my $stream
      = $self->_loop($c->{nb})->stream($id)->timeout($self->inactivity_timeout);
    

    Example 31:
    Difference between return and return undef . Just return will return an empty list in the context of the list, in the scalar context it will return undef . While return undef in any context will return undef .
    return undef if $self->tree->[0] eq 'root';
    #
    return unless defined $lines->[0][$num - 1];
    # свои примеры не верного использования
    sub foo { return undef }
    if (my @x = foo()) { print "oops, we think we got a result"; }
    #
    sub foo { return }
    %x = ('foo' => foo(), 'bar' => 'baz');
    if (!exists $x{'bar'}) { print "oops, bar became a value, not a key"; }
    

    Example 32:
    Converting any type to Boolean.
    sub has_data { !!keys %{shift->input} }
    


    Well, something like that, I brought the most interesting pieces of code here, if anyone is interested, you need to look through GitHub yet. And if you bring your code to this, then you should probably get a good application.

    Also popular now: