📜 ⬆️ ⬇️

Mojolicious Perl Style

I want to describe the programming style in the Perl language, to which I aspire and which I basically adopted from the modern Mojolicious web framework, but probably many other similar ones are used. It seems to me to develop the correct coding style - it is very important.

Example 1:
Methods in one line.
If each function argument is called only once, and the order of their application in the code corresponds to the order of the arguments passed, it is proposed to extract them using the standard shift function, which, if called without arguments, by default works with the @_ array, which stores all function arguments passed, pushes the first argument out of 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 retrieve the first parameter, the name of the class, for example, we pass 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 the one line method.
Here, the same argument is accessed as many as 3 times, therefore, to access the argument, a direct access to the element of the $ _ [0] argument array 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 one-line if , else if or unless block , and then if the condition is not met, the code will continue to be executed 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 immediately 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:
The 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 is necessary to make it basic for your module and using it you can add methods for events, and within some of your own methods 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:
Elimination of memory leaks. For example, in the Mojo :: EventEmitter module, there is a once method that could have caused a memory leak if it had not used the weaken function.
If you have a link to a function that either uses the variable that references it (directly or indirectly) itself, then weaken (Scalar :: Util) should 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 array.
 $_ eq $child ? last : $i++ for @$parent[$i .. $#$parent]; 

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

Example 10:
Assignment of 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 an array or hash, without unnecessary curly braces.
 my $params = $self->{params} = []; return $params unless length $str; 

Example 11:
If in blocks if , else if or else the code is simple from one expression, then it is better to write 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 a base for your module. Enables a number of default pragmas (modules) such as strict , warnings , utf8, and Perl 5.10 innovations.
Adds to our class the has method, a similar style from Moose, which can be used to create class variables in the following way.
If you pass the hash to the class constructor, as in the last line of the example, it initializes the object variables.
 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 => <<EOF; #   See 'APPLICATION help COMMAND' for more information on a specific command. EOF # my $Foo = Foo->new({kept_alive =>$kept_alive, local_address => $local_address}); 

Example 13:
The method of the base class that you want to override in the 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:
The string containing the variable inside and double quotes, it is better to write so.
 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. Big hash is better to create separately, and not directly in the argument anonymous, so it is 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 one-line condition block and is planned to be used in a tele block, then it cannot be done, it will be unavailable, therefore you need to declare the variable before the block.
In the second case, in the example, when using the created variable, braces are used in the block, an example of extracting the value from the hash by key is also shown there, 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’s better to use the standard qw function, it’s not necessary to separate each element with a comma, so it’s 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, separated by commas and call this function in 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 construction 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 $ class variable 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 we 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 do not fit even 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 for example to check module existence.
 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 to a class, no strict and no warnings , only acts locally, in a function or block. It is also worth noting that symbolic links work only 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 use in the console, write small cool one-liners. The -M option connects the module and thus the name of the -Mojo framework is obtained. There some functions are collected, in the import method, the monkey_patch function described above is 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 header of the official site Mojolicious or the headers of posts on the main site 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:
The function call for each element of the collection, the element is available as the first function argument or through $ _ here, as in the example above with the list of post headers. I would like to emphasize that the peculiarity is that it is made so that each element is accessible through $ _ , this is convenient. In the same way as in standard functions grep , map .
 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 the hash keys. Also creating a hash, if it has not yet been created, and just like in the example above, assigning a simple local variable in order to make it easier to use 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 the block else or else if .
 } # New socket else { 

Example 27:
Using the shift function as a hash key, you must call it with parentheses, otherwise it will be interpreted as a string "shift" .
Also for a reminder, if anyone has forgotten, in this example, accessing immediately to several elements of the hash, deleting 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 needed 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 the block condition for , perhaps there is no reference to the hash.
 $self->_finish($_, 1) for keys %{$self->{connections} || {}}; 

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

Example 31:
Difference between return and return undef . Just return will return in the context of the list an empty list, in scalar context will return undef . While return undef in any context returns 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:
Convert any type to boolean.
 sub has_data { !!keys %{shift->input} } 


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

')

Source: https://habr.com/ru/post/233991/


All Articles