perlude-0.61000755001752000144 012524003657 12025 5ustar00mcusers000000000000README100644001752000144 67112524003657 12752 0ustar00mcusers000000000000perlude-0.61 This archive contains the distribution perlude, version 0.61: Shell and Powershell pipes, haskell keywords mixed with the awesomeness of perl. forget shell scrpting now! This software is copyright (c) 2012 by Marc Chantreux. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. This README file was generated by Dist::Zilla::Plugin::Readme v5.036. Changes100644001752000144 552312524003657 13406 0ustar00mcusers000000000000perlude-0.61Revision history for Perlude 0.61 2015-05-11 Marc Chantreux * removed FUTURE text file as it's now present (also some bloat canceled) * reviewed all the PODs, seems to be complete and in the good place now * installation tested and approved by Travis * improved test suite 0.60 2015-05-04 Marc Chantreux * installation and documentation cleanups 0.58 2014-02-18 Marc Chantreux * t/pod.t fixed encoding 0.57 2014-02-17 Marc Chantreux * t/pod.t now pass with the very last Test::Pod version * Perlude::Lazy and Perlude::builtins are now in $VERSION '0.0' (PAUSE indexer will appreciate) 0.56 2014-02-13 Marc Chantreux * remove ruins from the Module::Starter era * removed deprecated files * README becomes README.textile * added example from the euler project (see eg/euler) * fix all known bugs (some where fixed for long time) * merged MetaJSON pull request (fix #86899) (thanks Dolmen) * this file (Changes) updated (fix RT#86923) (thanks Dolmen) * new features * Perlude::Open (open_file) * chunksOf (named after http://hackage.haskell.org/package/text-1.1.0.0/docs/Data-Text.html#v:chunksOf). * lines rewritten * now uses open_file, so much richier prototype * no longer relies on &records in the first era of Perlude, it was decided to reuse a lot of Perlude to write Perlude. that's why we used apply {} on records. however, i broken this law for lines because: * this part of Perlude is stable for years now * lines performance is critical enough to break laws 0.55 2013-01-06 Marc Chantreux * &nth consumer added to pick the nth element of a stream * minor doc fixing 0.54 2012-11-27 Marc Chantreux * Updated documentation * README to README.textile * POD from lib/Perlude.pm to lib/Perlude.pod * lot of improvement of tutorial (yet unfinished) * Module::Starter replaced by Dist::Zilla * &pairs iterator added (on pairs of an associative array) * lines now coerce path to file handle * traverse is now DEPRECATED: use &now instead 0.53 Never released 0.52 Never released 0.51 2011-08-20 Marc Chantreux - Burak Gürsoy CPAN installation * now works thanks to Burak Back to the roots * () remains * lazy lists are now in Perlude::Lazy * Perlude namespace back to the roots (just streams) * traverse is replaced by now * traverse will be removed in 0.60 0.50 2011-08-20 Marc Chantreux - Philippe Bruhat - Olivier Mengué Rewrite from FPW * () replaces undef as terminator * lazy list added * enlist added 0.42 2011-06-14 Marc Chantreux First version, released on an unsuspecting world. t000755001752000144 012524003657 12211 5ustar00mcusers000000000000perlude-0.61pod.t100644001752000144 35012524003657 13276 0ustar00mcusers000000000000perlude-0.61/t#!perl -T use strict; use warnings; use Test::More; # Ensure a recent version of Test::Pod my $min_tp = 1.22; eval "use Test::Pod $min_tp"; plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; all_pod_files_ok(); LICENSE100644001752000144 4366312524003657 13147 0ustar00mcusers000000000000perlude-0.61This software is copyright (c) 2012 by Marc Chantreux. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2012 by Marc Chantreux. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2012 by Marc Chantreux. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End dist.ini100644001752000144 35312524003657 13533 0ustar00mcusers000000000000perlude-0.61name = perlude author = Marc Chantreux license = Perl_5 copyright_holder = Marc Chantreux copyright_year = 2012 [@Basic] [Prereqs] Carp = 0 [ExecDir] dir = scripts [VersionFromModule] [MetaJSON] [GithubMeta] META.yml100644001752000144 113712524003657 13361 0ustar00mcusers000000000000perlude-0.61--- abstract: 'Shell and Powershell pipes, haskell keywords mixed with the awesomeness of perl. forget shell scrpting now! ' author: - 'Marc Chantreux ' build_requires: {} configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 5.036, CPAN::Meta::Converter version 2.142690' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: perlude requires: Carp: '0' resources: homepage: https://github.com/eiro/p5-perlude repository: https://github.com/eiro/p5-perlude.git version: '0.61' MANIFEST100644001752000144 170512524003657 13242 0ustar00mcusers000000000000perlude-0.61# This file was automatically generated by Dist::Zilla::Plugin::Manifest v5.036. Changes LICENSE MANIFEST META.json META.yml Makefile.PL README README.md dist.ini eg/euler.pl lib/Perlude.pm lib/Perlude.pod lib/Perlude/Lazy.pm lib/Perlude/Open.pm lib/Perlude/Tutorial.pod lib/Perlude/builtins.pm t/00_open_file.t t/data/brian t/lazy/01_enlist.t t/lazy/10_fold.t t/lazy/11_consumers.t t/lazy/12_buffer.t t/lazy/13_traverse.t t/lazy/14_tuple.t t/lazy/15_range.t t/lazy/16_lines.t t/lazy/17_concat.t t/lazy/20-old-take.t t/lazy/21-old-takeWhile.t t/lazy/22-old-range.t t/lazy/30_builtins.t t/lazy/pod-coverage.t t/perlude-test-lines-data t/pod-coverage.t t/pod.t t/stream/10_fold.t t/stream/11_consumers.t t/stream/12_buffer.t t/stream/13_traverse.t t/stream/14_tuple.t t/stream/15_range.t t/stream/16_lines.t t/stream/17_concat.t t/stream/18_chunksOf.t t/stream/19_twice.t t/stream/20-old-take.t t/stream/21-old-takeWhile.t t/stream/22-old-range.t t/stream/23_pairs.t README.md100644001752000144 12412524003657 13342 0ustar00mcusers000000000000perlude-0.61read the pods for more documentation. * lib/Perlude.pod * lib/Perlude/Tutorial.pod META.json100644001752000144 175612524003657 13540 0ustar00mcusers000000000000perlude-0.61{ "abstract" : "Shell and Powershell pipes, haskell keywords mixed with the awesomeness of perl. forget shell scrpting now! ", "author" : [ "Marc Chantreux " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 5.036, CPAN::Meta::Converter version 2.142690", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "perlude", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Carp" : "0" } } }, "release_status" : "stable", "resources" : { "homepage" : "https://github.com/eiro/p5-perlude", "repository" : { "type" : "git", "url" : "https://github.com/eiro/p5-perlude.git", "web" : "https://github.com/eiro/p5-perlude" } }, "version" : "0.61" } eg000755001752000144 012524003657 12341 5ustar00mcusers000000000000perlude-0.61euler.pl100644001752000144 105412524003657 14152 0ustar00mcusers000000000000perlude-0.61/eg#! /usr/bin/perl use Eirotic; sub sum ($xs) { my $sum = 0; now {state $sum=0; $sum+=$_ } $xs } say "euler 1: ", sum filter { not ( ($_ % 3) ||($_ % 5)) } range 1, 1000; sub fibo (@seed) { sub { push @seed, $seed[0] + $seed[1]; shift @seed; } } say "euler 2: ", sum filter { not ($_ % 2) } takeWhile { $_ < 4_000_000 } fibo 1, 1; sub is_prime ($x=$_) { map { return 0 unless $x % $_ } 2..($x-1); 1 } say now {$_} filter {is_prime} range 1, 600_851_475_143; Makefile.PL100644001752000144 203012524003657 14053 0ustar00mcusers000000000000perlude-0.61# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.036. use strict; use warnings; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Shell and Powershell pipes, haskell keywords mixed with the awesomeness of perl. forget shell scrpting now! ", "AUTHOR" => "Marc Chantreux ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "perlude", "EXE_FILES" => [], "LICENSE" => "perl", "NAME" => "perlude", "PREREQ_PM" => { "Carp" => 0 }, "VERSION" => "0.61", "test" => { "TESTS" => "t/*.t t/lazy/*.t t/stream/*.t" } ); my %FallbackPrereqs = ( "Carp" => 0, "ExtUtils::MakeMaker" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); data000755001752000144 012524003657 13122 5ustar00mcusers000000000000perlude-0.61/tbrian100644001752000144 2312524003657 14233 0ustar00mcusers000000000000perlude-0.61/t/dataromanes eunt domus lib000755001752000144 012524003657 12514 5ustar00mcusers000000000000perlude-0.61Perlude.pm100644001752000144 1133012524003657 14630 0ustar00mcusers000000000000perlude-0.61/libpackage Perlude; use Perlude::Open; use strict; use warnings; use 5.10.0; use Carp qw< croak >; use Exporter qw< import >; our @EXPORT = qw< fold unfold takeWhile take drop filter apply now cycle range tuple concat concatC concatM records lines pairs nth chunksOf open_file >; # ABSTRACT: Shell and Powershell pipes, haskell keywords mixed with the awesomeness of perl. forget shell scrpting now! use Carp; our $VERSION = '0.61'; sub pairs ($) { my ( $ref ) = @_; my $isa = ref $ref or die "'$ref' isn't a ref"; # TODO: use reftypes here! if ($isa eq 'HASH') { sub { my @pair; while ( @pair = each %$ref ) { return \@pair } () } } # elsif ($isa eq 'ARRAY') { # my $index = 1; # sub { # return if $index > @$ref; # my $r = # [ $$ref[$index-1] # , $$ref[$index] ]; # $index+=2; # $r; # } # } else { die "can't pair this kind of ref: $isa" } } # sub pairs (&$) { # my ( $do, $on ) = @_; # sub { # while ( @$_ = each %$on ) { return $do->() } # () # } # } # private helpers sub _buffer ($) { my ($i) = @_; my @b; sub { return shift @b if @b; @b = ( $i->() ); return @b ? shift @b : (); } } # interface with the Perl world sub unfold (@) { my @array = @_; sub { @array ? shift @array : () } } sub fold ($) { my ( $i ) = @_; my @v; unless (wantarray) { if (defined wantarray) { my $n = 0; $n += @v while @v = $i->(); return $n; } else { undef while @v = $i->(); return; } } my @r; push @r, @v while @v = $i->(); @r; } # stream consumers (lazy) sub takeWhile (&$) { my ($cond, $i ) = @_; sub { ( my @v = $i->() ) or return; return $cond->() ? @v : () for @v; } } sub filter (&$) { my ( $cond, $i ) = @_; $i = _buffer $i; sub { while (1) { ( my @v = $i->() ) or return; $cond->() and return @v for @v; } } } sub take ($$) { my ( $n, $i ) = @_; $i = _buffer $i; sub { $n-- > 0 or return; $i->() } } sub drop ($$) { my ( $n, $i ) = @_; $i = _buffer $i; fold take $n, $i; $i; } sub apply (&$) { my ( $code, $i ) = @_; sub { ( my @v = $i->() ) or return; (map $code->(), @v)[0]; } } # stream consumers (exhaustive) sub now (&$) { my ( $code, $i ) = @_; my @b; while (1) { ( my @v = $i->() ) or return pop @b; @b = map $code->(), @v; } } sub records { my $source = shift; sub { <$source> // () } } sub lines { my $fh = &open_file; my $line; sub { return unless defined ( $line = <$fh> ); chomp $line; $line; } } sub concat { my ($s, @ss) = @_; # streams my @v; sub { while (1) { @v = $s->() and return @v; $s = shift @ss or return (); } } } sub concatC ($) { my $ss = shift; # stream my ($s) = $ss->() or return sub {()}; my @v; sub { while (1) { @v = $s->() and return @v; $s = $ss->() or return (); } } } sub concatM (&$) { my ( $apply, $stream ) = @_; concatC apply {$apply->()} $stream; } # stream generators sub cycle (@) { (my @ring = @_) or return sub {}; my $index = -1; sub { $ring[ ( $index += 1 ) %= @ring ] } } sub range { my $begin = shift // croak "range begin undefined"; my $end = shift; my $step = shift // 1; return sub { () } if $step == 0; $begin -= $step; if (defined $end) { if ($step > 0) { sub { (($begin += $step) <= $end) ? ($begin) : () } } else { sub { (($begin += $step) >= $end) ? ($begin) : () } } } else { sub { ($begin += $step) } } } sub tuple ($$) { my ( $n, $i ) = @_; croak "$n is not a valid parameter for tuple()" if $n <= 0; $i = _buffer $i; sub { my @v = fold take $n, $i; @v ? \@v : (); } } sub nth { my ( $n, $s ) = @_; $n--; take 1, drop $n, $s } sub chunksOf ($$;$) { my ( $n, $src, $offset ) = @_; $n > 1 or die "chunksOf must be at least 1 (don't forget unfold)"; $offset //= 0; my ( $end , $exhausted , $from, $to ) = ( $#$src , 0 ); sub { return if $exhausted; ( $from , $offset )= ( $offset , $offset + $n ); $end <= ($to = $offset - 1) and do { $exhausted=1; $to = $end; }; [ @{$src}[$from..$to] ]; } } 1; Perlude.pod100644001752000144 3602612524003657 15007 0ustar00mcusers000000000000perlude-0.61/lib=encoding utf8 =for HTML CPAN version Endorse eiro on Coderwall =head1 SYNOPSIS If you're used to a unix shell, Windows Powershell or any langage comming with the notion of streams, perl could be frustrating as functions like map and grep only works with arrays. The goodness of it is that C<|> is an on demand operator that can easily compose actions on potentially very large amount of data in a very memory and you can control the amount of consummed data in a friendly way. Perlude gives a better C<|> to Perl: as it works on scalars which can be both strings (like unix shell), numbers or references (like powershell). In L i show examples The big difference is there is no C<|> operator, so the generator is used as function parameter instead of lhs of the pipe (still, the ease of composition remains). So the perlude notation of seq 1000 | sed 5q is take 5, range 1, 1000 this code returns a new iterator you want to consume, maybe to fold it in a array, maybe to act on each lastly generated element with the keyword C (as "now, compute things you learnt to compute"). my @five = fold take 5, range 1, 1000; map {say} take 5, range 1, 1000; a classical, memory agressive, Perl code would be map {say} (1..1000)[0..4] Note that map {say} (1..4)[0..1000] is an error when now {say} take 1000, range 1,4 Perlude stole some keywords from the Haskell Prelude (mainly) to make iterators easy to combine and consume. map say, grep /foo/, ; Perlude provides "streamed" counterpart where a stream is a set (whole or partial) of results an iterator can return. now {say} filter {/foo/} lines \*STDIN; Now we'll define the concepts under Perlude. the functions provided are in the next section. =head2 an iterator is a function reference that can produce a list of at least one element at each calls. an exhausted iterator returns an empty list. Counter is a basic example of iterator my $counter = sub { state $x = 0; $x++ }; If you use Perl before 5.10, you can write my $counter = do { my $x = 0; sub {$x++} }; (see "Persistent variables with closures") in the C. =head2 an iteration one call of an iterator print $counter->(); =head2 a stream the list of all elements an iterator can produce (it may be infinite). the five first elements of the stream of C<$counter> (if it wasn't previously used) is my @top5 = map $counter->(), 1..5; the perlude counterpart is my @top5 = fold take 5, $counter; =head2 a generator is a function that retuns an iterator. sub counter ($) { my $x = $_[0]; # iterator starts here sub { $x++ } } my $iterator = counter 1; print $iterator->(); =head2 a filter is a function that take an iterator as argument and returns an iterator, applying a behavior to the elements of the stream. such behavior can be removing or adding elements of the stream, exhaust it or applying a function in the elements of it. some filters are Perlude counterparts of the perl C and C, other can control the way the stream is consumed (think of them as unix shell filters). =head2 a consumer filters are about combining things nothing is computed as long as you don't use the stream. consumers actually starts to stream (iterate on) them (think python3 C or the perl6 C<&eager>). =head1 to sumarize A stream is a list finished by an empty list (which makes sense if you come from a functional language). (2,4,6,8,10,()) A an iterator is a function that can return the elements of an iterator one by one. A generator is a function that retuns the iterator sub from_to { # the generator my ( $from, $to ) = @_; sub { # the iterator return () if $from > $to; my $r = $from; $from+=2; return $r } } note that perlude authors are used to implicit notations so we're used to write more like sub { return if $from > $to; (my $r, $from) = ( $from, $from + 2 ); $r; } (see the code of the C<&lines> generator) =head1 Examples find the first five zsh users my @top5 = fold take 5, filter {/zsh$/} lines "/etc/passwd"; A math example: every elements of fibo below 1000 (1 element a time in memory) use Perlude; use strict; use warnings; sub fibo { my @seed = @_; sub { push @seed, $seed[0] + $seed[1]; shift @seed } } now {say} takeWhile { $_ < 1000 } fibo 1,1; Used to shell? the Perlude version of yes "happy birthday" | sed 5q is sub yes ($msg) { sub { $msg } } now {say} take 5, yes "happy birthday" A sysop example: throw your shellscripts away use Perlude; use strictures; use 5.10.0; # iterator on a glob matches stolen from Perlude::Sh module sub ls { my $glob = glob shift; my $match; sub { return $match while $match = <$glob>; (); } } # show every txt files in /tmp now {say} ls "/tmp/*txt # remove empty files from tmp now { unlink if -f && ! -s } ls "/tmp/*" # something more reusable/readable ? sub is_empty_file { -f && ! -s } sub empty_files_of { filter {is_empty_file} shift } sub rm { now {unlink} shift } rm empty_files_of ls "/tmp/*./txt"; =head1 Function composition When relevant, i used the Haskell Prelude documentation descriptions and examples. for example, the take documentation comes from L. =head1 Functions =head2 generators =head3 range $begin, [ $end, [ $step ] ] A range of numbers from $begin to $end (infinity if $end isn't set) $step by $step. range 5 # from 5 to infinity range 5,9 # 5, 6, 7, 8, 9 range 5,9,2 # 5, 7, 9 =head3 cycle @set infinitly loop on a set of values cycle 1,4,7 # 1,4,7,1,4,7,1,4,7,1,4,7,1,4,7,... =head3 records $ref given any kind of ref that implements the "<>" iterator, returns a Perlude compliant iterator. now {print if /data/} records do { open my $fh,"foo"; $fh; }; =head3 as_open just easier (yet safer?) to use wrapper on the sub described in C (also L). the goal is to have an wrapper on open does a coercion (just return @_ if nothing to do). so =over 2 =item * don't carre about prototype (so you can call it with an array, not only a list) =item * return a FILEHANDLE instead of having a side effect on the first variable =item * just return a FILEHANDLE passed as argument (so it's a coercion from C<@_> to an open handler). open FILEHANDLE open EXPR open MODE,EXPR open MODE,EXPR,LIST open MODE,EXPR,REF =back =head3 lines @openargs if C<$openargs[0]> is a string, C<&open> @openargs (nothing done there if it's already a file handler). return an iterator that chomp the records of the open file. so now {say} lines "/etc/passwd" can be written like now {say} apply { chomp; $_ } do { open my $fh, "/etc/passwd"; sub { return unless defined my $line = <$fh>; chomp $line; $line; } } =head2 filters filters are composition functions that take a stream and returns a modified stream. =head3 filter $xs the Perlude counterpart of C. sub odds () { filter { $_ % 2 } shift } =head3 apply the Perlude counterpart of C. sub double { apply {$_*2} shift } =head3 take $n, $xs take $n, applied to a list $xs, returns the prefix of $xs of length $n, or $xs itself if $n > length $xs: sub top10 { take 10, shift } take 5, range 1, 10 # 1, 2, 3, 4, 5, () take 5, range 1, 3 # 1, 2, 3, () =head3 takeWhile $predicate, $xs takeWhile, applied to a predicate $p and a list $xs, returns the longest prefix (possibly empty) of $xs of elements that satisfy $p takeWhile { 10 > ($_*2) } range 1,5 # 1, 2, 3, 4 =head3 drop $n, $xs drop $n $xs returns the suffix of $xs after the first $n elements, or () if $n > length $xs: drop 3, range 1,5 # 4 , 5 drop 3, range 1,2 # () =head3 dropWhile $predicate, $xs dropWhile $predicate, $xs returns the suffix remaining after dropWhile $predicate, $xs dropWhile { $_ < 3 } unfold [1,2,3,4,5,1,2,3] # [3,4,5,1,2,3] dropWhile { $_ < 9 } unfold [1,2,3] # [] dropWhile { $_ < 0 } unfold [1,2,3] # [1,2,3] =head2 misc =head3 unfold $array unfold returns an iterator on the $array ref so that every Perlude goodies can be applied. there is no side effect on the referenced array. my @lower = fold takeWhile {/data/} unfold $abstract see also fold =head3 pairs $hash returns an iterator on the pairs of $hash stored in a 2 items array ref. now { my ( $k, $v ) = @$_; say "$k : $v"; } pairs {qw< a A b B >}; aims to be equivalent to my $hash = {qw< a A b B >}; while ( my ( $k, $v ) = each %$hash ) { say "$k : $v"; } except that: =over 4 =item * pairs can use an anonymous hash =item * can be used in streams =item * i hate the while syntax =back =head2 consumers =head3 now {actions} $xs read the $xs stream and execute the {actions} block with the returned element as $_ until the $xs stream exhausts. it also returns the last transformed element so that it can be used to foldl. (compare it to perl6 "eager" or haskell foldl) =head3 fold $xs returns the array of all the elements computed by $xs say join ',', take 5, sub { state $x=-2; $x+=2 } # CODE(0x180bad8) say join ',', fold take 5, sub { state $x=-2; $x+=2 } # 0,2,4,6,8 see also unfold =head3 nth $xs returns the nth element of a stream say fold nth 5, sub { state $x=1; $x++ } # 5 =head3 chunksOf non destructive splice alike (maybe best named as "traverse"? haskell name?). you can traverse an array by a group of copies of elements say "@$_" for fold chunksOf 3, ['a'..'f']; # a b c # d e f =head2 Composers =head3 concat @streams concat takes a list of streams and returns them as a unique one: concat map { unfold [split //] } split /\s*/; streams every chars of the words of the text =head3 concatC $stream_of_streams takes a stream of streams $stream_of_streams and expose them as a single one. A stream of streams is a steam that returns streams. concatC { take 3, range $_ } lines $fh take 3 elements from the range started by the values of $fh, so if $fh contains (5,10), the stream is (5,6,7,10,11,12) =head3 concatM $apply, $stream applying $apply on each iterations of $stream must return a new stream. concatM expose them as a single stream. # ls is a generator for a glob sub cat { concatM {lines} ls shift } cat "/tmp/*.conf" =head1 Perlude companions some modules comes with generators so they are perfect Perlude companions (send me an exemple if yours does too). =head1 C use aliased qw(Path::Iterator::Rule find); now {print} take 3, find->new -> file -> size('>1k') -> and( sub { -r } ) -> iter(qw( /tmp )); you can use C instead of C: now {print} take 3, filter {-r} find->new -> file -> size('>1k') -> iter(qw( /tmp )); =head1 C use Path::Tiny; now {print} take 3, path("/etc")->iterator; now {print} take 3, apply {chomp;$_} records path("/etc/passwd")->openr_utf8( {qw( locked 1 )}); =head1 C a very friendly way to write iterators. i rewrote the exemple from the C doc: use TAP::Parser; my $parser = TAP::Parser->new( { tap => $output } ); while ( my $result = $parser->next ) { print $result->as_string; } with Perlude now {print $_->as_string."\n"} do { my $parser = TAP::Parser -> new( { tap => path("/tmp/x")->slurp }); sub { $parser->next // () } } with Perlude and curry now {defined and print $_->as_string."\n"} TAP::Parser -> new( { tap => path("/tmp/x")->slurp }) -> curry::next; =head1 TODO / CONTRIBUTONS feedbacks and contributions are very welcome http://github.com/eiro/p5-perlude =over 4 =item * Improve general quality: doc, have a look on L, L. =item * Explore test suite to know what isn't well tested. find bugs :) * see range implementation # what if step 0 ? * pairs must support streams and array * provide an alternative to takeWhile to return the combo breaker * explore AST manipulations for futher optimizations =item * deprecate open_file and lines (or/and find a companion) as it is out of the scope of Perlude and open_file seems scary (anything to avoid the C prototype?). =item * reboot C remove the hardcoded C namespace and use C instead. =item * ask for BooK and Dolmen if they mind to remove C as noone seems to use it anymore. =item * C anyone ? =item * Something to revert the callback mechanism: how to provide a generic syntax to use Anyevent driven streams or "callback to closures" (for example: Net::LDAP callback to treat entries onfly) =item * provide streamers for common sources CSV, LDAP, DBI (see C) =back =head1 KNOWN BUGS not anymore, if you find one, please email bug-Perlude [at] rt.cpan.org. =head1 AUTHORS =over 4 =item * Philippe Bruhat (BooK) =item * Marc Chantreux (eiro) =item * Olivier MenguE (dolmen) =back =head1 CONTRIBUTORS Burak Gürsoy (cpanization) =head1 ACKNOWLEDGMENTS =over 4 =item * Thanks to Nicolas Pouillard and Valentin (#haskell-fr), i leanrt a lot about streams, lazyness, lists and so on. Lazyness.pm was my first attempt. =item * The name "Perlude" is an idea from Germain Maurice, the amazing sysop of http://linkfluence.com back to early 2010. =item * Former versions of Perlude used undef as stream terminator. After my talk at the French Perl Workshop 2011, dolmen suggested to use () as stream terminator, which makes sense not only because undef is a value but also because () is the perfect semantic to end a stream. So Book, Dolmen and myself rewrote the entire module from scratch in the hall of the hotel with a bottle of chartreuse and Cognominal. We also tried some experiments about real lazyness, memoization and so on. it becomes clear now that this is hell to implement correctly: use perl6 instead :) I was drunk and and mispelled Perlude as "Perl dude" so Cognominal collected some quotes of "The Big Lebowski" and we called ourselves "the Perl Dudes". This is way my best remember of peer programming and one of the best moment i shared with my friends mongueurs. =back =cut 00_open_file.t100644001752000144 114412524003657 14775 0ustar00mcusers000000000000perlude-0.61/t#! /usr/bin/perl use Perlude; use Test::More; my $expected = [qw< romanes eunt domus >]; sub is_reference_to ($$;$) { my ( $isa, $v, $desc ) = @_; $desc //= "which is a ref to $isa"; is ((ref $v), $isa, $desc); } for ( ['t/data/brian'] , ['t/data/brian', '<:encoding(utf8)'] ) { my $mean = sprintf 'open_file(%s)' , join ',' , map { (ref $_) || $_ } @$_; my $fh = open_file @$_ or die "$! while $mean"; is_reference_to GLOB => $fh , "$mean returns a GLOB"; is_deeply [fold lines $fh] => $expected , "$mean read as expected"; } done_testing; pod-coverage.t100644001752000144 112412524003657 15107 0ustar00mcusers000000000000perlude-0.61/tuse strict; use warnings; use Test::More; plan skip_all => "i know: lack of doc ..."; # Ensure a recent version of Test::Pod::Coverage my $min_tpc = 1.08; eval "use Test::Pod::Coverage $min_tpc"; plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" if $@; # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, # but older versions don't recognize some common documentation styles my $min_pc = 0.18; eval "use Pod::Coverage $min_pc"; plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" if $@; all_pod_coverage_ok(); lazy000755001752000144 012524003657 13170 5ustar00mcusers000000000000perlude-0.61/t10_fold.t100644001752000144 116512524003657 14744 0ustar00mcusers000000000000perlude-0.61/t/lazy#! /usr/bin/perl use strict; use warnings; use Test::More; use Perlude::Lazy; my @tests = ( [] , [undef] , [1..10] , [''] , ["haha"] ); plan tests => 3*@tests; for my $t (@tests) { is_deeply ( [fold unfold @$t] , $t , "fold unfold => id" ) } # fold in void context for my $t (@tests) { my @l = @$t; my $n = 1+@l; #fold apply { $n--; @_ } unfold @$t; fold enlist sub { $n--; @l ? (shift @l) : () }; is $n, 0, (0+@$t)." elements in void context"; } # fold in scalar context for my $t (@tests) { my $n = fold unfold @$t; is $n, 0+@$t, (0+@$t)." elements in scalar context"; } 15_range.t100644001752000144 131512524003657 15116 0ustar00mcusers000000000000perlude-0.61/t/lazyuse strict; use warnings; use Test::More tests => 12; use Perlude::Lazy; is_deeply [ fold range(1, 1, 1) ], [ 1 ]; is_deeply [ fold range(1, 2, 1) ], [ 1, 2 ]; is_deeply [ fold range(1, 1) ], [ 1 ]; is_deeply [ fold range(1, 2) ], [ 1, 2 ]; is_deeply [ fold range(2, 1, -1) ], [ 2, 1 ]; is_deeply [ fold range(1, 1, -1) ], [ 1 ]; is_deeply [ fold range(1, 0, -1) ], [ 1, 0 ]; diag "infinite ranges"; is_deeply [ fold take 3, range(1, undef) ], [ 1, 2, 3 ]; is_deeply [ fold take 3, range(1, undef, 1) ], [ 1, 2, 3 ]; is_deeply [ fold take 3, range(1, undef, 2) ], [ 1, 3, 5 ]; is_deeply [ fold take 3, range(5, undef, -1) ], [ 5, 4, 3 ]; is_deeply [ fold take 3, range(5, undef, -2) ], [ 5, 3, 1 ]; 14_tuple.t100644001752000144 126312524003657 15154 0ustar00mcusers000000000000perlude-0.61/t/lazyuse Test::More; use strict; use warnings; use Perlude::Lazy; my @pass = ( [ 3, enlist { } ] , [ 4, unfold( 0 .. 10 ), [ 0 .. 3 ], [ 4 .. 7 ], [ 8 .. 10 ] ] , [ 2.5, unfold( 0 .. 6 ), [ 0 .. 2 ], [ 3 .. 5 ], [ 6 ] ] ); my @fail = ( [ 0, enlist { } ] , [ -1, unfold( 0 .. 10 ) ] , [ 'a', unfold( 0 .. 10 ) ] ); plan tests => @pass + 2 * @fail; for my $t (@pass) { my ( $n, $i, @r ) = @$t; is_deeply( [ fold tuple $n, \&$i ], \@r, "tuple $n" ); } for my $t (@fail) { my ( $n, $i ) = @$t; ok( !eval { tuple $n, \&$i; 1 }, "tuple $n FAIL" ); like( $@, qr/^\Q$n\E is not a valid parameter for tuple\(\) at /, "error message for tuple $n" ); } 16_lines.t100644001752000144 41312524003657 15113 0ustar00mcusers000000000000perlude-0.61/t/lazy#! /usr/bin/perl use strict; use warnings; use Test::More; use Perlude::Lazy; my @seed = qw/ toto tata tutu /; my $content = 'toto tata tutu '; open my $file, '<', \$content; is_deeply ( [fold lines $file] , [map { "$_\n" } @seed] , "raw lines" ); done_testing; stream000755001752000144 012524003657 13504 5ustar00mcusers000000000000perlude-0.61/t10_fold.t100644001752000144 115012524003657 15252 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use strict; use warnings; use Test::More; use Perlude; my @tests = ( [] , [undef] , [1..10] , [''] , ["haha"] ); plan tests => 3*@tests; for my $t (@tests) { is_deeply ( [fold unfold @$t] , $t , "fold unfold => id" ) } # fold in void context for my $t (@tests) { my @l = @$t; my $n = 1+@l; #fold apply { $n--; @_ } unfold @$t; fold sub { $n--; @l ? (shift @l) : () }; is $n, 0, (0+@$t)." elements in void context"; } # fold in scalar context for my $t (@tests) { my $n = fold unfold @$t; is $n, 0+@$t, (0+@$t)." elements in scalar context"; } 17_concat.t100644001752000144 35612524003657 15257 0ustar00mcusers000000000000perlude-0.61/t/lazy#! /usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; use Perlude::Lazy; use autodie; my $got = [fold concat range(1,4), range(5,10)]; my $expected = [1..10]; is_deeply ($got, $expected, "contat ranges works fine"); 12_buffer.t100644001752000144 322212524003657 15267 0ustar00mcusers000000000000perlude-0.61/t/lazyuse Test::More; use strict; use warnings; use Perlude::Lazy; ok 1,"some old tests to restore if someone wants to maintain lazy"; done_testing; __END__ my @tests = ( [ fold => ( apply {@$_} tuple 3, unfold 1 .. 7 ), [ 1 .. 7 ] ] , [ takeWhile => ( takeWhile { $_ % 2 } apply {@$_} tuple 3, unfold 1, 3, 5, 2, 7, 9 ) , [ 1, 3, 5 ] ] , [ filter => ( filter { $_ % 2 } apply {@$_} tuple 3, unfold 1 .. 7 ) , [ 1, 3, 5, 7 ] ] , [ take => ( take 5, apply {@$_} tuple 3, unfold 1 .. 100 ), [ 1 .. 5 ] ] , [ drop => ( drop 5, apply {@$_} tuple 3, unfold 1 .. 100 ), [ 6 .. 100 ] ] , [ apply => ( apply {@$_} tuple 17, unfold 1 .. 100 ), [ 1 .. 100 ] ] , [ traverse => sub { # the state variable ensure the sub runs once only ( state $i++ ) ? () : traverse { -$_ } apply {@$_} tuple 5, unfold 1 .. 10; } , [-10] ] , [ tuple => ( tuple 2, apply {@$_} tuple 3, unfold 0 .. 10 ), , [ [ 0, 1 ], [ 2, 3 ], [ 4, 5 ], [ 6, 7 ], [ 8, 9 ], [10] ] ] ); plan tests => @tests + 1; $TODO = '_buffer removed'; # generate the todo list my %todo = do { no strict 'refs'; map { ( $_ => 1 ) } # exception list grep { !/^(?:import|carp|confess|croak|_.*|[A-Z_]+|unfold|cycle)$/ } # functions in the Perlude:: namespace grep { defined ${'Perlude::'}{$_} } keys %Perlude::; }; # run the tests for my $t (@tests) { my ( $name, $i, $out ) = @$t; my $got = [ fold $i ]; my $ok = is_deeply( $got, $out, $name ); delete $todo{$name}; } # check all sub in Perlude:: have been tested ok( !keys %todo, 'All Perlude functions tested for buffering' ) or diag "Untested functions: @{[sort keys %todo]}"; 01_enlist.t100644001752000144 463412524003657 15322 0ustar00mcusers000000000000perlude-0.61/t/lazyuse strict; use warnings; use 5.10.0; use Test::More; use Perlude::Lazy; plan tests => my $tests; # very basic test BEGIN { $tests += 1 } my $l = enlist { state $n; $n++ }; is( ref $l, 'CODE', 'enlist returns a coderef' ); # check some values BEGIN { $tests += 3 } my @v; ( $l, @v ) = $l->(); is_deeply( \@v, [0], 'first item' ); ( $l, @v ) = $l->(1); # peek at the next value is_deeply( \@v, [1], 'peek at the next item' ); ( $l, @v ) = $l->(); is_deeply( \@v, [1], 'next item' ); # peek at a lot of values at once BEGIN { $tests += 3 } my $n = int 1000 * rand; ( $l, @v ) = $l->($n); is( scalar @v, $n, "Peeked at $n items" ); is( $v[-1], 1 + $n, "Last item is " . ( $n + 1 ) ); ( $l, @v ) = $l->(); is_deeply( \@v, [2], 'next item' ); # corner cases BEGIN { $tests += 4 } ( $l, @v ) = $l->(0); is( scalar @v, 0, 'peek at nothing' ); ( $l, @v ) = $l->(); is_deeply( \@v, [3], 'next item' ); ( $l, @v ) = $l->(-1); is( scalar @v, 0, 'peek at nothing (-1)' ); ( $l, @v ) = $l->(); is_deeply( \@v, [4], 'next item' ); # bounded list BEGIN { $tests += 4 } $l = unfold 0 .. 5; ( $l, @v ) = $l->(6); is( scalar @v, 6, 'peek at the remaining items' ); ( $l, @v ) = $l->(); is_deeply( \@v, [0], 'next item' ); ( $l, @v ) = $l->(7); is( scalar @v, 5, 'peek at more than the remaining total' ); ( $l, @v ) = $l->(); is_deeply( \@v, [1], 'next item' ); my @tests; BEGIN { @tests = ( [ (unfold 0..5), [6], [0..5], [], [0], [], [1], [6], [2..5], [], [2], [], [3], [], [4], [9], [5], [], [5], [9], [], [], [], ], [ (unfold 0..1), [1], [0], [2], [0..1], [2], [0..1], [], [0], [2], [1], [], [1], [2], [], [], [], ], [ (unfold 0..3), [2], [0..1], [], [0], [1], [1], [], [1], [2], [2..3], [], [2], [], [3], [1], [], [], [], ], [ Perlude::Lazy::NIL, [], [], ], ); $tests += @$_ for @tests; } my $m = 1; while (@tests) { my $t = shift @tests; my $l = shift @$t; my $n = 1; while (@$t) { ($l, my @v) = $l->(@{ shift @$t }); is_deeply \@v, (shift @$t), "test $m,$n"; ($l, @v) = $l->(0); is_deeply \@v, [], "test $m,$n: peek 0"; $n++; }; is $l, Perlude::Lazy::NIL, "end $m"; $m++; } Perlude000755001752000144 012524003657 14114 5ustar00mcusers000000000000perlude-0.61/libLazy.pm100644001752000144 1242412524003657 15554 0ustar00mcusers000000000000perlude-0.61/lib/Perludepackage Perlude::Lazy; use strict; use warnings; use 5.10.0; use Carp qw< croak >; use Exporter qw< import >; our $VERSION = '0.0'; our @EXPORT = qw< enlist unfold fold takeWhile take drop filter apply now cycle range tuple lines concat >; use Carp; # End-of-list value: always return itself, with no data { my $NIL; $NIL = sub { $NIL }; sub NIL() { $NIL } } # interface with the Perl world sub enlist (&) { my ($i) = @_; my ( $l, @b ); $l = sub { if (@_) { my $n = shift; return ( $l, @b[ 0 .. $n - 1 ] ) if @b >= $n; # there's enough push @b, my @v = $i->(); # need more push @b, @v = $i->() while @b < $n && @v; # MOAR return ( $l, @b < $n ? @b : @b[ 0 .. $n - 1 ] ); # give it a peek } else { return ( $l, shift @b ) if @b; # use the buffer first push @b, $i->(); # obtain more items return @b ? ( $l, shift @b ) : NIL; } }; } sub concat { my ($l, @ls)= @_; my @v; my $r; $r = sub { while ($l) { ( $l, @v ) = $l->(); return ($r,@v) if @v; $l = shift @ls; } }; $r } sub unfold (@) { my @array = @_; enlist { @array ? shift @array : () }; } sub fold ($) { my ($l) = @_; my @v; unless (wantarray) { if ( defined wantarray ) { my $n = 0; $n += @v while 1 < ( ( $l, @v ) = $l->() ); return $n; } else { # The real lazy one: when called in scalar context, values are # ignored: # undef while defined ( $l = $l->() ); # But producers must be able to handle that # So keep that for later and use the eager implementation for now undef while 1 < ( ( $l, @v ) = $l->() ); return; } } my @r; push @r, @v while 1 < ( ( $l, @v ) = $l->() ); @r; } # stream consumers (lazy) sub takeWhile (&$) { my ( $cond, $l ) = @_; my $m; $m = sub { 1 < ( ( $l, my @v ) = $l->() ) or return ($l); return $cond->() ? ( $m, @v ) : ( sub { ( $l, @v ) } ) for @v; }; } sub filter (&$) { my ( $cond, $l ) = @_; my $m; $m = sub { while (1) { 1 < ( ( $l, my @v ) = $l->() ) or return ($l); $cond->() and return ($m, @v) for @v; } }; } sub take ($$) { my ( $n, $l ) = @_; my $m; $m = sub { $n-- > 0 or return ($l); 1 < ( ( $l, my @v ) = $l->() ) or return ($l); ( $m, @v ); } } sub drop ($$) { my ( $n, $l ) = @_; fold take $n, $l; $l; } sub apply (&$) { my ( $code, $l ) = @_; my $m; $m = sub { 1 < ( ( $l, my @v ) = $l->() ) or return $l; ( $m, map $code->(), @v ); } } # stream consumers (exhaustive) sub now (&$) { my ( $code, $l ) = @_; my @b; while (1) { 1 < ( ( $l, my @v ) = $l->() ) or return pop @b; @b = map $code->(), @v; } } # stream generators sub cycle (@) { (my @ring = @_) or return NIL; my $index = -1; enlist { $ring[ ( $index += 1 ) %= @ring ] } } sub range ($$;$) { my $begin = shift // croak "range begin undefined"; my $end = shift; my $step = shift // 1; return NIL if $step == 0; $begin -= $step; my $l; return $l = defined $end ? $step > 0 ? sub { ( ( $begin += $step ) <= $end ) ? ( $l, $begin ) : ($l) } : sub { ( ( $begin += $step ) >= $end ) ? ( $l, $begin ) : ($l) } : sub { ( $l, $begin += $step ) }; } sub tuple ($$) { my ( $n, $l ) = @_; croak "$n is not a valid parameter for tuple()" if $n <= 0; my $m; $m = sub { $l = take $n, $l; my (@r, @v); push @r, @v while 1 < ( ( $l, @v ) = $l->() ); @r ? ( $m, \@r ) : ( $l ) } } sub lines { # private sub that coerce path to handles state $fh_coerce = sub { my $v = shift; return $v if ref $v; open my ($fh),$v; $fh; }; my $fh = $fh_coerce->( pop ); # only 2 forms accepted for the moment # form 1: lines 'file' @_ or return enlist { <$fh> // () }; # confess if not 2nd form $_[0] eq 'chomp' or confess 'cannot handle parameters ' , join ',', @_ ; # lines chomp => 'file' enlist { defined (my $v = <$fh>) or return; chomp $v; $v; } } 1; =head1 Perlude::Lazy An experimentation of implementing real lazy lists in Perl5. For real world usecases, please use Perlude instead. =head1 SYNOPSIS Haskell prelude miss you when you write perl stuff? Perlude is a port of the most common keywords. Some other keywords where added when there is no haskell equivalent. Example: in haskell you can write nat = [0..] is_even x = ( x `mod` 2 ) == 0 evens = filter is_even main = mapM_ print $ take 10 $ evens nat in perlude, the same code will be: use Perlude; my $nat = enlist { state $x = 0; $x++ }; sub is_even { ($_ % 2) == 0 } sub evens { filter {is_even} shift } traverse {say} take 10, evens $nat =head1 FUNCTIONS all the Perlude documentation is relevent. just replace sub by enlist =cut Open.pm100644001752000144 172112524003657 15514 0ustar00mcusers000000000000perlude-0.61/lib/Perludepackage Perlude::Open; use strict; use warnings; use 5.10.0; use Exporter qw< import >; our @EXPORT = qw< open_file >; sub _source_for_nargs (_) { my $nargs = shift or die; my $args = join ',', map {'$a'.$_} 1..$nargs; sprintf 'sub { my (%s) = @_; open my $fh, %s or die "$! while open_file %s"; $fh; }', ($args)x3; }; sub _prepare_special_forms { +{ map { my $cb = eval _source_for_nargs; $@ and die $@; $_ => $cb; } 1..4 }; } sub _callback_for_nargs(_) { # build callbacks for number of arguments from 1 to 4 state $open_with = _prepare_special_forms; # the 4 arguments form callback should work for 4 and more. # i copy this ref on demand whenever i see a new number of arguments $open_with->{ +@_ } ||= $open_with->{4}; } sub open_file { # if the file (can be $_) is open, just return return shift if ref ($_[0]||=$_); ( _callback_for_nargs +@_ )->(@_) } 19_twice.t100644001752000144 61412524003657 15436 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use Test::More; use Perlude; use strict; use warnings; for ( [ "perlude respect multivalued returns", [ fold take 3, sub { 1, 2 } ] => [qw( 1 2 1 )] ] , [ "apply respect multivalued returns", [ fold take 3, apply { $_, $_ } sub { 8, 9 } ] => [qw( 8 8 8 )] ] ) { my ( $desc, $got, $expected ) = @$_; is_deeply $got, $expected, $desc; } done_testing; 15_range.t100644001752000144 130712524003657 15433 0ustar00mcusers000000000000perlude-0.61/t/streamuse strict; use warnings; use Test::More tests => 12; use Perlude; is_deeply [ fold range(1, 1, 1) ], [ 1 ]; is_deeply [ fold range(1, 2, 1) ], [ 1, 2 ]; is_deeply [ fold range(1, 1) ], [ 1 ]; is_deeply [ fold range(1, 2) ], [ 1, 2 ]; is_deeply [ fold range(2, 1, -1) ], [ 2, 1 ]; is_deeply [ fold range(1, 1, -1) ], [ 1 ]; is_deeply [ fold range(1, 0, -1) ], [ 1, 0 ]; diag "infinite ranges"; is_deeply [ fold take 3, range(1, undef) ], [ 1, 2, 3 ]; is_deeply [ fold take 3, range(1, undef, 1) ], [ 1, 2, 3 ]; is_deeply [ fold take 3, range(1, undef, 2) ], [ 1, 3, 5 ]; is_deeply [ fold take 3, range(5, undef, -1) ], [ 5, 4, 3 ]; is_deeply [ fold take 3, range(5, undef, -2) ], [ 5, 3, 1 ]; 14_tuple.t100644001752000144 124712524003657 15472 0ustar00mcusers000000000000perlude-0.61/t/streamuse Test::More; use strict; use warnings; use Perlude; my @pass = ( [ 3, sub { } ] , [ 4, unfold( 0 .. 10 ), [ 0 .. 3 ], [ 4 .. 7 ], [ 8 .. 10 ] ] , [ 2.5, unfold( 0 .. 6 ), [ 0 .. 2 ], [ 3 .. 5 ], [ 6 ] ] ); my @fail = ( [ 0, sub { } ] , [ -1, unfold( 0 .. 10 ) ] , [ 'a', unfold( 0 .. 10 ) ] ); plan tests => @pass + 2 * @fail; for my $t (@pass) { my ( $n, $i, @r ) = @$t; is_deeply( [ fold tuple $n, \&$i ], \@r, "tuple $n" ); } for my $t (@fail) { my ( $n, $i ) = @$t; ok( !eval { tuple $n, \&$i; 1 }, "tuple $n FAIL" ); like( $@, qr/^\Q$n\E is not a valid parameter for tuple\(\) at /, "error message for tuple $n" ); } 23_pairs.t100644001752000144 135112524003657 15453 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use strict; use warnings; use Perlude; use Test::More; eval { fold pairs "haha" }; ok $@, "die when arg isn't ref"; for ( [ "each pairs from hash" , 5 , {qw< a_key a_value b_key b_value c_key c_value d_key d_value e_key e_value >} ] # WHEN ARRAYs implemented # # , [ "each pairs from alphabet" # , 5 # , ['a'..'e'] ] # # , [ "each pairs from weird cases" # , 4 # , [ undef, 0, '', 'weird' ] ] # # WHEN streams implemented # # , [ "each pairs from a stream" # , 5 # , take 5, sub { state $x = 0; $x++ } ] ) { my ( $desc, $expected, $from ) = @$_; my $got = my @r = fold pairs $from; is $got, $expected, $desc; } done_testing; 16_lines.t100644001752000144 51212524003657 15427 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use strict; use warnings; use Test::More; use Perlude; note "this should be removed as lines is out of the scope of Perlude"; my @seed = qw/ toto tata tutu /; my $content = 'toto tata tutu '; open my $file, '<', \$content; is_deeply ( [fold lines $file] , [map { "$_" } @seed] , "raw lines" ); done_testing; 17_concat.t100644001752000144 35012524003657 15565 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use strict; use warnings; use Test::More 'no_plan'; use Perlude; use autodie; my $got = [fold concat range(1,4), range(5,10)]; my $expected = [1..10]; is_deeply ($got, $expected, "contat ranges works fine"); 12_buffer.t100644001752000144 321112524003657 15601 0ustar00mcusers000000000000perlude-0.61/t/streamuse Test::More; use strict; use warnings; use Perlude; ok 1 ,"done testing, please check if those tests are still relevent"; done_testing; __END__ my @tests = ( [ fold => ( apply {@$_} tuple 3, unfold 1 .. 7 ) , [ 1 .. 7 ] ] , [ takeWhile => ( takeWhile { $_ % 2 } apply {@$_} tuple 3, unfold 1, 3, 5, 2, 7, 9 ) , [ 1, 3, 5 ] ] , [ filter => ( filter { $_ % 2 } apply {@$_} tuple 3, unfold 1 .. 7 ) , [ 1, 3, 5, 7 ] ] , [ take => ( take 5, apply {@$_} tuple 3, unfold 1 .. 100 ) , [ 1 .. 5 ] ] , [ drop => ( drop 5, apply {@$_} tuple 3, unfold 1 .. 100 ) , [ 6 .. 100 ] ] , [ apply => ( apply {@$_} tuple 17, unfold 1 .. 100 ) , [ 1 .. 100 ] ] , [ now => sub { # the state variable ensure the sub runs once only ( state $i++ ) ? () : now { -$_ } apply {@$_} tuple 5, unfold 1 .. 10; } , [-10] ] , [ tuple => ( tuple 2, apply {@$_} tuple 3, unfold 0 .. 10 ), , [ [ 0, 1 ], [ 2, 3 ], [ 4, 5 ], [ 6, 7 ], [ 8, 9 ], [10] ] ] ); # plan tests => @tests + 1; $TODO = '_buffer removed'; # generate the todo list my %todo = do { no strict 'refs'; map { ( $_ => 1 ) } # exception list grep { !/^(?:import|carp|confess|croak|_.*|[A-Z_]+|unfold|cycle)$/ } # functions in the Perlude:: namespace grep { defined ${'Perlude::'}{$_} } keys %Perlude::; }; # run the tests for my $t (@tests) { my ( $name, $i, $out ) = @$t; my $got = [ fold $i ]; my $ok = is_deeply( $got, $out, $name ); delete $todo{$name}; } # check all sub in Perlude:: have been tested ok( !keys %todo, 'All Perlude functions tested for buffering' ) or diag "Untested functions: @{[sort keys %todo]}"; 13_traverse.t100644001752000144 62712524003657 15640 0ustar00mcusers000000000000perlude-0.61/t/lazyuse strict; use warnings; use 5.10.0; use Test::More; use Perlude::Lazy; sub sum { now {state $sum = 0; $sum += $_ } shift } my @tests = ( [ undef, enlist {} ] , [ 10 => take 10, enlist {1} ] , [ 15 => unfold 1 .. 5 ] , [ 0 => take 10, cycle -1, 1 ] ); plan tests => 0+ @tests; for my $t (@tests) { my ( $sum, $i ) = @$t; is( sum($i), $sum, "sum = @{[ defined $sum ? $sum : '' ]}" ); } 30_builtins.t100644001752000144 353312524003657 15654 0ustar00mcusers000000000000perlude-0.61/t/lazyuse strict; use warnings; use Test::More skip_all => 'should this module be deprecated?'; use Perlude::Lazy; use Perlude::builtins; # first get the list of all builtins my %builtins = map { $_ => $_ } grep { defined \&{"f::$_"} } keys %f::; # these builtins are easy to test: it's basically a map my %simple = ( abs => 'num', chr => 'num', cos => 'num', defined => 'str', exp => 'num', glob => 'file', hex => 'hex', int => 'num', lcfirst => 'str', lc => 'str', length => 'str', log => 'pos', oct => 'oct', ord => 'chr', quotemeta => 'str', ref => 'ref', sin => 'num', sqrt => 'pos', ucfirst => 'str', uc => 'str', ); # some values used for testing my %values = ( num => [ 0, 1, -1, 0.5, -1.48, 37.999 ], pos => [ 1, 0.5, 1.48, 37.999 ], str => [ '', $/, "$/$/", qw( MUON eTa_Prime GRaviTiNo xi &kj@!$jh ) ], hex => [ 0, 35, 173, 1415, 18499, 'a', 'ff', 'dead', 'beef' ], oct => [ 0, 1, 7, 5647251 ], chr => [qw( a b c é か )], ref => [ [], {}, sub { }, \'a', \1, bless( {}, 'zlonk' ) ], file => [ 'zlonk', '*', 'lib/*' ], ); # skip this delete $builtins{$_} for qw( sub ); my @tests; plan tests => 1 + @tests + keys %simple; # test the simple builtins for my $builtin ( sort keys %simple ) { my $f = \&{"f::$builtin"}; my @v = @{ $values{ $simple{$builtin} } }; is_deeply( [ fold $f->( unfold @v ) ], [ map { eval "$builtin( \$_ )" } @v ], $builtin ); delete $builtins{$builtin}; } TODO: { local $TODO = sprintf 'The tests are not yet implemented for %s', join q{, }, sort keys %builtins; # did we test everything? is_deeply( [ sort keys %builtins ], [], "Tested all builtins" ); } 20-old-take.t100644001752000144 433312524003657 15437 0ustar00mcusers000000000000perlude-0.61/t/lazy#! /usr/bin/perl use strict; use warnings; use Perlude::Lazy; use Test::More tests => 12; my ( @input, $got, $expected ); # # @input = qw/ test toto tata tutu et le reste /; # $got = [fold takeWhile { /^t/ } sub { shift @input }]; # $expected = [qw/ test toto tata tutu /]; # # is_deeply( $got, $expected, "takeWhile works"); # sub begins_with_t ($) { takeWhile { /^t/ } shift } # my @t = qw/ toto tata aha /; # print "$_\n" for fold begins_with_t sub { shift @t } my $fold_ok = is ( fold( sub { () } ) , 0 , "fold works" ); unless ( $fold_ok ) { diag("fold failed so every other tests will fail too"); exit; } sub test_it { my ( $f, $input, $expected, $desc ) = @_; my $got = [fold $f->( unfold @$input ) ]; is_deeply( $got, $expected, $desc ); } for my $test ( [ sub { takeWhile { /^t/ } shift } , [qw/ toto tata haha got /] , [qw/ toto tata /] , "takeWhile ok" ], [ sub { take 2, shift } , [qw/ foo bar pan bing /] , [qw/ foo bar /] , "take ok" ], [ sub { filter { /a/ } shift } , [qw/ foo bar pan bing /] , [qw/ bar pan /] , "filter ok" ], ) { test_it @$test } @input = qw/ foo bar test /; sub eat { fold take shift, enlist { @input ? (shift @input) : () } }; { my $take_test = 1; for my $takes ( [ [qw/ foo bar /] , [eat 2] ] , [ [qw/ test /] , [eat 10] ] , [ [] , [eat 10] ] ) { my ( $expected, $got ) = @$takes; is_deeply ( $got, $expected , "take test $take_test ok" ); $take_test++; } } SKIP: { skip "mapC not (yet?) reimplmented", 1; sub take2ones { take 2, enlist { 1 } } $got = [ fold mapC { $_ + 1 } take2ones ]; $expected = [ 2, 2 ]; is_deeply( $got, $expected, 'mapC works'); } SKIP: { skip "mapR not (yet?) reimplmented", 2; my $count = 0; $got = mapR { $count+=$_ } take2ones; is( $got , undef, 'mapR returns nothing'); is( $count, 2, 'mapR did things'); } ($got) = fold drop 2, do { my @a = qw/ a b c d e f /; enlist { @a ? (shift @a) : () } }; is( $got, 'c', 'drop works' ); ($got) = fold drop 2, do { my @a = qw/ /; enlist { @a ? (shift @a) : () } }; is( $got, undef, 'drop works again' ); # take 3, cycle 1, 2; pod-coverage.t100644001752000144 112412524003657 16066 0ustar00mcusers000000000000perlude-0.61/t/lazyuse strict; use warnings; use Test::More; plan skip_all => "i know: lack of doc ..."; # Ensure a recent version of Test::Pod::Coverage my $min_tpc = 1.08; eval "use Test::Pod::Coverage $min_tpc"; plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" if $@; # Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, # but older versions don't recognize some common documentation styles my $min_pc = 0.18; eval "use Pod::Coverage $min_pc"; plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" if $@; all_pod_coverage_ok(); 22-old-range.t100644001752000144 114412524003657 15606 0ustar00mcusers000000000000perlude-0.61/t/lazy#! /usr/bin/perl use strict; use warnings; use 5.10.0; use Test::More skip_all => 'range not implemented'; use Perlude::Lazy qw/ fold range /; sub fold_for { my ( $args, $expected, $description ) = @_; is_deeply( [ fold range @$args ], $expected, $description ) } fold_for [1,5] , [1..5] , "range works with no step" ; fold_for [1,1] , [1] , "range with 1 element" ; fold_for [0,9,2] , [0, 2, 4, 6, 8] , "range with step 2" ; # plan skip_all => "i don't know what to do with inverted min,max"; # fold_for [5,1] , [] , "inverted range is empty" ; 11_consumers.t100644001752000144 327412524003657 16042 0ustar00mcusers000000000000perlude-0.61/t/lazyuse strict; use warnings; use 5.10.0; use Test::More; use Perlude::Lazy; my $limit = 10_000; my @tests = ( [ takeWhile => sub { $_ < 10 }, unfold( 1, 10, 4 ), [1] ] , [ takeWhile => sub { $_ < 10 }, unfold(), [] ] , [ takeWhile => sub { $_ < 10 }, unfold(10), [] ] , [ take => 2, unfold( 1 .. 30 ), [ 1, 2 ] ] , [ take => 2, unfold( 0, 1 ), [ 0, 1 ] ] , [ take => 2, unfold( 0, 1, 2 ), [ 0, 1 ] ] , [ take => 3, unfold(), [] ] , [ take => 3, unfold(1), [1] ] , [ take => 3, unfold( undef, 2 ), [ undef, 2 ] ] , [ take => 0, unfold( undef, 2 ), [] ] , [ take => -1, unfold( undef, 2 ), [] ] , [ take => 'ABC', enlist { state $n; $n++ }, [] ] , [ take => 0.5, enlist { state $n; $n++ }, [ 0 ] ] , [ drop => 2, unfold( 1 .. 30 ), [ 3 .. 30 ] ] , [ drop => 2, unfold( 0, 1 ), [] ] , [ drop => 2, unfold( 0, 1, 2 ), [2] ] , [ drop => 3, unfold(), [] ] , [ drop => 3, unfold(1), [] ] , [ drop => 3, unfold( undef, 2 ), [] ] , [ drop => 0, unfold( undef, 2 ), [ undef, 2 ] ] , [ drop => -1, unfold( undef, 2 ), [ undef, 2 ] ] , [ drop => "ABC", unfold( 1 .. 3 ), [ 1 .. 3 ] ] , [ drop => 0.1, unfold( 1 .. 3 ), [ 2, 3 ] ] , [ filter => sub { $_ % 2 }, unfold( 1, 2, 3 ), [1,3] ] , [ filter => sub { $_ % 2 }, enlist { state $n = 0; $n++ } , [ grep { $_ % 2 } 0 .. ( 2 * $limit ) ] ] , [ apply => sub { $_ % 2 }, unfold( 1, 2, 3 ), [1,0,1] ] , [ apply => sub { $_ % 2 }, enlist { state $n = 0; $n++ } , [ fold take $limit, cycle 0, 1 ] ] ); plan tests => 0+ @tests; for my $t (@tests) { my ( $name, $arg, $i, $out ) = @$t; no strict 'refs'; my @r = ref $arg eq 'CODE' ? fold take $limit, &$name( \&$arg, $i ) : fold take $limit, &$name( $arg, $i ); is_deeply( \@r, $out, $name); } 18_chunksOf.t100644001752000144 153712524003657 16127 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use 5.010; use strict; use warnings; use Test::More 'no_plan'; use Perlude; # this is the reference: don't touch it # because chunksOf means to preserve the content of the original array my @reference = 'a'..'f'; # alpha symbols of hexadecimal base # this is the copy of the reference array! my @source = @reference; my $got = chunksOf 3, \@source; # what we expect $got to produce my @expect = ( [ qw[ a b c ]] , [ qw[ d e f ]] ); now { state $counter = 0; $counter++; @expect or BAIL_OUT ( "the ${counter}th call to chunksOf wasn't expected. it contains " . join ',',@$_ ); my $e = shift @expect; is_deeply $e, $_ , "row $counter is expected"; } $got; ok ( (0 == @expect) , 'chunksOf finish the job' ); my @unused = fold $got; ok ( (0 == @unused) , 'chunksOf dont send extra stuff' ); 13_traverse.t100644001752000144 61312524003657 16147 0ustar00mcusers000000000000perlude-0.61/t/streamuse strict; use warnings; use 5.10.0; use Test::More; use Perlude; sub sum { now {state $sum = 0; $sum += $_ } shift } my @tests = ( [ undef, sub {} ] , [ 10 => take 10, sub {1} ] , [ 15 => unfold 1 .. 5 ] , [ 0 => take 10, cycle -1, 1 ] ); plan tests => 0+ @tests; for my $t (@tests) { my ( $sum, $i ) = @$t; is( sum($i), $sum, "sum = @{[ defined $sum ? $sum : '' ]}" ); } 20-old-take.t100644001752000144 431112524003657 15747 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use strict; use warnings; use Perlude; use Test::More tests => 12; my ( @input, $got, $expected ); # # @input = qw/ test toto tata tutu et le reste /; # $got = [fold takeWhile { /^t/ } sub { shift @input }]; # $expected = [qw/ test toto tata tutu /]; # # is_deeply( $got, $expected, "takeWhile works"); # sub begins_with_t ($) { takeWhile { /^t/ } shift } # my @t = qw/ toto tata aha /; # print "$_\n" for fold begins_with_t sub { shift @t } my $fold_ok = is ( fold( sub { () } ) , 0 , "fold works" ); unless ( $fold_ok ) { diag("fold failed so every other tests will fail too"); exit; } sub test_it { my ( $f, $input, $expected, $desc ) = @_; my $got = [fold $f->( unfold @$input ) ]; is_deeply( $got, $expected, $desc ); } for my $test ( [ sub { takeWhile { /^t/ } shift } , [qw/ toto tata haha got /] , [qw/ toto tata /] , "takeWhile ok" ], [ sub { take 2, shift } , [qw/ foo bar pan bing /] , [qw/ foo bar /] , "take ok" ], [ sub { filter { /a/ } shift } , [qw/ foo bar pan bing /] , [qw/ bar pan /] , "filter ok" ], ) { test_it @$test } @input = qw/ foo bar test /; sub eat { fold take shift, sub { @input ? (shift @input) : () } }; { my $take_test = 1; for my $takes ( [ [qw/ foo bar /] , [eat 2] ] , [ [qw/ test /] , [eat 10] ] , [ [] , [eat 10] ] ) { my ( $expected, $got ) = @$takes; is_deeply ( $got, $expected , "take test $take_test ok" ); $take_test++; } } SKIP: { skip "mapC not (yet?) reimplmented", 1; sub take2ones { take 2, sub { 1 } } $got = [ fold mapC { $_ + 1 } take2ones ]; $expected = [ 2, 2 ]; is_deeply( $got, $expected, 'mapC works'); } SKIP: { skip "mapR not (yet?) reimplmented", 2; my $count = 0; $got = mapR { $count+=$_ } take2ones; is( $got , undef, 'mapR returns nothing'); is( $count, 2, 'mapR did things'); } ($got) = fold drop 2, do { my @a = qw/ a b c d e f /; sub { @a ? (shift @a) : () } }; is( $got, 'c', 'drop works' ); ($got) = fold drop 2, do { my @a = qw/ /; sub { @a ? (shift @a) : () } }; is( $got, undef, 'drop works again' ); # take 3, cycle 1, 2; builtins.pm100644001752000144 410012524003657 16436 0ustar00mcusers000000000000perlude-0.61/lib/Perludepackage Perlude::builtins; use Perlude; use strict; use warnings; our $VERSION = '0.0'; my %builtins = ( abs => [ qw( abs chr cos defined exp glob hex int lc lcfirst length log oct ord quotemeta rand ref sin sqrt uc ucfirst unlink ) ], chomp => [qw( chomp chop )], pack => [qw( pack )], pop => [qw( pop shift )], reverse => [qw( readline reverse )], splice => [qw( splice )], split => [qw( split )], stat => [qw( lstat stat )], substr => [qw( substr )], unpack => [qw( unpack )], ); # the snippets of code for each builtin type my %code = ( abs => [ '$' => 'return apply { %s } $a[0]' ], chomp => [ '$' => 'return apply { %s; $_ } $a[0]' ], pack => [ '$$' => 'return apply { %s $a[0], @$_ } $a[1]' ], pop => [ '$' => 'return apply { %s @$_ } $a[0]' ], reverse => [ '$' => 'return apply { scalar %s $_ } $a[0]' ], splice => [ '$$$' => << 'CODE' ], return $a[1] ? apply { [ %s @$_, $a[0], $a[1] ] } $a[2] : apply { [ %s @$_, $a[0] ] } $a[2]; CODE split => [ '$$' => 'return apply { [ %s $a[0] ] } $a[1]' ], stat => [ '$' => 'return apply { [ %s $_ ] } $a[0]' ], substr => [ '$$$' => << 'CODE' ], return $a[1] ? apply { %s $_, $a[0], $a[1] } $a[2] : apply { %s $_, $a[0] } $a[2]; CODE unpack => [ '$$' => 'return apply { [ %s $a[0], $_ ] } $a[1]' ], ); # generate the functions for my $type ( keys %code ) { my ( $proto, $code ) = @{ $code{$type} }; my $count = $code =~ s/%s/%s/g; for my $builtin ( @{ $builtins{$type} } ) { no strict 'refs'; *{"f::$builtin"} = eval sprintf "sub ($proto) { my \@a = \@_; $code }", ($builtin) x $count; die $@ if $@; } } # and a nice alias *f::sub = \&Perlude::enlist; 1; __END__ 22-old-range.t100644001752000144 113612524003657 16123 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use strict; use warnings; use 5.10.0; use Test::More skip_all => 'range not implemented'; use Perlude qw/ fold range /; sub fold_for { my ( $args, $expected, $description ) = @_; is_deeply( [ fold range @$args ], $expected, $description ) } fold_for [1,5] , [1..5] , "range works with no step" ; fold_for [1,1] , [1] , "range with 1 element" ; fold_for [0,9,2] , [0, 2, 4, 6, 8] , "range with step 2" ; # plan skip_all => "i don't know what to do with inverted min,max"; # fold_for [5,1] , [] , "inverted range is empty" ; 11_consumers.t100644001752000144 325212524003657 16352 0ustar00mcusers000000000000perlude-0.61/t/streamuse strict; use warnings; use 5.10.0; use Test::More; use Perlude; my $limit = 10_000; my @tests = ( [ takeWhile => sub { $_ < 10 }, unfold( 1, 10, 4 ), [1] ] , [ takeWhile => sub { $_ < 10 }, unfold(), [] ] , [ takeWhile => sub { $_ < 10 }, unfold(10), [] ] , [ take => 2, unfold( 1 .. 30 ), [ 1, 2 ] ] , [ take => 2, unfold( 0, 1 ), [ 0, 1 ] ] , [ take => 2, unfold( 0, 1, 2 ), [ 0, 1 ] ] , [ take => 3, unfold(), [] ] , [ take => 3, unfold(1), [1] ] , [ take => 3, unfold( undef, 2 ), [ undef, 2 ] ] , [ take => 0, unfold( undef, 2 ), [] ] , [ take => -1, unfold( undef, 2 ), [] ] , [ take => 'ABC', sub { state $n; $n++ }, [] ] , [ take => 0.5, sub { state $n; $n++ }, [ 0 ] ] , [ drop => 2, unfold( 1 .. 30 ), [ 3 .. 30 ] ] , [ drop => 2, unfold( 0, 1 ), [] ] , [ drop => 2, unfold( 0, 1, 2 ), [2] ] , [ drop => 3, unfold(), [] ] , [ drop => 3, unfold(1), [] ] , [ drop => 3, unfold( undef, 2 ), [] ] , [ drop => 0, unfold( undef, 2 ), [ undef, 2 ] ] , [ drop => -1, unfold( undef, 2 ), [ undef, 2 ] ] , [ drop => "ABC", unfold( 1 .. 3 ), [ 1 .. 3 ] ] , [ drop => 0.1, unfold( 1 .. 3 ), [ 2, 3 ] ] , [ filter => sub { $_ % 2 }, unfold( 1, 2, 3 ), [1,3] ] , [ filter => sub { $_ % 2 }, sub { state $n = 0; $n++ } , [ grep { $_ % 2 } 0 .. ( 2 * $limit ) ] ] , [ apply => sub { $_ % 2 }, unfold( 1, 2, 3 ), [1,0,1] ] , [ apply => sub { $_ % 2 }, sub { state $n = 0; $n++ } , [ fold take $limit, cycle 0, 1 ] ] ); plan tests => 0+ @tests; for my $t (@tests) { my ( $name, $arg, $i, $out ) = @$t; no strict 'refs'; my @r = ref $arg eq 'CODE' ? fold take $limit, &$name( \&$arg, $i ) : fold take $limit, &$name( $arg, $i ); is_deeply( \@r, $out, $name); } Tutorial.pod100644001752000144 1012312524003657 16600 0ustar00mcusers000000000000perlude-0.61/lib/Perlude =head1 Write your own generators Have you ever miss the shell pipe in Perl? Such a clever operator: it streams data from programs to programs on demand, which means that nothing will compute more than expected by the whole stream, any part the pipe can stop the whole stream. seq 1000 will compute 1000 lines seq 1000 | sed 5q will compute only 5 lines as sed ends its job there. We somehow miss it in perl. Sure, we have grep and map but they are only acting in complete arrays. Perlude comes with all most common filters, i doubt you'll missing one. If you do so: please feedback and i'll probably add it! So your job is about writing generators (or using those written in Perlude::Stuff and Perlude::Sh). If you have to write a generic one, please contribute. To write them, you have to understand the Perlude conventions. When you're using an iterator, say C, you have to think about the whole list of potential results. Those can be written as ( 1, 2, 3, 4 ) ( 1, 2, 3, 4, ) ( 1, 2, 3, 4, () ) so C<()> is used as a list terminator. your iterator must return one scalar by call and last it work sending a terminator. As exemple: sub read_file { open my $fh, shift; sub { <$fh> // () } } now you can write now {say} filter {/foo/} take 5, read_file "test.txt" which is equivalent to sed 5q test.txt | grep foo Not only it's easy to read and write, its behaviour is also the best expected: =over =item * it reads one record, use it and forget it before reading the next record. This is a memory friendly behavior =item * whoever in the pipe can decide to stop it. For example: it's fine for grep to release 3 records only. =back Writing unix filters is really easy. Also note that filters/generators compositions rules are simple and powerfull G | F => G F | F => F If you wrote shell, powershell, perl6 or any other functionnal langage, you probably miss it coming back to perl5. Basically, on demand lists are just iterators. Perlude is just a pleasant way to deal with them stealing keywords from haskell Perlude. =head3 example As example: What are the 5 first naturals containing a 3? A perl implementation would be: for ( my $_=0, my $count=0 ; $count <= 5 ; $_++ ) { if (/3/) { $count++; say } } Hard to read ... and worth: nothing is reusable at all The shell counterpart would be nat () { while {true} { print $[i++] } } nat | grep 3 | head -n5 There are things to understand about the shell elegance: =over 2 =item * there is no need of a counter variable, neither a for loop: head is the single command which handles it for you. =item * the implementation of nat is bare simple: you just focus on your nat problem, you don't care how many elements the filter could need. =item * you added nat to your toolkit, it's much more pain to resuse it in perl ... before Perlude =back also, it's easy to create a new function 'top5' by passsing a an argument to head (looks like a partial application): top5 () { head -n5 } contains3 () { grep 3 } nat | contains3 | top5 No perl builtin provide this power. =head2 I can haz nat in perl ? Perlude is a set of functions that takes closures as arguments, and returns others nat is the basic closure exemple: my $nat = sub { state $x=0; $x++ } a reusable way to write it would be: sub nat_from { my $x = shift; sub { $x++ } } sub nat { nat_from 0 } now you can use Perlude keywords on this functions sub evens_in { filter { not( $_ % 2 ) } shift } sub top5 { take 5, shift } =head1 Other dynamic langages stuff Ruby : Rubylude was written by Nono after RMLL'11 https://github.com/nono/Rubylude Javascript : http://weepy.github.com/kaffeine/ was quoted it the python pipe intro but i guess it's useless as http://jashkenas.github.com/coffee-script/ is javascript made right. Python : https://github.com/JulienPalard/Pipe with an introduction here: http://dev-tricks.net/pipe-infix-syntax-for-python =head1 Contribute http://github.com/eiro/p5-perlude perlude-test-lines-data100644001752000144 1712524003657 16666 0ustar00mcusers000000000000perlude-0.61/ttoto tata tutu 21-old-takeWhile.t100644001752000144 55612524003657 16414 0ustar00mcusers000000000000perlude-0.61/t/lazy#! /usr/bin/perl use strict; use warnings; use Perlude::Lazy; use Test::More; plan tests => 2; my ( @input, $got, $expected ); my $doubles = do { my $seed = 0; enlist { $seed+=2 } }; my @first = fold takeWhile { $_ < 5 } $doubles; is_deeply \@first, [2, 4]; $TODO = 'dolmen says this test is broken'; my ($next) = fold take 1, $doubles; is $next, 6; 21-old-takeWhile.t100644001752000144 57512524003657 16731 0ustar00mcusers000000000000perlude-0.61/t/stream#! /usr/bin/perl use 5.10.0; use strict; use warnings; use Perlude; use Test::More skip_all => 'this is not fixable'; my ( @input, $got, $expected ); my $doubles = sub { state $seed = 0; $seed+=2; }; my @first = fold takeWhile { $_ < 5 } $doubles; is_deeply \@first, [2, 4]; $TODO = 'dolmen says this test is broken'; my ($next) = fold take 1, $doubles; is $next, 6;