Perl / perl5

🐪 The Perl programming language
https://dev.perl.org/perl5/
Other
1.97k stars 555 forks source link

perldoc(sort) bugs #16360

Open p5pRT opened 6 years ago

p5pRT commented 6 years ago

Migrated from rt.perl.org#132716 (status was 'new')

Searchable as RT132716$

p5pRT commented 6 years ago

From jrw32982@gmail.com

This applies from perl 5.8 - 5.26

The perldoc(sort) documentation has long had bugs in it\, relative to its suggestions for the syntax for sorting when using a list-generator function or a user-defined comparator function. It gives suggestions\, but the suggestions do not take into account the variety of syntaxes that can be used\, and their consequences.

The test program included below shows many of these effects. My suggestions​: do not ever use parentheses around the sort arguments or around the list to be sorted\, since that can cause compiler errors or misinterpretation of the function names. Do not use the form &generatorfunc since that will cause a compiler error when the generatorfunc name has no underscore in it. To simplify usage\, use only these forms​:

  sort @​list_to_be_sorted   sort cmpfunc @​list_to_be_sorted   sort +genfunc @​args_to_genfunc # list generator function   sort cmpfunc +genfunc @​args_to_genfunc # comparator function   sort map map_func\, @​args_to_map # built-in list generator (map)   sort cmpfunc map map_func\, @​args_to_map

Here are my comments on the specific text from perldoc(sort)​:

  #------------------------------------------------------------------------   Warning​: syntactical care is required when sorting the list returned from   a function. If you want to sort the list returned by the function call   find_records(@​key)\, you can use​:

  @​contact = sort { $a cmp $b } find_records @​key; # ok   @​contact = sort +find_records(@​key); # best\, with or without parens   @​contact = sort &find_records(@​key); # requires parens   @​contact = sort(find_records(@​key)); # worst\, requires inner parens\,   # which must be adjacent to the   # function name (otherwise the   # function will be used as a   # comparator function)!

  If instead you want to sort the array @​key with the comparison routine   find_records() then you can use​:

  @​contact = sort { find_records() } @​key; # ok   @​contact = sort find_records(@​key); # best\, with or without parens   @​contact = sort(find_records @​key); # bad\, compiler warning if the   # comparator function name has   # no underscore   @​contact = sort(find_records (@​key)); # worst\, if inner parens are   # adjacent to function name\,   # sort() will sort the output   # of find_records(@​key);   # compiler warning if the   # comparator function name has   # no underscore   #------------------------------------------------------------------------

Here is my test program to demonstrate some of the weird results when using parentheses\, with or without a space in front of them\, combined with different forms of arguments for sort().

#!/usr/bin/perl #****************************************************************************** # Test the use of sort with various user-defined and built-in functions. # Especially take into consideration placement of parentheses. # # The user-defined comparator functions (c and ncmp) do *not* have underscores # in their names\, which causes the parser to emit compile-time warnings # ("Unquoted string "COMPARATOR-NAME" may clash with future reserved word")\, # even though the function has been declared before being used. # # Also discovered​: parentheses are required around the generator function's # arguments\, if the form &generator is used\, or a fatal compile-time error is # emitted ("Array found where operator expected"\, followed by "syntax error"). # # Finally\, the use or lack of use of parentheses and *the placement of the # parentheses* is critical to the correct interpretation of the arguments to # sort(). #******************************************************************************

use strict; use warnings;

use Test​::More qw(no_plan);

my @​list = qw( c b e d ); my $clist = [ sort @​list ]; my $glist = [ sort "a"\, @​list ];

sub g { return ( "a"\, @​_ ) } # generate list sub c { return $a cmp $b } # sort comparator sub ncmp { return $a \<=> $b } # numeric comparator sub p { return @​_ } # pass-through

#******************************************************************************

our @​w; my $w = 0; BEGIN { $SIG{__WARN__} = sub { push @​w\, "@​_" } }

sub w {   my ($lnum) = @​_;   return "WARN-$w" if $w[$w++] =~ /may clash.*$lnum/;   die "unexpected​: $w[$w - 1]\n"; }

