Wednesday, May 20, 2009

Perl-Artikel im Blog bei O'Reilly

In den nächsten Wochen wird es ein paar Blog-Beiträge von mir im Blog von O'Reilly geben. Eben gerade ist die Anmoderation von Ariane Hesse online gegangen...

Das was Frau Hesse dort beschreibt, kann ich nur allzu gut nachvollziehen. Solche Fehler unterlaufen uns auch immer mal wieder im Perl-Magazin. Aber wir sind ja auch nur Menschen ;-)

"Perl programming": Einfluss auf den TIOBE-Index nehmen.

Viele werden den TIOBE-Index kennen, mit dem die Beliebtheit von Programmiersprachen gemessen werden soll. Perl schneidet da relativ schlecht ab. Das hängt auch mit der Art zusammen, wie TIOBE den Index erstellt: relativ einfach mit Google und der Suche nach " programming", also z.B. "perl programming".

Dass vielen diese Problematik bewusst ist, zeigt auch Richard Dice (Präsident der Perl-Foundation) in einem Interview im Perl-Magazin.

In einem Blog habe ich jetzt eine gute Idee gefunden, wie man daran arbeiten kann: Man muss sich die vielen Seiten auf CPAN zu nutze machen und über all "CPAN - The Perl programming archive" einfügen.

Das werde ich wohl irgendwo in der Dokumentation unterbringen, wenn ich meine Module mal wieder update.

Monday, May 11, 2009

Meine CPAN-Module - Teil 2

Auf CPAN finden sich sehr viele Module. Darunter sind sehr viele nützliche Module, aber auch einige, die nur so "zum Spaß" da sind. Diese sind meist im "Acme::*"-Namensraum angesiedelt. Mit zu bekanntesten, dürften Acme::Bleach und Acme::Buffy gehören. Beides sind Module, die den Quellcode verändern. Beim ersten Modul hat man dann lauter "Whitespace" - ein wahrhaft sauberes Programm ;-)

Manche Acme::*-Module sind aber auch zum Testen da. So wie mein Acme::DarmstadtPM::TieHash. Ein anderes Mitglied von Darmstadt.pm hat auf der Mailingliste ein Ruby-Snippet gepostet (habe ich damals auch bei Perl-Community.de gepostet) und gemeint, dass Perl so etwas nicht kann. So etwas interessiert mich dann und ich versuche, eine (Perl-)Lösung dafür zu finden.

Zum Glück gibt es "tie"s in Perl.

Daraus ist dann oben genanntes Modul entstanden, mit dem man einen Hash an eine Subroutine binden kann und wenn man auf einen Schlüssel des Hashs zugreift, dann wird das Ergebnis der Subroutine zurückgeliefert.

Als "Proof of Concept" ist es ganz ok ;-)

 1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Test::More tests => 2;
6
7 use constant ADT => 'Acme::DarmstadtPM::TieHash';
8
9 use_ok(ADT);
10
11 tie my %hash,ADT,sub{$_[0] + $_[-1]};
12
13 is($hash{[1,5]},6,'Check [1,5]');
14
15 untie %hash;
oder auch
  1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Acme::DarmstadtPM::TieHash;
6
7 tie my %hash, 'Acme::DarmstadtPM::TieHash', sub{ "\U@_\n" };
8
9 print $hash{hallo};
10
11 untie %hash;

Friday, May 08, 2009

Meine CPAN-Module - Teil 1

Durch Yuval Kogman inspiriert, werde ich hier in der nächsten Zeit immer mal wieder über meine CPAN-Module schreiben. Dabei werde ich nicht nur die Funktionen beschreiben - dafür gibt es ja die Doku ;-) - sondern auch warum ich das geschrieben habe.

Da gibt's auch Module, die vielleicht nicht optimal sind...

Denn Anfang mache ich heute mit den ersten Modulen, die ich geschrieben habe: Bio::FASTASequence und Bio::FASTASequence::File. Beide Module sind ganz am Anfang meiner "Perl-Karriere" entstanden, als ich bei einer Firma im Industriepark Höchst meine Ausbildung in der Bio-Informatik gemacht habe.

