Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

Meditations

( #480=superdoc: print w/replies, xml ) Need Help??

If you've discovered something amazing about Perl that you just need to share with everyone, this is the right place.

This section is also used for non-question discussions about Perl, and for any discussions that are not specifically programming related. For example, if you want to share or discuss opinions on hacker culture, the job market, or Perl 6 development, this is the place. (Note, however, that discussions about the PerlMonks web site belong in PerlMonks Discussion.)

Meditations is sometimes used as a sounding-board — a place to post initial drafts of perl tutorials, code modules, book reviews, articles, quizzes, etc. — so that the author can benefit from the collective insight of the monks before publishing the finished item to its proper place (be it Tutorials, Cool Uses for Perl, Reviews, or whatever). If you do this, it is generally considered appropriate to prefix your node title with "RFC:" (for "request for comments").

User Meditations
Truth and Falsehood
3 direct replies — Read more / Contribute
by haukex
on Aug 17, 2019 at 05:19

    Truth and Falsehood

    In Perl, the following values are false in boolean context:

    • The number zero (0)
    • The string "0"
    • The empty string ""
    • undef

    All other values are true.

    Boolean context is a scalar context. Therefore the following things, when evaluated in boolean/scalar context, are also false:

    • An empty array, which evaluates to the number of elements it contains, i.e. zero (0).
    • An empty hash, which evaluates to a false value when it is empty.
    • The empty list (), which evaluates to undef.

    A true value negated by ! or not, plus many of Perl's builtins/operators, return a special false value: When evaluated as a string it is treated as "", but as a number, it is treated as 0, without causing any warnings.

    When the Perl documentation says that a operator or function returns "a false value" or "a true value" (or more simply, "false" or "true"), it may return any of the above values.

What modules should be added to the corelist?
7 direct replies — Read more / Contribute
by Anonymous Monk
on Aug 16, 2019 at 09:17
Refactoring example (A good one? (Perl6))
1 direct reply — Read more / Contribute
by holli
on Aug 10, 2019 at 17:45
    Allright, so I am hacking on a module that needs to load plugins. There is the Pluggable-module which attempts to do that. But I needed additional functionality like being able to get a list of plugins without loading them, and load a list of plugins I give it. And so I looked at the code and found it to be deeply entangled in two big functions, which somehow included already what I needed. So instead of c&p the pieces I needed into new code, I ended up refactoring the whole thing.

    The thing with refactoring and staying DRY is, that it is often hard to judge where to stop tearing the functionality into smaller and smaller components. At some point, oversight is lost. That idea was reinforced by some talk I watched this week and so I am asking myself wether my judgement in this matter is as good as I think it is.

    Long story short, here are the original version and the refactored version. Keep in mind the refactored version has a greater functionality. Did I do a good job? Which version would you want to add to? Which version would you rather look for a bug in?

    Original:
    Mine:


    holli

    You can lead your users to water, but alas, you cannot drown them.
Teen Perl
No replies — Read more | Post response
by Anonymous Monk
on Aug 09, 2019 at 03:14
    Will Braswell talks about "the Perl 11 Master Plan to make Perl the king of languages again" in the "State of the Scallion Address" at The Perl Conference 2019 in Pittsburgh. Step 1 of the plan: "We're going to have to educate young people". Perl has this hidden gem of a tutorial which was originally a multi-page website titled "Tinkering With Perl: A Child's Guide" that survives as a single webpage (without the enchanting subtitle) at https://cjshayward.com/perl/

    Will Braswell - "The State of the Scallion Address"
    https://www.youtube.com/watch?v=NyphRo5roV0

    The Perl 11 Master Plan
    https://www.youtube.com/watch?v=NyphRo5roV0&t=27m20s

RFC: Concise OOP Syntax
1 direct reply — Read more / Contribute
by LanX
on Aug 04, 2019 at 20:59
    Dear monastery!

    In continuation to

    I tried to hack a proof of concept for a concise OO syntax

    The basic ideas are that:

    Declaration of instance variable
    • my on the class level with :attributes
    • the TYPE is given right after the my
    • attributes reflect the Moo(se-)model of has keys where possible
    • the assigned values are defaults for the new-constructor
    Access of instance variables inside methods
    • an instance variable x is readable and writable via $$x
    • this is automatically mirrored in $self->{x}
    • $self->{x} is an alternative syntax for the same access
    $self
    • $self is already shifted from @_ and directly available
    Methods
    • all subs declared inside the scope of a class are methods
    • imported subs (like pp) are ignored

    { use Class BLA => ISA-LIST; use Data::Dump qw/pp/; my Int ($x,$y) :has :rw = (10,11); sub set_x { my ($var) = @_; #warn "set_x $$x -> $var \n"; $$x = $var; } sub print_x { print "x = $$x \n"; } sub print_self_x { print "x = $self->{x} (via \$self)\n"; } sub dump { warn '$self:',\$self; warn pp '$self: ', $self; warn pp '$$x: ', $$x; warn pp '$$y: ', $$y; } }

    The implementation is done via a macro expansion from use Class which injects some boilerplate into the head of the class, which handles the creation.

    Injecting is basically done via a source filter or alternatively via Keyword::Simple. NB: just injecting some code doing introspection. No parsing, regexing or modification of the code you see.

    I'm supposing this concise syntax could be used as a front end for all current OO models in Perl and might help offering a stable backwards compatible syntax if it's hardcoded into the engine.

    A rough proof of concept follows here:

    NB: This example is pretty barebone, and not meant to be an alternative to other OO Frameworks, but rather a frontend. It doesn't create accessors and the constructor is only simplistic.

    Comments?

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

parent::versioned supports minimum version specification in parent classes
2 direct replies — Read more / Contribute
by davido
on Aug 04, 2019 at 14:58

    A question came up here recently where a user wished to specify a minimum version for a parent class. base doesn't support specifying minimum versions, nor does parent. base and parent are core Perl modules. I don't think it would be appropriate to add complexity to the core Perl parent by adding this feature.

    Perl facilitates setting minimum versions in use statements:

    use Time::HiRes 1.9;

    use has a compiletime effect, so that code behaves like this:

    BEGIN { require Time::HiRes; Time::HiRes->VERSION(1.9) Time::HiRes->import }

    However, BEGIN blocks require us to consider the order of loading more than we may want to. This doesn't fit the model of keeping simple things simple, if the simple thing we want to do is fairly common.

    For inheritance we can do this:

    package MyAgent; BEGIN { require LWP::Usergent; LWP::UserAgent->VERSION(6.39); # Assert a minimal version. push @MyAgent::ISA, 'LWP::UserAgent'; }

    To me that's too much boilerplate for a common need. Another way:

    package MyAgent; use parent 'LWP::UserAgent'; BEGIN {LWP::UserAgent->VERSION(6.39)}

    But that's a little fragile; someone may refactor and forget to keep the BEGIN block after the call to parent causing things blow up. Sometimes code becomes complex, masking simple mistakes. I would prefer a solution that doesn't require the user to maintain several steps to set up inheritance while assuring minimal versions for parent classes.

    parent::versioned makes the inheritance and compile-time version checking more convenient, and harder to get wrong. I uploaded it to CPAN a few hours ago.

    package Myagent; use parent::versioned ['LWP::UserAgent' => 6.39];

    parent and parent::versioned behave the same for all import lists unless an element in the import list is an array ref. Any array ref passed to the import list will be treated as a tuple that specifies a module name, and a minimum version for that module. Multiple inheritance works like this:

    use parent::versioned ['LWP::UserAgent' => 6.39], ['Mojo::DOM' => 7.2 ], 'Foo::Base';

    This example sets up triple inheritance: two modules that are version checked, and one that is not. As with parent, parent::versioned accepts the -norequire flag.

    The parent module has 100% code test coverage. parent::versioned passes the parent test suite and maintains 100% coverage. It should work as a drop-in replacement for parent, but with the additional functionality exposed by passing an array-ref as part (or all) of the import list. Most of the code is a direct fork from parent. You can use it just like you use parent:

    use parent::versioned qw(Foo);

    I prefer keeping the module small, but if you're interested please do look at the GitHub repository. Patches, issues, and suggestions are welcomed. I hope it becomes useful.


    Dave

Hashes do preserve insertion order after all
5 direct replies — Read more / Contribute
by kikuchiyo
on Jul 31, 2019 at 12:00

    It is often repeated that hashes in Perl do not preserve insertion order. Yet:

    #!/usr/bin/perl use strict; use warnings; use feature qw/say/; my %hoh = ( foo => { value => 'first' }, bar => { value => 'second' }, baz => { value => 'third' }, ); for my $elem (sort values %hoh) { say "value => " . $elem->{value}; }

    Output:

    value => first value => second value => third

    Edit and clarification: I'm not saying that you should ever use this (in fact, I'm saying that you shouldn't, the comments below describe why). It looks like as if it really preserved insertion order, but actually the trick relies on implementation details (memory allocation) you can't (or shouldn't want to) control.

Thx, St. Larry, for the Beauty of Sigils
4 direct replies — Read more / Contribute
by msouth
on Jul 26, 2019 at 13:12
    TL;DR Thanks, Larry, for sigils

    I recently took a job at a Python shop, helping out a friend. I have tried to be patient with the new-to-me language and to realize that there are different ways of doing things and that I should expect some mental friction simply due to my unfamiliarity. But, man, I just can't escape the idea that sigils were a good idea.

    I needed to access a dynamically specified method and run a dynamically specified method against that method's result. Python can't tell the difference between a variable you created that contains a method name and a method name itself. You have to use a function called getattr to cumbersomely retrieve the reference you want. And if you go any deeper than one, it gets ugly very quickly. In my case, I have something like

    getattr(getattr(object_identifier,variable_containing_the_name_of_meth +od_one), variable_containing_the_name_of_method_two)

    (I'm using ridiculously long variable names for clarity about what the variables contain.)

    With sigils to let Perl know that I mean 'grab the contents of the variable and execute the method having that name', I can do:

    $object_identifier->$variable_containing_the_name_of_method_one->$obje +ct_containing_the_name_of_method_two

    With the ridiculous names it may be less clear how much nicer this is, but look at:

    $obj->$var1->$var2

    vs

    gettattr( getattr(obj,var1) , var2)

    I hate going to the inside and working my way out, trying to remember what I picked up along the way.

    I realize that Python people hate having to type sigils all over the place, and that they would find it ironic that this would be the thing I would write in praise of. But I am doing a lot of work right now with dynamic method/attribute names and it really seems to me that Larry made the right call on this.

response to "The Perl Community - a mixed bag of sometimes intollerance and sometimes fantastic help"
4 direct replies — Read more / Contribute
by daxim
on Jul 24, 2019 at 03:19
    mje says he was ridiculed and alludes to intolerance (in the heading):
    GIven my new enthusiasm for the Perl community I dare to post an issue I was having to IRC on #perl-help and it all fell down. [] anyone posting in #perl-help, needs help and I'd forgotten that certain people view this channel as a shooting ground where you pour ridicule on posters "who don't understand their problem" - of course they don't or why would they post on #perl-help. I got some helpful comments but I also got the ridicule.
    That sounds untypical for #perl-help, in which I occasionally participate on both the Q and A sides. (There's my disclosure of bias.) mje did not substantiate, so I went and looked what happened. I did not find ridicule, not even a heated discussion. I notice I am confused.

    I decided to post the log because the Perl community as a whole was blamed, not individuals, and in order to provide accurate first-hand information so that anyone can form their own opinion.

    2019-07-23 18:47:42 mje__ I have a strange warning that I cannot + provide a runnable example for. The code "if (defined($match->{in_co +ntrol}) && ($match->{in_control} == 1))" ends up issuing a warning sa +ying "Use of uninitialized value in numeric eq (==)" but I cannor und +erstand how this can happen. $match is defined and even if it wasn't +I'd expect a warning saying trying to deref and undef. I don't think +it is autovivification as my tests show 2019-07-23 18:47:42 mje__ defined doesn't do this. Any ideas? 2019-07-23 18:48:44 mje__ Perl 5.24.4 2019-07-23 18:56:48 alh in_control could have operator overloa +ding 2019-07-23 18:57:00 alh Alternatively, the warning isn't happe +ning where you think it is which seems more likely 2019-07-23 18:57:21 alh Line numbers can be wrong if it's part + of a giant if () { } elsif () { } else {} block 2019-07-23 18:57:35 alh So it could be a different conditional + triggering it 2019-07-23 18:58:54 mje__ Simple hash ref, so no overloading. Th +e warning seems pretty good since the line mentioned contains an == 2019-07-23 18:59:58 alh Is it part of a multi-line conditional +? 2019-07-23 19:00:02 alh Can you show us more of the code 2019-07-23 19:00:08 mst the warning is happening somewhere els +e 2019-07-23 19:00:10 mst 99% chance 2019-07-23 19:02:28 mje__ Here is what it looks like now https:/ +/pastebin.com/fsTnLRaS, the only thing changed from when the warning +was output was defined changed to exists 2019-07-23 19:02:29 Repaster Repasted text from mje__: http +://perl.bot/p/mmydbc 2019-07-23 19:03:03 alh That's using exists 2019-07-23 19:03:04 alh not defined 2019-07-23 19:03:07 alh So it can exist and e undef 2019-07-23 19:03:13 alh Which is your problem 2019-07-23 19:03:46 mje__ I said, the original was defined when +the warning was issued and what I pasted was afterwards when it was c +hanged to exists 2019-07-23 19:04:48 mje__ the change from defined to exists is l +ikely wrong but the warning came from the defined case 2019-07-23 19:06:29 alh Can you show more context? 2019-07-23 19:12:38 mje__ https://pastebin.com/sYC3XR31 2019-07-23 19:12:39 Repaster Repasted text from mje__: http +://perl.bot/p/t8p5x4 2019-07-23 19:16:48 alh before line 18 warn the value of $exis +ting_match->{in_control} 2019-07-23 19:17:01 alh Beore line 14 do the same for $events{ +$event_id}->{event_status_id} 2019-07-23 19:17:05 alh That may be where the real warning is +coming from 2019-07-23 19:22:02 mje__ ok, so the crux of the opinion is the +line number is possibly wrong and so other == tests could be at fault +? 2019-07-23 19:24:47 mst the crux of the opinion is "you're con +fused about *something* 2019-07-23 19:24:51 alh Yes. And you can figure out by adding +debug everywhere 2019-07-23 19:24:57 mst I'd probably apply warn + Data::Dumper 2019-07-23 19:25:12 alh If you think $x->{foo} == 1 is warning +, warn reight before that conditional, warn inside that conditional, +warn after that conditional 2019-07-23 19:25:31 alh Where the unintialized value warning a +ppears in regards to what you've added should help you track it down 2019-07-23 19:28:21 Grinnz source filters are also known to screw + up line numbers 2019-07-23 19:29:32 mst oh. yes. if you have 'use Switch;' in +that code, you can assume all line numbers are wrong 2019-07-23 19:30:00 Grinnz (and in that case, that it also caused + the error) 2019-07-23 19:31:00 mje__ Unfortunately this is code working on +live data and although it is possible to play it back the playback is +n't EXACTLY as it was live. We don't use Switch. I hadn't considered +the warning line number was wrong so I've got something to work on. T +hanks 2019-07-23 19:31:43 Grinnz there are still some core bugs that ma +ke wrong line numbers too 2019-07-23 19:34:35 alh Also if a comment like this is anywher +e in the file: 2019-07-23 19:34:38 alh # line 5 10 2019-07-23 19:35:07 alh Or even: 2019-07-23 19:35:09 alh # line 10 2019-07-23 19:35:12 mst honestly I still thing you're just con +fused about the data
    Timestamps are in Europe/Vienna timezone. The log is complete and unmodified. I'd appreciate replies of confirmation that it is so.
Off by one key... or yet another example of why strict and warnings are always a good idea (even for "one-liners")
3 direct replies — Read more / Contribute
by atcroft
on Jul 17, 2019 at 20:02
    "Just sit right back and you'll hear a tale
    a tale of a fateful trip,
    that started from this simple term,
    aboard this single slip."

    Please, dear reader, learn from my mistake. (It will likely be less painful than repeating it yourself.)

    Earlier today I did something I have done dozens of times before-created a command-line perl script (a "one-liner", for some very long definition of a "line") to create a file containing a subset of messages from a set of compressed logs, with the intent of pulling them back to my machine for additional processing. I launched it in a screen session, saw it was running and would take some time to begin generating output, and stepped away for lunch. I was rather surprised, however, when I returned to find it throwing "No space left on device" messages-especially when I looked to find that the partition's previous 40GB of free space was now zero. After cleaning up the space, I began trying to find the cause.

    In digging through my code, I almost missed the issue-a line of the form if ( $str -~ m/some pattern/ ) { print $str; }. Notice the issue? An off-by-one-key error from a en.US keyboard, where [-/_] and [=/+] are beside one another. This was compounded by skipping the well-espoused logic of use strict; use warnings; because it was a "one-liner" (where "-Mstrict -Mwarnings" would have added a mere 19 characters in length). Had I have done so, instead of:

    # made-up example with actual issue $ perl -le 'my $str = q{zxcv}; if ( $str -~ m/some pattern/i ) { print + $str; }' zxcv
    I would have seen:
    # made-up example with actual issue, with strict and warnings $ perl -Mstrict -Mwarnings -le 'my $str = q{zxcv}; if ( $str -~ m/some + pattern/i ) { print $str; }' Use of uninitialized value $_ in pattern match (m//) at -e line 1. Argument "zxcv" isn't numeric in subtraction (-) at -e line 1. zxcv
    Seeing unexpected output like that (that early) would likely have resulted in my cancelling the command to investigate, and thus likely not filling up 40GB of storage.

    I know this will probably be like the tone after the television broadcast day ended to a sleeping viewer, but maybe, just maybe, it will help someone. In the end, learning from others' mistakes is often less painful (but not necessarily as memorable).

    (And yes, I do use the the duo on most every script I write-just not always on "one-liners".)

File::Spec->case_tolerant() is broken
No replies — Read more | Post response
by afoken
on Jul 13, 2019 at 20:42

    This is the long version of Re^2: open file error and Re^5: Unify windows filenames (from ten years ago), triggered by Re^2: perl script to compare two directories, plus a little bit of bean counting on the code for non-Unix systems.


    File::Spec has a severe design problem that will cause wrong results on modern Unix systems (*BSD, Linux, MacOS X). It assumes that all filesystems are equal on any Unix operating system. And this assumtion is plain wrong since decades:

    • Any modern Unix can mount FAT and NTFS, two filesystems that are commonly used in a case insensitive way, and so they are generally mounted in a case insensitive way.
    • At the same time, the native filesystems (ext2/3/4, btrfs, ufs, ufs2, zfs) are mounted in a case sensitive way.
    • ISO9660 is case insensive, Rock Ridge extensions make it case sensitive, so a CDROM / DVD-ROM / Blueray mounted on a Unix system may be either case sensitive or case insensitive.
    • Mounting a case insensitive FAT filesystem on Linux is quite common: All Raspberry Pis boot from a FAT partition later mounted as /boot
    • And to drive people mad, Linux 5.2 also can mount its native ext4 filesystem in a case insensitive way.

    To make things worse, the code for at least Windows and cygwin is broken, too.

    And here is how File::Spec handles all of that:

    package File::Spec; use strict; our $VERSION = '3.75'; $VERSION =~ tr/_//d; my %module = ( MSWin32 => 'Win32', os2 => 'OS2', VMS => 'VMS', NetWare => 'Win32', # Yes, File::Spec::Win32 works on Ne +tWare. symbian => 'Win32', # Yes, File::Spec::Win32 works on sy +mbian. dos => 'OS2', # Yes, File::Spec::OS2 works on DJGP +P. cygwin => 'Cygwin', amigaos => 'AmigaOS'); my $module = $module{$^O} || 'Unix'; require "File/Spec/$module.pm"; our @ISA = ("File::Spec::$module"); 1;

    For any modern Unix, the %module hash has no overriding key, and so File::Spec::Unix will implement all methods of File::Spec.

    File::Spec::Unix

    package File::Spec::Unix; use strict; use Cwd (); our $VERSION = '3.75'; $VERSION =~ tr/_//d; # ... sub case_tolerant { 0 } use constant _fn_case_tolerant => 0; # ...

    File::Spec->case_tolerant() constantly returns false, and that is plain wrong, as explained above.

    The constant _fn_case_tolerant, used by File::Spec::Functions, is also wrong. It should not exist at all.

    File::Spec::Win32

    Strangely, the Windows implementation, which optionally allows passing a drive letter to the case_tolerant() method, is better, but still wrong:

    package File::Spec::Win32; use strict; use Cwd (); require File::Spec::Unix; our $VERSION = '3.75'; $VERSION =~ tr/_//d; our @ISA = qw(File::Spec::Unix); # ... sub case_tolerant { eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require Win32API::File; } or return 1; my $drive = shift || "C:"; my $osFsType = "\0"x256; my $osVolName = "\0"x256; my $ouFsFlags = 0; Win32API::File::GetVolumeInformation($drive, $osVolName, 256, [], [] +, $ouFsFlags, $osFsType, 256 ); if ($ouFsFlags & Win32API::File::FS_CASE_SENSITIVE()) { return 0; } else { return 1; } } # ...
    • Failing to load Win32API::File silently and magically makes all filesystems case insensitive. Plain wrong. If all filesystems were case insensitive, messing with the Win32 API function GetVolumeInformation() would not be needed and the entire function body could be reduced to { 1 }. Right for the common case, wrong for edge cases.
    • The return value of GetVolumeInformation(), which may fail, is not checked at all. Microsoft does not specify what happens to the variable passed as lpFileSystemFlags, so the contents of $ouFsFlags may be junk in that case. If you are lucky, $ouFsFlags is not touched, stays 0, and the following test for FS_CASE_SENSITIVE ends by returning true, which is right by accident in the common case. If you test an edge case, or if $ouFsFlags has been modified so that its FS_CASE_SENSITIVE bit is set, the returned result is wrong.
    • Omitting the drive letter tests the C: drive, probably to be compatible with the File::Spec::Unix implementation, which has no documented parameters. Again, right for the common case, wrong for edge cases.
    • Undocumented feature: GetVolumeInformation() also accepts UNC paths ('\\server\share'), and case_tolerant() does not prevent you from testing UNC paths.
    • NTFS volume mount points (that allow mounting any filesystem supported by Windows in an empty subdirectory of a NTFS volume) are not documented, neither in File::Spec::Win32, nor in the documentation of GetVolumeInformation(). Probably, some more code is required to handle NTFS volume mount points.

    File::Spec::Cygwin

    package File::Spec::Cygwin; use strict; require File::Spec::Unix; our $VERSION = '3.75'; $VERSION =~ tr/_//d; our @ISA = qw(File::Spec::Unix); # ... sub case_tolerant { return 1 unless $^O eq 'cygwin' and defined &Cygwin::mount_flags; my $drive = shift; if (! $drive) { my @flags = split(/,/, Cygwin::mount_flags('/cygwin')); my $prefix = pop(@flags); if (! $prefix || $prefix eq 'cygdrive') { $drive = '/cygdrive/c'; } elsif ($prefix eq '/') { $drive = '/c'; } else { $drive = "$prefix/c"; } } my $mntopts = Cygwin::mount_flags($drive); if ($mntopts and ($mntopts =~ /,managed/)) { return 0; } eval { local @INC = @INC; pop @INC if $INC[-1] eq '.'; require Win32API::File; } or return 1; my $osFsType = "\0"x256; my $osVolName = "\0"x256; my $ouFsFlags = 0; Win32API::File::GetVolumeInformation($drive, $osVolName, 256, [], [] +, $ouFsFlags, $osFsType, 256 ); if ($ouFsFlags & Win32API::File::FS_CASE_SENSITIVE()) { return 0; } else { return 1; } } # ...
    • This code inherits all problems of File::Spec::Win32 by copying the broken code from File::Spec::Win32::case_tolerant(), and extending it with its own code.
    • Missing Cygwin::mount_flags() silently and magically makes all filesystems case insensitive, even before all problems copied from File::Spec::Win32::case_tolerant() can appear.
    • Any false value (undef, empty string, 0) passed as argumnent, including passing no argument at all, triggers the default logic that creates a Unix-style, cygwin-specific path in $drive. If that path is not detected as "managed" (by cygwin), this Unix-style path will be passed as a drive letter to GetVolumeInformation(). I expect GetVolumeInformation() to fail in that case. As in File::Spec::Win32::case_tolerant(), there is no error check, see there.

    File::Spec::*

    File::Spec::AmigaOS inherits case_tolerant() from File::Spec::Unix, so it returns false. I have no clue how AmigaOS handles file names. AmigaOS states that device names are case insensitive, so case_tolerant() should return true for device names. IIRC, old Amigas could at least read DOS floppies. Again, case_tolerant() should return true for DOS floppies. So, it looks like File::Spec::AmigaOS::case_tolerant() is broken.

    File::Spec::Epoc implements case_tolerant() to constantly return true. Again, I have no clue how EPOC a.k.a. Symbian OS handles file names.

    File::Spec::Mac implements case_tolerant() to constantly return true. This should be ok, the filesystems of classic MacOS (MFS, HFS, HFS+) are case insensitive, as are FAT-formatted floppies from DOS. I don't know if old Macs could access any other filesystems.

    File::Spec::VMS implements case_tolerant() to constantly return true. Again, no clue how VMS handles file names, or if it can mount case-sensitive filesystems. Files-11 suggest case insensitive behaviour. If case-sensitive filesystems can be mounted, this implementation is broken.

    File::Spec::OS2, used also for DOS, implements case_tolerant() to constantly return true. This should be ok for DOS and OS/2 native filesystems (FAT, HPFS). I don't know if later versions of OS/2 support case-sensitive filesystems. If they do, this implementation is broken.

    File::Spec::Functions

    package File::Spec::Functions; # ... our $VERSION = '3.75'; $VERSION =~ tr/_//d; #... my %udeps = ( # ... case_tolerant => [], # ... ); foreach my $meth (@EXPORT, @EXPORT_OK) { my $sub = File::Spec->can($meth); no strict 'refs'; if (exists($udeps{$meth}) && $sub == File::Spec::Unix->can($meth) +&& !(grep { File::Spec->can($_) != File::Spec::Unix->can($_) } @{$udeps{$meth}}) && defined(&{"File::Spec::Unix::_fn_$meth"})) { *{$meth} = \&{"File::Spec::Unix::_fn_$meth"}; } else { *{$meth} = sub {&$sub('File::Spec', @_)}; } } # ...
    • File::Spec::Functions generates function wrappers for the methods of File::Spec.
    • The generic way is a simple function that injects 'File::Spec' as first argument for the method call.
    • For methods that are implemented by File::Spec::Unix, are listed in %udeps, don't have dependencies (from %udeps) implemented in other classes, and have a function named "_fn_$meth" defined in File::Spec::Unix, that function is used instead. The intention is clear: All of those functions in File::Spec::Unix are implemented via constant, and using them saves several CPU cycles compared to wrapping a method. case_tolerant() is one of those methods, and File::Spec::Functions will choose File::Spec::Unix::_fn_case_tolerant() instead of generating a wrapper if the O/S-specific class does not implement its own case_tolerant() method.
    • File::Spec::Functions makes no attempt to speed up constant methods in O/S-specific classes.
    • The return value of File::Spec::Unix::_fn_case_tolerant() is wrong, as explained above.
    • Repairing File::Spec::Unix->case_tolerant() implies that File::Spec::Unix::_fn_case_tolerant() has to be removed, see below.

    Fixing case_tolerant() for Unix

    There seems to be no easy fix for Unix. Returning false is the wrong answer for all of the edge case shown above. Returning true is the wrong answer for all of the common cases (native filesystems).

    At least, case_tolerant() needs a path to work on, as there is no generic answer on Unix. The path should be the equivalent of a drive letter on windows, i.e. a mount point.

    For convenience, passing a filename should be treated like passing the directory containing it. (This should happen for all operating systems)

    Also for convenience, passing a directory that is not a mount point should be treated like passing the next mount point upwards the directory tree. (This should also happen for all operating systems.)

    Detecting a mount point depends on the operating system, but a general solution for Unix exists: compare the dev fields of stat($dir) and lstat("$dir/.."), if they differ, you found a mount point. Note that this method fails to detect bind mounts on Linux (according to mountpoint.c from util-linux).

    Knowing the mount point for a filesystem, you "only" have to find out if that filesystem is mounted case-sensitive or case-insensitive. And that depends on the operating system.

    For bug-compatibility to the existing File::Spec::Unix, calling case_tolerant() without arguments could continue to return false.

    Fixing case_tolerant() for Windows

    case_tolerant() should also accept directories and files deep inside a volume, as required for the fixed File::Spec::Unix->case_tolerant()

    As far as GetVolumeInformation() is concerned, mount points are either drive letters or server shares. That can easily be handled by a regexp.

    Detecting NTFS volume mount points needs more work. See https://docs.microsoft.com/en-us/windows/win32/fileio/volume-mount-points.

    GetVolumeInformationByHandleW() looks promising, but requires at least Vista / Server 2008, and it requires that you pass a file handle, not just a path.

    Fixing case_tolerant() for Cygwin

    case_tolerant() should also accept directories and files deep inside a volume, as required for the fixed File::Spec::Unix->case_tolerant()

    1. Check if the argument looks like a cygwin path
    2. If it does, find the cygwin mount point for the argument and check the mount options.
    3. Else, load File::Spec::Win32, pass the argument to File::Spec::Win32->case_tolerant() and return whatever that method returns

    Especially, do not check for Cygwin or Cycwin functions before you know you have to work with a cygwin path.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
"open" Best Practices
10 direct replies — Read more / Contribute
by haukex
on Jul 11, 2019 at 10:41

    open Best Practices

    open my $fh, '<', $filename or die "$filename: $!";

    You will see styles of open such as "open FILE, $filename;" or "open(LOG, ">$filename") || die "Could not open $filename";" in many places. These mainly come from versions of Perl before 5.6.0 (released in 2000), because that version of Perl introduced lexical filehandles and the three-argument open. Since then, these new features have become a best practice, for the reasons below.

    1. Use Lexical Filehandles

    Instead of open FILE, ..., say: open my $fh, ....

    Lexical filehandles have the advantage of not being global variables, and such filehandles will be automatically closed when the variable goes out of scope. You can use them just like any other filehandle, e.g. instead of print FILE "Output", you just say print $fh "Output". They're also more convenient to pass as parameters to subs. Also, "bareword" filehandles like FILE have a potential for conflicts with package names (see choroba's reply for details).

    2. Use the Three-Argument Form

    Instead of open my $fh, ">$filename", say: open my $fh, '>', $filename.

    In the two-argument form of open, the filename has to be parsed for the presence of mode characters such as >, <+, or |. If you say open my $fh, $filename, and $filename contains such characters, the open may not do what you want, or worse, if $filename is user input, this may be a security risk! The two-argument form can still be useful in rare cases, but I strongly recommend to play it safe and use the three-argument form instead.

    In the three-argument form, $filename will always be taken as a filename. Plus, the mode can include "layers", so instead of having to do a binmode after the open, you can just say e.g. open my $fh, "<:raw", $filename.

    3. Check and Handle Errors

    open my $fh, '<', $filename;                          # Bad: No error handling!
    open my $fh, '<', $filename  || die ...;              # WRONG!1
    open my $fh, '<', $filename  or die "open failed";    # error is missing info
    
    open my $fh, '<', $filename  or die "$filename: $!";  # good
    open(my $fh, '<', $filename) or die "$filename: $!";  # good
    open(my $fh, '<', $filename) || die "$filename: $!";  # works, but risky!1
    
    use autodie qw/open/;  # at the top of your script / code block
    open my $fh, '<', $filename;                          # ok, but read autodie!
    

    You should check the return value of the open function, and if it returns a false value, report the error that is available in the $! variable. It is best to also report the filename as well, and of course you're free to customize the message as needed (see the tips below for some suggestions).

    1 It is a common mistake to use open my $fh, '<', $filename || die ... - because of the higher precedence of ||, it actually means open( my $fh, '<', ($filename || die ...) ). So to avoid mistakes, I would suggest just staying away from || in this case (as is also highlighted in these replies by AM and eyepopslikeamosquito).

    Note that open failing does not necessarily have to be a fatal error, see some examples of alternatives here. Also, note that the effect of autodie is limited to its lexical scope, so it's possible to turn it on for only smaller blocks of code (as discussed in kcott's reply).

    4. Additional Tips

    • Make sure that the filename you're opening always matches the filename in the error message. One easy way to accomplish this is to use a single variable to hold the filename, like $filename in the above examples (as described in Eily's reply).
    • Consider putting the filename in the error message in quotes or similar, such as "'$filename': $!", so that it's easier to see issues arising from whitespace at the beginning or end of the filename (as suggested in Discipulus's reply).
    • In addition, consider adding even more useful details to your error message, such as whether you're trying to read or write from/to the file, and put quotes around $! as well, so it's easier to tell everything apart (as suggested by haj).
    • On Windows, consider also displaying $^E as part of the error message for more information (as suggested in Discipulus's reply).
    • If you're setting global variables that will affect reading the file, like $/, it's best to use local in a new block (as mentioned in stevieb's reply).
    • Remember that it's possible for multiple processes to access the same file at the same time, and you may need to consider a way to coordinate that, such as file locking (as mentioned in davido's reply).

    Fellow Monks: I wrote this so I would have something to link to instead of repeating these points again and again. If there's something you think is worth adding, please feel free to suggest it!

    Update 2019-07-12: Added section "Additional Tips", mentioned bareword filehandles, and added a bit more on autodie. Thanks to everyone for your suggestions! 2019-07-13: Added more suggestions from replies, thanks!

Perl growth in India, China, Russia, Germany and Romania
No replies — Read more | Post response
by Anonymous Monk
on Jul 05, 2019 at 17:22
    A recent comment on r/perl claims that Perl is growing "nicely" in several key nations of Europe and Asia. The author cites their own experience and google as sources:

    "If my google alerts don't mislead me Perl is growing nicely in India, China, Russia and Germany... I can testify to growth in Romania, based on the pressure the HR people are putting on me to recruit people from the places I worked before."

    I share this good news hoping it will make you happy; and also seeking confirmation from those who know where to find such information.

    https://old.reddit.com/r/perl/comments/btt9e9/every_time_i_hear_perl_is_dead_i_think_of_the/eqdno37/

RFC: Basic Testing Tutorial
3 direct replies — Read more / Contribute
by hippo
on Jul 05, 2019 at 04:24

    Fellow monks, I humbly submit this basic testing tutorial for review. All comments are welcomed - feel free to send a personal message instead of posting a reply if you so wish.

    Rationale: There are only a couple of tutorials about testing here in the Monastery and both of them concern mocking. What is missing IMHO is a nice, gentle introduction to testing with some example code to help take out that first massive leap on the learning curve. I was late to the testing game in Perl and it's a regret that I did not get into it sooner. Hopefully this will be useful to those in a similar situation.

    Updates: Add examples of is, like and skip as suggested by haukex and stevieb. Add mention of the t/ directory with prove as suggested by haukex. Add mention of done_testing() as suggested by many respondents.


    Basic Testing Tutorial

    One of the widely-acknowledged strengths of Perl is its use of testing as a cornerstone. Almost every module on CPAN comes with its own test suite and there are many modules available to help with testing and to make it as painless as possible. For those looking to add testing to an existing code base or looking to get started with TDD here is a guide to the basics.

    Test scripts and TAP

    Tests in perl are, at their heart, simple scripts which evaluate a series of boolean expressions and output the results. No doubt you could write such a script in a few minutes and the output would tell you how your test criteria have fared. However, there is a standard form to the output of the test scripts which is best adhered to. This is the Test Anything Protocol or TAP for short. By having your script output the results in this format, they can be analysed by a wealth of other programs called TAP Harnesses to provide summary data, highlight failures, etc.

    TAP is an open protocol but was written for Perl originally. It is very simple at its heart. The first line of output is a range of test numbers starting at 1 and each subsequent line consists of three fields: a pass/fail flag which is either "ok" or "not ok", the number of the current test and an optional description of that test. Thus a script containing a single, passing test might output this when run:

    1..1 ok 1 The expected file "foo" is present

    A first test script

    So, let's write a simple test script which will output TAP. Suppose we want to check that nobody is running our code far in the past. Here would be a trivial test.

    use strict; use warnings; use Time::Piece; print "1..1\n"; print localtime(time)->year > 1999 ? 'ok' : 'not ok'; print " 1 Not in a previous century\n";

    When we run this without time-travelling we see this output:

    1..1 ok 1 Not in a previous century

    and we see that our test has passed.

    Test modules

    Of course there are modules on CPAN to help with testing. The simplest of these is, appropriately enough, Test::Simple which is a core module. It is little more than a wrapper with 2 handy functions to ensure that your TAP output is in the correct format. We can rewrite our simple century test with this module:

    use strict; use warnings; use Time::Piece; use Test::Simple tests => 1; ok localtime(time)->year > 1999, 'Not in a previous century';

    Now there are no print statements because the module takes care of all the output. The tests => 1 on line 4 sets the number of tests we expect to run, so we no longer need to print "1..1\n" ourselves. Similarly, the ok function evaluates the first argument as a boolean expresssion and outputs the correct TAP line as a result. The second argument is the optional description.

    Technically it is optional but I would encourage you very strongly to include a description for any test. If you have a script with say 50 tests in it and test 37 fails but has no description, how will you know what is wrong? Make life easy for yourself (and your collaborators and even the users) by describing each test in the TAP output.

    Other testing functions

    While the ok function is useful, the output is a simple pass/fail - it doesn't say how it failed. If our century test fails we don't know what year it thinks it is. For that we would need to write more code or use code someone else has written. Fortunately there is a plethora of other testing modules to choose from, the most common of which is Test::More (also in core). This gives us a heap of other functions so that we can easily perform different types of evaluations and receive better feedback when they fail.

    Let's use Test::More and its handy cmp_ok function in our script.

    use strict; use warnings; use Time::Piece; use Test::More tests => 1; cmp_ok localtime(time)->_year, '>', 1999, 'Not in a previous century';

    Note that I've introduced a bug here (using _year instead of year) so that the test will likely fail. Now our test output looks like this:

    1..1 not ok 1 - Not in a previous century # Failed test 'Not in a previous century' # at /tmp/bar.t line 6. # '119' # > # '1999' # Looks like you failed 1 test of 1.

    We can see at a glance what is being tested and that the year we actually have (119) is clearly wrong so we need to fix the bug. All lines in TAP which start with a hash (#) are comments for the reader: Test::More and friends use this to give us verbose reports about how things have gone wrong.

    There are a number of other useful comparator functions in Test::More such as is for simple equality, like for regex and so on. These are fully explained in the Test::More documentation, but their usage is quite straightforward. Let's add a couple of other tests to see how they are used.

    use strict; use warnings; use Time::Piece; use Test::More tests => 3; my $now = localtime (time); cmp_ok $now->_year, '>', 1999, 'Not in a previous century'; is $now->time, $now->hms, 'The time() and hms() methods give the same +result'; like $now->fullday, qr/day$/, 'The dayname ends in "day"';

    There are also control flow structures such as skip to avoid running tests in certain circumstances such as an invalid underlying O/S or absence of a particular module. We could use this here to skip the test of the dayname if a non-English locale applies.

    use strict; use warnings; use Time::Piece 1.31_02; use Test::More tests => 3; Time::Piece->use_locale; my $now = localtime (time); cmp_ok $now->_year, '>', 1999, 'Not in a previous century'; is $now->time, $now->hms, 'The time() and hms() methods give the same +result'; SKIP: { skip 'Non-English locale', 1 unless substr ($ENV{LANG} // 'en', 0, + 2) eq 'en'; like $now->fullday, qr/day$/, 'The dayname ends in "day"'; }

    Further still there are other modules in the Test::* namespace to help with all manner of scenarios.

    Working to a plan

    It may be the case that the precise number of tests in the script is not known or may change frequently. In those situations, specifying the number of tests like use Test::More tests => 3; can become unwieldy or problematic. Instead we can just use Test::More; and then specify the plan later.

    One method of doing this is to call plan () as a stand-alone statement. If the number of tests is dependent on an array which is only computed at run time we could write

    plan tests => scalar @array;

    once the array has been populated.

    Another approach is to use done_testing (scalar @array); but as its name suggests this must only be called after the final test has been run. The number of tests can even be omitted entirely here but that removes the check that all the tests expected have indeed run, of course.

    done_testing (); exit;

    Using a harness

    If you have installed a module from CPAN you will probably have noticed the test phase running. You can use the same harness on your own test scripts by running the prove command. By default this condenses the results of tests and at the end provides a summary of which tests in which files have failed, how long the run took, etc. eg:

    $ prove /tmp/bar.t /tmp/bar.t .. 1/3 # Failed test 'Not in a previous century' # at /tmp/bar.t line 8. # '119' # > # '1999' # Looks like you failed 1 test of 3. /tmp/bar.t .. Dubious, test returned 1 (wstat 256, 0x100) Failed 1/3 subtests (less 1 skipped subtest: 1 okay) Test Summary Report ------------------- /tmp/bar.t (Wstat: 256 Tests: 3 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=3, 0 wallclock secs ( 0.03 usr 0.00 sys + 0.06 cusr + 0.01 csys = 0.10 CPU) Result: FAIL

    This is particularly useful for larger projects with many scripts/modules each of which has many tests. If prove is run with no arguments it will look for files matching t/*.t and run all of those in sequence.

    Test Driven Development

    Now that you can test your code you can consider TDD as a methodology. By writing the tests before the code you are setting out what you expect the code to do - it's a formal representation of the specification. Doing so is a skill in itself and many people make a career out of being a tester.

    See Also

Refactoring just to refactor?
8 direct replies — Read more / Contribute
by Lady_Aleena
on Jun 27, 2019 at 20:25

    Hello everyone.

    I wrote the following subroutine recently using map and grep in the BLOCK LIST style. Line 4 had gotten long, so as you can see, I put the map, grep, and sort on separate lines. After doing that and seeing how good it looked, I began thinking about all the times I used map and grep in the EXPR, LIST format and that I may want to rewrite all of them.

    Most, if not all, other people I have chatted with over my years here use the BLOCK LIST format instead of the EXPR, LIST format. I stubbornly stuck with the EXPR, LIST format. Now I am not sure if I am thinking of refactoring to use the format is normally used or if this is refactoring just to refactor. So, I am on the fence on whether or not I should do it.

    What do you think?

    No matter how hysterical I get, my problems are not time sensitive. So, relax, have a cookie, and a very nice day!
    Lady Aleena

Add your Meditation
Title:
Meditation:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":


  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?
    Username:
    Password:

    What's my password?
    Create A New User
    Chatterbox?
    and the web crawler heard nothing...

    How do I use this? | Other CB clients
    Other Users?
    Others lurking in the Monastery: (4)
    As of 2019-08-22 20:03 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found

      Notices?