sub e {   return "COMPILE-ERROR" if $@​ =~ /syntax error/;   die "unexpected​: \$@&#8203;\n"; }

sub compile {   local $SIG{__WARN__} = sub {};   undef $@​;   eval $_[0]; }

like $^V\,   qr/^( \x05\x08\x04   | \x05\x08\x08   | v5[.]   ( 10[.]1   | 16[.]3   | 18[.]2   | 22[.]2   | 24[.]1   | 26[.]0   )   )\z/x\, 'perl version'; ok 1\, "";

is_deeply [ sort @​list ]\, $clist\, " OK! sort L"; is_deeply [ sort( @​list) ]\, $clist\, " OK! sort( L)"; is_deeply [ sort (@​list) ]\, $clist\, " OK! sort (L)"; ok 1\, "";

is_deeply [ sort { $a cmp $b } @​list ]\, $clist\, " OK! sort {\<=>} L"; is_deeply [ sort { c } @​list ]\, $clist\, " OK! sort {c } L"; is_deeply [ sort { c() } @​list ]\, $clist\, " OK! sort {c()} L"; ok 1\, "";

is_deeply [ sort( { $a cmp $b } @​list) ]\, $clist\, "OK! sort( {\<=>} L)"; is_deeply [ sort( { c } @​list) ]\, $clist\, "OK! sort( {c } L)"; is_deeply [ sort( { c() } @​list) ]\, $clist\, "OK! sort( {c()} L)"; ok 1\, "";

is_deeply [ sort ({ $a cmp $b } @​list) ]\, $clist\, "OK! sort ({\<=>} L)"; is_deeply [ sort ({ c } @​list) ]\, $clist\, "OK! sort ({c } L)"; is_deeply [ sort ({ c() } @​list) ]\, $clist\, "OK! sort ({c()} L)"; ok 1\, "";

is_deeply [ sort { $a cmp $b } g @​list ]\, $glist\, "OK! sort {\<=>} g L"; is_deeply [ sort { c } g @​list ]\, $glist\, "OK! sort {c } g L"; is_deeply [ sort { c() } g @​list ]\, $glist\, "OK! sort {c()} g L"; ok 1\, "";

is_deeply [ sort( { $a cmp $b } g @​list) ]\, $glist\, "OK! sort( {\<=>} g L)"; is_deeply [ sort( { c } g @​list) ]\, $glist\, "OK! sort( {c } g L)"; is_deeply [ sort( { c() } g @​list) ]\, $glist\, "OK! sort( {c()} g L)"; ok 1\, "";

is_deeply [ sort ({ $a cmp $b } g @​list) ]\, $glist\, "OK! sort ({\<=>} g L)"; is_deeply [ sort ({ c } g @​list) ]\, $glist\, "OK! sort ({c } g L)"; is_deeply [ sort ({ c() } g @​list) ]\, $glist\, "OK! sort ({c()} g L)"; ok 1\, "";

compile ' sort &g @​list '; ok $@​\, " sort &g L @​{[e]}"; is_deeply [ sort &g( @​list) ]\, $glist\, "ok sort &g( L)"; is_deeply [ sort &g (@​list) ]\, $glist\, "ok sort &g (L)"; ok 1\, "";

compile ' sort( &g @​list ) '; ok $@​\, " sort( &g L ) @​{[e]}"; is_deeply [ sort( &g( @​list)) ]\, $glist\, "ok sort( &g( L))"; is_deeply [ sort( &g (@​list)) ]\, $glist\, "ok sort( &g (L))"; ok 1\, "";

compile ' sort (&g @​list ) '; ok $@​\, " sort (&g L ) @​{[e]}"; is_deeply [ sort (&g( @​list)) ]\, $glist\, "ok sort (&g( L))"; is_deeply [ sort (&g (@​list)) ]\, $glist\, "ok sort (&g (L))"; ok 1\, "";

is_deeply [ sort +g @​list ]\, $glist\, "OK! sort +g L"; is_deeply [ sort +g( @​list) ]\, $glist\, "OK! sort +g( L)"; is_deeply [ sort +g (@​list) ]\, $glist\, "OK! sort +g (L)"; ok 1\, "";