Da tauchte immer wieder die Aufgabe auf, Dateien mit FASTA-Sequenzen zu parsen. Am Anfang alles im Skript selbst (als Perl-Autodidakt kennt man Module noch nicht gleich), dann habe ich in meiner Freizeit die erste Version des Moduls geschrieben.

Warum kein BioPerl? Weil ich es da noch nicht kannte - und später war mein Modul zu sehr an das ganze System angepasst.

Das Modul dient also dazu, Informationen über FASTA-Sequenzen herauszufinden. Diese Informationen wurden weiterverarbeitet.

Wer sich den Code anschaut, wird sofort erkennen, dass das jemand geschrieben hat, der noch nicht allzu lange mit Perl programmiert hat. Da ich aber nicht mehr in der Bio-Informatik unterwegs bin, werde ich da auch nicht mehr allzu viel dran ändern.

Jetzt aber mal etwas zur Funktion des Moduls - an Hand eines kleinen Beispiels:

#!/usr/bin/perl

use strict;
use warnings;
use Bio::FASTASequence;

my $sequence = qq~>sp|P01815|Beschreibung.
QVTLRESGPALVKPTQTLTLTCTFSGFSLSSTGMCVGWIRQPPGKGLEWLARIDWDDDKY
YNTSLETRLTISKDTSRNQVVLTMDPVDTATYYCARITVIPAPAGYMDVWGRGTPVTVSS~;

my $object = Bio::FASTASequence->new($sequence);
my @cysteins =

print 'The sequence of ' . $object->getAccessionNr .
' is ' . $object->getSequence ,"\n",
'This sequence contains ', scalar( @{ $object->allIndexesOf('C') } ),
' Cysteins at the positions ';

print $_+1, ', ' for @{$object->allIndexesOf('C') };

Bio::FASTASequence ermöglicht das Parsen einer einzelnen Sequenz. Möchte man ganze Dateien einlesen, kann man Bio::FASTASequence::File verwenden.

Das Modul eignet sich ganz gut, wenn man nur mal Dateien im FASTA-Format schnell parsen will, ohne sich in die Tiefen von BioPerl zu graben. Für größere Sachen empfehle ich aber BioPerl (das ich dann später in anderen Projekten auch selbst verwendet habe).

Monday, May 04, 2009

eigene Perl::Critic Policies

Im Zuge des EPO-Iron Mans werde ich auch vereinzelt Artikel aus früheren Ausgaben von "$foo - Perl-Magazin" posten.

Ich mache hier die Regeln

eigene Policies für Perl::Critic

Jedes Unternehmen hat eigene Programmierrichtlinien und jeder Programmierer hat einen eigenen Stil. Soll der Code auf den eigenen Stil hin überprüft werden, sind die bestehenden Policies von Perl::Critic häufig ungeeignet. Da müssen also eigene Policies geschrieben werden.

Dafür ist es notwendig, dass man sich einigermaßen mit PPI (siehe auch $foo Ausgabe 3) auskennt und einige Regeln von Perl::Critic beachtet. In diesem Artikel soll das Schreiben eigener Policies am Beispiel "öffnende Klammer einer Schleife muss in der Zeile des Schleifenkopfes stehen" gezeigt werden.

Es soll in Zukunft also so etwas erzwungen werden wie

  for my $var ( ... ) {

und nicht

  for my $var ( ... )
{

Big Perl is watching your Code

Wie schon erwähnt, steht hinter Perl::Critic das Modul PPI. PPI zerlegt den Perl-Code in Tokens, erstellt daraus "Nodes" und baut daraus eine Baumstruktur. Perl::Critic ruft bei jedem "Node" die entsprechende Perl::Critic::Policy-Subklasse auf und wenn ein Code vorhanden ist, der nicht den Regeln entspricht, wird ein Perl::Critic::Violation-Objekt zurückgeliefert.

Das Gerüst muss stehen

Die Policies sind Subklassen von Perl::Critic::Policy, und die Klassen müssen dementsprechend auch in diesem Namensraum liegen. Perl::Critic arbeitet mit Module::Pluggable, um die Policies automatisch zu finden.

Die ersten Zeilen einer Policy (nach dem Package-Namen) sollten so aussehen:
  use strict;
use warnings;
use Perl::Critic::Util;
use Perl::Critic::Violation;

use base qw/Perl::Critic::Policy/;

our $VERSION = '0.01';
Die Policy muss einem Nutzer auch sagen, was falsch ist. Dafür gibt es zwei Variablen in jeder Policy: Zum Einen $desc und zum Anderen $expl. $desc ist ein String, der aussagen sollte, was falsch gelaufen ist. Das kann in wenigen Worten geschehen. $expl ist entweder ein String mit einer genauen Beschreibung, was falsch gemacht wurde, oder eine Arrayreferenz mit den Seitenzahlen aus "Perl Best Practices", auf denen das gewünschte Verhalten erklärt ist.

Da in diesem Beispiel kein Programmierstil von "Perl Best Practices" behandelt wird, sind das ganz eigene Sachen, die dort ausgegeben werden.
  my $desc = q~{ nicht in der Zeile des Schleifenkopfes.~;
my $expl = q~Die öffnende Klammer muss in der Zeile des
Zeilenkopfes stehen, also

for my $var ( ... ){
...
}

anstatt

for my $var ( ... )
{
...
}
~;

Die new-Methode sollte nur überschrieben werden, wenn der Nutzer Argumente an die Policy übergeben können soll. Das ist hier aber nicht notwendig.

Mein Profil

Jede Policy hat ein paar Standardsachen, die ein gewisses Profil für die Policy darstellen. Dazu gehören neben der Kurzbeschreibung in $desc und der genauen Beschreibung in $expl auch ein paar Subroutinen, die angeben, für welche PPI-Nodes die Policy angewendet werden soll, zu welchen Themes die Policy gehört und natürlich für welchen Severity-Level die Policy angewendet werden soll.

Die Angabe des PPI-Knotens ist wichtig, damit die Policy auch nur für ganz bestimmte Code-Fragmente angewendet wird. Außerdem bedeutet es einen Geschwindigkeitsvorteil, wenn nicht alle Policies für alle Knoten angewendet werden.

Der Knoten wird durch die Subroutine applies_to bestimmt. Die for-Schleife ist ein PPI::Statement::Compound, also muss dies in der Subroutine eingetragen werden.
  sub applies_to{ return 'PPI::Statement::Compound' }
Über die Angabe des Themes können Anwender bestimmte Policies ein- oder ausschalten. Dies ist ganz praktisch, wenn man für mehrere Kunden arbeitet und alle etwas unterschiedliche Regelungen haben.

Dieses Beispiel soll in den Themes "core" und "foo" enthalten sein. Die Subroutine, die das bestimmt, heißt default_themes.
  sub default_themes{ return qw/core foo/ }
Der Severity-Level bestimmt, wie wichtig die Regel ist. Ist es eine Regel, die in jedem Programm eingehalten werden muss, dann ist das die höchste Prioritätsstufe, ist es jedoch nur eine "Schönheitsregel", dann kann eine niedrige Prioritätsstufe ausgewählt werden. Da die hier gezeigte Regel auf jeden Fall eingehalten werden soll, bekommt sie die höchste Priorität
  sub default_severity{ return $SEVERITY_HIGHEST }
Die Variable $SEVERITY_HIGHEST wird vom Modul Perl::Critic::Utils exportiert.

Jetzt geht's los!

Bevor das große Coden losgeht, am besten nochmal genau darüber Gedanken machen, was die Policy machen soll. In diesem Fall müssen wir bedenken, dass kein Fehler ausgegeben werden soll, wenn der Schleifenkopf nachgestellt ist, also so etwas wie
  print $_ for @array;
Als ganz nützlich hat sich herausgestellt, vorher verschiedene Varianten von Code, die der Nutzer schreiben könnte, mit PPI zu testen und sich die Baumstruktur anzuschauen.

In den Beispielcodes auf der Webseite ist ein Beispielprogramm mit verschiedenen Schleifenvarianten enthalten. In Listing 2 ist die PDOM-Struktur einer for-Schleife, wie sie gültig ist, zu sehen.

  PPI::Statement::Compound
PPI::Token::Word 'for'
PPI::Token::Whitespace ' '
PPI::Token::Word 'my'
PPI::Token::Whitespace ' '
PPI::Token::Symbol '$var'
PPI::Token::Whitespace ' '
PPI::Structure::ForLoop ( ... )
PPI::Statement
PPI::Token::Symbol '@array'
PPI::Structure::Block { ... }

Ein kleiner Teil ist bei einer ungültigen Schleife unterschiedlich. Zwischen der ForLoop und dem Block taucht noch ein Newline auf:

  PPI::Structure::ForLoop     (... )
PPI::Statement
PPI::Token::Symbol '@array'
PPI::Token::Whitespace '\n'
PPI::Structure::Block { ... }
Die einfachste Variante, eine solche Policy zu entwickeln ist, sich alle möglichen Varianten der zu untersuchenden Struktur aufzuschreiben und mit dem Dumper von PPI anzuschauen. Dann können die Unterschiede besser herausgearbeitet werden. Für diesen Artikel gibt es bei den Codes auf der Webseite das Skript show_pdoms.pl, das genau dieses zeigt.

Bei dem hier gezeigten Beispiel fällt auf, dass bei einer Regelverletzung immer ein \n zwischen der ForLoop und dem Block erscheint.

In Listing 3 ist die Methode zu sehen, die für die eigentliche Regelüberprüfung
zuständig ist.

1 sub violates{
2 my ($self,$elem,$doc) = @_;
3
4 my $base = $elem->schild(0);
5 return unless $base eq 'for';
6
7 my ($list,$newline,$block);
8
9 my @children = $elem->children;
10
11 for my $i ( 0 .. $#children ){
12 my $child = $children[$i];
13
14 if( $child->isa( 'PPI::Structure::Block' ) ){
15 $block = $i;
16 }
17 elsif( $child->isa( 'PPI::Structure::ForLoop' ) ){
18 $list = $i;
19 }
20 elsif( $child->isa( 'PPI::Token::Whitespace' ) and
21 $child eq "\n" and
22 not $newline){
23 $newline = $i;
24 }
25 }
26
27 if( $newline and $newline > $list and $newline < $block){ 28 my $sev = $self->get_severity;
29 return Perl::Critic::Violation->new( $desc,$expl,$elem,$sev );
30 }
31
32 return;
33 }


Die Methode, die die Überprüfung enthält, heißt violates und bekommt von Perl::Critic automatisch drei Parameter übergeben: das Perl::Critic-Objekt, das entsprechende PPI-Element, auf das die Regel angewendet wird und das komplette PPI-Dokument (die Datei oder das Snippet, auf das Perl::Critic angewendet wird).

Die Policy soll nur auf for-Schleifen angewendet werden. Deshalb muss als erstes überprüft werden, ob es sich tatsächlich um eine solche Schleife handelt. Dazu wird überprüft, ob das erste child-Element ein 'for' ist.

Danach werden die Positionen von Block, ForLoop und Newline festgestellt. Nur in dem Fall, in dem ein Newline zwischen Block und ForLoop ist, wird ein Fehler ausgegeben.

Und schon ist die erste eigene Regel implementiert. Bei einer for-Schleifen, die gegen die Regel verstößt, wird in Zukunft ein Fehler ausgegeben, wie er in Listing 4 zu sehen ist.
oeffnende Klammer muss in Zeile mit Schleifenkopf sein at line 20, column 5. Es
soll in Zukunft also so etwas erzwungen werden wie

for my $var ( ... ) {

und nicht

for my $var ( ... )
{
.