is_deeply [ sort( +g @​list ) ]\, $glist\, "OK! sort( +g L )"; is_deeply [ sort( +g( @​list)) ]\, $glist\, "OK! sort( +g( L))"; is_deeply [ sort( +g (@​list)) ]\, $glist\, "OK! sort( +g (L))"; ok 1\, "";

is_deeply [ sort (+g @​list ) ]\, $glist\, "OK! sort (+g L )"; is_deeply [ sort (+g( @​list)) ]\, $glist\, "OK! sort (+g( L))"; is_deeply [ sort (+g (@​list)) ]\, $glist\, "OK! sort (+g (L))"; ok 1\, "";

is_deeply [ sort c @​list ]\, $clist\, "OK! sort c L"; is_deeply [ sort c( @​list) ]\, $clist\, "OK! sort c( L)"; is_deeply [ sort c (@​list) ]\, $clist\, "OK! sort c (L)"; ok 1\, "";

is_deeply [ sort( c @​list ) ]\, $clist\, " sort( c L ) @​{[w __LINE__]}"; is_deeply [ sort( g( @​list)) ]\, $glist\, " sort( g( L)) NOT-COMPARATOR"; is_deeply [ sort( c (@​list)) ]\, $clist\, " sort( c (L)) @​{[w __LINE__]}"; ok 1\, "";

is_deeply [ sort (c @​list ) ]\, $clist\, " sort (c L ) @​{[w __LINE__]}"; is_deeply [ sort (g( @​list)) ]\, $glist\, " sort (g( L)) NOT-COMPARATOR"; is_deeply [ sort (c (@​list)) ]\, $clist\, " sort (c (L)) @​{[w __LINE__]}"; ok 1\, "";

is_deeply [ sort c g @​list ]\, $glist\, "OK! sort c g L"; is_deeply [ sort c( g @​list) ]\, $glist\, "OK! sort c( g L)"; is_deeply [ sort c ( g @​list) ]\, $glist\, "OK! sort c (g L)"; ok 1\, "";

is_deeply [ sort c +g @​list ]\, $glist\, "OK! sort c +g L"; is_deeply [ sort c( +g @​list) ]\, $glist\, "OK! sort c( +g L)"; is_deeply [ sort c (+g @​list) ]\, $glist\, "OK! sort c (+g L)"; ok 1\, "";

is_deeply [ sort( c g @​list )]\, $glist\, " sort( c g L ) @​{[w __LINE__]}"; is_deeply [ sort( p( g @​list))]\, $glist\, " sort( p( g L)) NOT-COMPARATOR"; is_deeply [ sort( c ( g @​list))]\, $glist\, " sort( c (g L)) @​{[w __LINE__]}"; ok 1\, "";

is_deeply [ sort (c g @​list ) ]\, $glist\, " sort (c g L ) @​{[w __LINE__]}"; is_deeply [ sort (p( g @​list)) ]\, $glist\, " sort (p( g L)) NOT-COMPARATOR"; is_deeply [ sort (c (g @​list)) ]\, $glist\, " sort (c (g L)) @​{[w __LINE__]}"; ok 1\, "";

my @​nlist = ( 50\, 51\, 49 ); my @​slist = (qw( 2 3 1 )); my $snlist = [ sort @​nlist ]; my $sslist = [ sort @​slist ];

is_deeply [ sort map chr\, @​nlist ]\, $sslist\, "OK! sort map mf\, L"; is_deeply [ sort +map chr\, @​nlist ]\, $sslist\, "OK! sort +map mf\, L"; is_deeply [ sort ncmp map $_\, @​nlist ]\, $snlist\, "OK! sort c map mf\, L"; is_deeply [ sort ncmp +map $_\, @​nlist ]\, $snlist\, "OK! sort c +map mf\, L"; ok 1\, "";

is_deeply [ sort( map chr\, @​nlist) ]\, $sslist\, "ok sort( map mf\, L)"; is_deeply [ sort( +map chr\, @​nlist) ]\, $sslist\, "ok sort( +map mf\, L)"; is_deeply [ sort( ncmp map $_\, @​nlist) ]\, $snlist\, " sort( c map mf\, L) @​{[w __LINE__]}"; is_deeply [ sort( ncmp +map $_\, @​nlist) ]\, $snlist\, " sort( c +map mf\, L) @​{[w __LINE__]}"; ok 1\, "";

is_deeply [ sort ( map chr\, @​nlist) ]\, $sslist\, "ok sort ( map mf\, L)"; is_deeply [ sort ( +map chr\, @​nlist) ]\, $sslist\, "ok sort ( +map mf\, L)"; is_deeply [ sort (ncmp map $_\, @​nlist) ]\, $snlist\, " sort (c map mf\, L) @​{[w __LINE__]}"; is_deeply [ sort (ncmp +map $_\, @​nlist) ]\, $snlist\, " sort (c +map mf\, L) @​{[w __LINE__]}"; ok 1\, "";

is $#w\, $w-1\, "warning_count";

# done_testing(); # not available in perl 5.8 is "DONE"\, "DONE"\, "done";

#******************************************************************************

Perl Info ``` Flags: category=docs severity=medium Site configuration information for perl 5.18.2: Configured by Debian Project at Fri Nov 10 12:46:50 UTC 2017. Summary of my perl5 (revision 5 version 18 subversion 2) configuration: Platform: osname=linux, osvers=4.4.0-98-generic, archname=x86_64-linux-gnu-thread-multi uname='linux lgw01-amd64-032 4.4.0-98-generic #121-ubuntu smp tue oct 10 14:24:03 utc 2017 x86_64 x86_64 x86_64 gnulinux ' config_args='-Dusethreads -Duselargefiles -Dccflags=-DDEBIAN -D_FORTIFY_SOURCE=2 -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Dldflags= -Wl,-Bsymbolic-functions -Wl,-z,relro -Dlddlflags=-shared -Wl,-Bsymbolic-functions -Wl,-z,relro -Dcccdlflags=-fPIC -Darchname=x86_64-linux-gnu -Dprefix=/usr -Dprivlib=/usr/share/perl/5.18 -Darchlib=/usr/lib/perl/5.18 -Dvendorprefix=/usr -Dvendorlib=/usr/share/perl5 -Dvendorarch=/usr/lib/perl5 -Dsiteprefix=/usr/local -Dsitelib=/usr/local/share/perl/5.18.2 -Dsitearch=/usr/local/lib/perl/5.18.2 -Dman1dir=/usr/share/man/man1 -Dman3dir=/usr/share/man/man3 -Dsiteman1dir=/usr/local/man/man1 -Dsiteman3dir=/usr/local/man/man3 -Duse64bitint -Dman1ext=1 -Dman3ext=3perl -Dpager=/usr/bin/sensible-pager -Uafs -Ud_csh -Ud_ualarm -Uusesfio -Uusenm -Ui_libutil -Uversiononly -DDEBUGGING=-g -Doptimize=-O2 -Duseshrplib -Dlibperl=libperl.so.5.18.2 -des' hint=recommended, useposix=true, d_sigaction=define useithreads=define, usemultiplicity=define useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef use64bitint=define, use64bitall=define, uselongdouble=undef usemymalloc=n, bincompat5005=undef Compiler: cc='cc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fstack-protector -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64', optimize='-O2 -g', cppflags='-D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fstack-protector -fno-strict-aliasing -pipe -I/usr/local/include' ccversion='', gccversion='4.8.4', gccosandvers='' intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678 d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16 ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8 alignbytes=8, prototype=define Linker and Libraries: ld='cc', ldflags =' -fstack-protector -L/usr/local/lib' libpth=/usr/local/lib /lib/x86_64-linux-gnu /lib/../lib /usr/lib/x86_64-linux-gnu /usr/lib/../lib /lib /usr/lib libs=-lgdbm -lgdbm_compat -ldb -ldl -lm -lpthread -lc -lcrypt perllibs=-ldl -lm -lpthread -lc -lcrypt libc=, so=so, useshrplib=true, libperl=libperl.so.5.18.2 gnulibc_version='2.19' Dynamic Linking: dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E' cccdlflags='-fPIC', lddlflags='-shared -L/usr/local/lib -fstack-protector' Locally applied patches: DEBPKG:debian/cpan_definstalldirs - Provide a sensible INSTALLDIRS default for modules installed from CPAN. DEBPKG:debian/db_file_ver - http://bugs.debian.org/340047 Remove overly restrictive DB_File version check. DEBPKG:debian/doc_info - Replace generic man(1) instructions with Debian-specific information. DEBPKG:debian/enc2xs_inc - http://bugs.debian.org/290336 Tweak enc2xs to follow symlinks and ignore missing @INC directories. DEBPKG:debian/errno_ver - http://bugs.debian.org/343351 Remove Errno version check due to upgrade problems with long-running processes. DEBPKG:debian/libperl_embed_doc - http://bugs.debian.org/186778 Note that libperl-dev package is required for embedded linking DEBPKG:fixes/respect_umask - Respect umask during installation DEBPKG:debian/writable_site_dirs - Set umask approproately for site install directories DEBPKG:debian/extutils_set_libperl_path - EU:MM: Set location of libperl.a to /usr/lib DEBPKG:debian/no_packlist_perllocal - Don't install .packlist or perllocal.pod for perl or vendor DEBPKG:debian/prefix_changes - Fiddle with *PREFIX and variables written to the makefile DEBPKG:debian/fakeroot - Postpone LD_LIBRARY_PATH evaluation to the binary targets. DEBPKG:debian/instmodsh_doc - Debian policy doesn't install .packlist files for core or vendor. DEBPKG:debian/ld_run_path - Remove standard libs from LD_RUN_PATH as per Debian policy. DEBPKG:debian/libnet_config_path - Set location of libnet.cfg to /etc/perl/Net as /usr may not be writable. DEBPKG:debian/mod_paths - Tweak @INC ordering for Debian DEBPKG:debian/module_build_man_extensions - http://bugs.debian.org/479460 Adjust Module::Build manual page extensions for the Debian Perl policy DEBPKG:debian/prune_libs - http://bugs.debian.org/128355 Prune the list of libraries wanted to what we actually need. DEBPKG:fixes/net_smtp_docs - [rt.cpan.org #36038] http://bugs.debian.org/100195 Document the Net::SMTP 'Port' option DEBPKG:debian/perlivp - http://bugs.debian.org/510895 Make perlivp skip include directories in /usr/local DEBPKG:debian/cpanplus_definstalldirs - http://bugs.debian.org/533707 Configure CPANPLUS to use the site directories by default. DEBPKG:debian/cpanplus_config_path - Save local versions of CPANPLUS::Config::System into /etc/perl. DEBPKG:debian/deprecate-with-apt - http://bugs.debian.org/702096 Point users to Debian packages of deprecated core modules DEBPKG:debian/squelch-locale-warnings - http://bugs.debian.org/508764 Squelch locale warnings in Debian package maintainer scripts DEBPKG:debian/skip-upstream-git-tests - Skip tests specific to the upstream Git repository DEBPKG:debian/patchlevel - http://bugs.debian.org/567489 List packaged patches for 5.18.2-2ubuntu1.3 in patchlevel.h DEBPKG:debian/skip-kfreebsd-crash - http://bugs.debian.org/628493 [perl #96272] Skip a crashing test case in t/op/threads.t on GNU/kFreeBSD DEBPKG:fixes/document_makemaker_ccflags - http://bugs.debian.org/628522 [rt.cpan.org #68613] Document that CCFLAGS should include $Config{ccflags} DEBPKG:debian/find_html2text - http://bugs.debian.org/640479 Configure CPAN::Distribution with correct name of html2text DEBPKG:debian/hurd_test_skip_stack - http://bugs.debian.org/650175 Disable failing GNU/Hurd tests dist/threads/t/stack.t DEBPKG:fixes/manpage_name_Test-Harness - http://bugs.debian.org/650451 [ rt.cpan.org #73399] cpan/Test-Harness: add NAME headings in modules with POD DEBPKG:debian/makemaker-pasthru - http://bugs.debian.org/660195 [ rt.cpan.org #28632] Make EU::MM pass LD through to recursive Makefile.PL invocations DEBPKG:debian/perl5db-x-terminal-emulator.patch - http://bugs.debian.org/668490 Invoke x-terminal-emulator rather than xterm in perl5db.pl DEBPKG:debian/cpan-missing-site-dirs - http://bugs.debian.org/688842 Fix CPAN::FirstTime defaults with nonexisting site dirs if a parent is writable DEBPKG:fixes/memoize_storable_nstore - [rt.cpan.org #77790] http://bugs.debian.org/587650 Memoize::Storable: respect 'nstore' option not respected DEBPKG:fixes/net_ftp_failed_command - [rt.cpan.org #37700] http://bugs.debian.org/491062 Net::FTP: cope gracefully with a failed command DEBPKG:fixes/perlbug-patchlist - [3541c11] http://bugs.debian.org/710842 [perl #118433] Make perlbug look up the list of local patches at run time DEBPKG:fixes/module_metadata_security_doc - [68cdd4b] CVE-2013-1437 documentation fix DEBPKG:fixes/module_metadata_taint_fix - [bff978f] http://bugs.debian.org/722210 [rt.cpan.org #88576] untaint version, if needed, in Module::Metadata DEBPKG:fixes/IPC-SysV-spelling - http://bugs.debian.org/730558 [ rt.cpan.org #86736] Fix spelling of IPC_CREAT in IPC-SysV documentation DEBPKG:fixes/fix-undef-source - DEBPKG:fixes/CVE-2013-7422.patch - [PATCH] [perl #119505] Segfault from bad backreference DEBPKG:fixes/CVE-2014-4330.patch - [PATCH] don't recurse infinitely in Data::Dumper DEBPKG:fixes/CVE-2016-2381.patch - [PATCH 1/2] remove duplicate environment variables from environ DEBPKG:CVE-2017-12837.patch - [PATCH] regcomp [perl #131582] DEBPKG:CVE-2017-12883.patch - [PATCH] PATCH: [perl #131598] @INC for perl 5.18.2: /home/jrw32982/cfg/perl_local_lib/lib/perl5/5.18.2/x86_64-linux-gnu-thread-multi /home/jrw32982/cfg/perl_local_lib/lib/perl5/5.18.2 /home/jrw32982/cfg/perl_local_lib/lib/perl5/x86_64-linux-gnu-thread-multi /home/jrw32982/cfg/perl_local_lib/lib/perl5 /home/jrw32982/std/lib/perl /jrw/mdst/lib /jrw/mdst/lib/locallib /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /home/jrw32982/cfg/perl_local_lib/lib/perl5/5.18.1 /usr/local/lib/site_perl . Environment for perl 5.18.2: HOME=/home/jrw32982 LANG=en_US.UTF-8 LANGUAGE (unset) LC_ALL=C LD_LIBRARY_PATH (unset) LOGDIR (unset) PATH=/home/jrw32982/cfg/perl_local_lib/bin:/home/jrw32982/std/bin:/home/jrw32982/sh:/home/jrw32982/bin:/jrw/mdst/sh:/jrw/mdst/sh/GAMES:/jrw/mdst/binu:/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/games:/usr/bin/X11:/etc:. PERL5LIB=/home/jrw32982/cfg/perl_local_lib/lib/perl5:/home/jrw32982/std/lib/perl:/jrw/mdst/lib:/jrw/mdst/lib/locallib PERLDB_OPTS=NonStop frame=2 PERL_BADLANG (unset) PERL_LOCAL_LIB_ROOT=/home/jrw32982/cfg/perl_local_lib PERL_MB_OPT=--install_base "/home/jrw32982/cfg/perl_local_lib" PERL_MM_OPT=INSTALL_BASE=/home/jrw32982/cfg/perl_local_lib SHELL=/bin/bash ```