NAME
   Operator::Util - A selection of array and hash functions that extend
   operators

VERSION
   This document describes Operator::Util version 0.02.

SYNOPSIS
       use Operator::Util qw(
           reduce reducewith
           zip zipwith
           cross crosswith
           hyper hyperwith
           applyop reverseop
       );

WARNING
   This is a pre-alpha release of Operator::Util. Not all features
   described in this document are complete. Please see the "TODO" list for
   details. The interface and functionality may change in the future based
   on user feedback. Please make suggestions by creating an issue at
   <http://github.com/patch/operator-util-pm5/issues>.

DESCRIPTION
   A pragmatic approach at providing the functionality of many of Perl 6's
   meta-operators in Perl 5.

   The terms "operator string" or "opstring" are used to describe a string
   that represents an operator, such as the string '+' for the addition
   operator or the string '.' for the concatenation operator. Except where
   noted, opstrings default to binary infix operators and the short form
   may be used, e.g., '*' instead of 'infix:*'. All other operator types
   (prefix, postfix, circumfix, and postcircumfix) must have the type
   prepended in the opstrings, e.g., "prefix:++" and "postcircumfix:{}".

   When a list is passed as an argument for any of the functions, it must
   be either an array reference or a scalar value that will be used as a
   single-element list.

   The following functions are provided but are not exported by default.

 Reduction
   reduce OPSTRING, LIST [, triangle => 1 ]
       "reducewith" is an alias for "reduce". It may be desirable to use
       "reducewith" to avoid naming conflicts or confusion with "reduce" in
       List::Util.

       Any infix opstring (except for non-associating operators) can be
       passed to "reduce" along with an arrayref to reduce the array using
       that operation:

           reduce('+', [1, 2, 3])  # 1 + 2 + 3 = 6
           my @a = (5, 6)
           reduce('*', \@a)        # 5 * 6 = 30

       "reduce" associates the same way as the operator used:

           reduce('-', [4, 3, 2])   # 4-3-2 = (4-3)-2 = -1
           reduce('**', [4, 3, 2])  # 4**3**2 = 4**(3**2) = 262144

       For comparison operators (like "<"), all reduced pairs of operands
       are broken out into groups and joined with "&&" because Perl 5
       doesn't support comparison operator chaining:

           reduce('<', [1, 3, 5])  # 1 < 3 && 3 < 5

       If fewer than two elements are given, the results will vary
       depending on the operator:

           reduce('+', [])   # 0
           reduce('+', [5])  # 5
           reduce('*', [])   # 1
           reduce('*', [5])  # 5

       If there is one element, the "reduce" returns that one element.
       However, this default doesn't make sense for operators like "<" that
       don't return the same type as they take, so these kinds of operators
       overload the single-element case to return something more
       meaningful.

       You can also reduce the comma operator, although there isn't much
       point in doing so. This just returns an arrayref that compares
       deeply to the arrayref passed in:

           [1, 2, 3]
           reduce(',', [1, 2, 3])  # same thing

       Operators with zero-element arrayrefs return the following values:

           **    # 1    (arguably nonsensical)
           =~    # 1    (also for 1 arg)
           !~    # 1    (also for 1 arg)
           *     # 1
           /     # fail (reduce is nonsensical)
           %     # fail (reduce is nonsensical)
           x     # fail (reduce is nonsensical)
           +     # 0
           -     # 0
           .     # ''
           <<    # fail (reduce is nonsensical)
           >>    # fail (reduce is nonsensical)
           <     # 1    (also for 1 arg)
           >     # 1    (also for 1 arg)
           <=    # 1    (also for 1 arg)
           >=    # 1    (also for 1 arg)
           lt    # 1    (also for 1 arg)
           le    # 1    (also for 1 arg)
           gt    # 1    (also for 1 arg)
           ge    # 1    (also for 1 arg)
           ==    # 1    (also for 1 arg)
           !=    # 1    (also for 1 arg)
           eq    # 1    (also for 1 arg)
           ne    # 1    (also for 1 arg)
           ~~    # 1    (also for 1 arg)
           &     # -1   (from ^0, the 2's complement in arbitrary precision)
           |     # 0
           &&    # 1
           ||    # 0
           //    # 0
           =     # undef (same for all assignment operators)
           ,     # []

       You can say

           reduce('||', [a(), b(), c(), d()])

       to return the first true result, but the evaluation of the list is
       controlled by the semantics of the list, not the semantics of "||".

       To generate all intermediate results along with the final result,
       you can set the "triangle" argument:

           reduce('+', [1..5], triangle=>1)  # (1, 3, 6, 10, 15)

       The visual picture of a triangle is not accidental. To produce a
       triangular list of lists, you can use a "triangular comma":

           reduce(',', [1..5], triangle=>1)
           # [1],
           # [1,2],
           # [1,2,3],
           # [1,2,3,4],
           # [1,2,3,4,5]

 Zip
   zip OPSTRING, LIST1, LIST2
   zip LIST1, LIST2
       "zipwith" is an alias for "zip".

       The "zip" function may be passed any infix opstring. It applies the
       operator across all groupings of its list elements.

       The string concatenating form is:

           zip('.', ['a','b'], [1,2])  # ('a1', 'b2')

       The list concatenating form when used like this:

           zip(',', ['a','b'], [1,2], ['x','y'])

       produces

           ('a', 1, 'x', 'b', 2, 'y')

       This list form is common enough to have a shortcut, calling "zip"
       without an opstring as the first argument will use "," by default:

           zip(['a','b'], [1,2], ['x','y'])

       also produces

           ('a', 1, 'x', 'b', 2, 'y')

       Any non-mutating infix operator may be used.

           zip('*', [1,2], [3,4])  # (3, 8)

       All assignment operators are considered mutating.

       If the underlying operator is non-associating, so is the cross
       operator, except for basic comparison operators since a chaining
       workaround is provided:

           zip('cmp', \@a, \@b, \@c)  # ILLEGAL
           zip('eq', \@a, \@b, \@c)   # ok

       The underlying operator is always applied with its own
       associativity, just as the corresponding "reduce" operator would do.

       All lists are assumed to be flat; multidimensional lists are handled
       by treating the first dimension as the only dimension.

       The response is a flat list by default. To return a list of
       arrayrefs, unset the "flat" argument:

           zip(['a','b'], [1,2], ['x','y'], flat=>0)

       produces:

           (['a', 1, 'x'], ['b', 2, 'y'])

 Cross
   cross OPSTRING, LIST1, LIST2
   cross LIST1, LIST2
       "crosswith" is an alias for "cross".

       The "cross" function may be passed any infix opstring. It applies
       the operator across all groupings of its list elements.

       The string concatenating form is:

           cross('.', ['a','b'], [1,2])  # ('a1', 'a2', 'b1', 'b2')

       The list concatenating form when used like this:

           cross(',', ['a','b'], [1,2], ['x','y'])

       produces

           'a', 1, 'x',
           'a', 1, 'y',
           'a', 2, 'x',
           'a', 2, 'y',
           'b', 1, 'x',
           'b', 1, 'y',
           'b', 2, 'x',
           'b', 2, 'y'

       This list form is common enough to have a shortcut, calling "cross"
       without an opstring as the first argument will use "," by default:

           cross(['a','b'], [1,2], ['x','y'])

       Any non-mutating infix operator may be used.

           cross('*', [1,2], [3,4])  # (3, 4, 6, 8)

       All assignment operators are considered mutating.

       If the underlying operator is non-associating, so is the cross
       operator, except for basic comparison operators since a chaining
       workaround is provided:

           cross('cmp', \@a, \@b, \@c)  # ILLEGAL
           cross('eq', \@a, \@b, \@c)   # ok

       The underlying operator is always applied with its own
       associativity, just as the corresponding "reduce" operator would do.

       All lists are assumed to be flat; multidimensional lists are handled
       by treating the first dimension as the only dimension.

       The response is a flat list by default. To return a list of
       arrayrefs, unset the "flat" argument:

           cross(['a','b'], [1,2], ['x','y'], flat=>0)

       produces:

           ['a', 1, 'x'],
           ['a', 1, 'y'],
           ['a', 2, 'x'],
           ['a', 2, 'y'],
           ['b', 1, 'x'],
           ['b', 1, 'y'],
           ['b', 2, 'x'],
           ['b', 2, 'y']

 Hyper
   hyper OPSTRING, LIST1, LIST2 [, dwim_left => 1, dwim_right => 1 ]
   hyper OPSTRING, LIST
       "hyperwith" is an alias for "hyper".

       The "hyper" function operates on each element of its arrayref
       argument (or arguments) and returns a single list of the results. In
       other words, "hyper" distributes the operator over its elements as
       lists.

            hyper('prefix:-' [1,2,3])             # (-1,-2,-3)
            hyper('+', [1,1,2,3,5], [1,2,3,5,8])  # (2,3,5,8,13)

       Unary operators always produce a list of exactly the same shape as
       their single argument. When infix operators are presented with two
       arrays of identical shape, a result of that same shape is produced.
       Otherwise the result depends on what "dwim" arguments are passed.

       For an infix operator, if either argument is insufficiently
       dimensioned, "hyper" "upgrades" it, but only if you tell it to
       "dwim" on that side.

            hyper('-', [3,8,2,9,3,8], 1, dwim_right=>1)  # (2,7,1,8,2,7)
            hyper('+=', \@array, 42, dwim_right=>1)      # add 42 to each element

       If you don't know whether one side or the other will be
       under-dimensioned, you can dwim on both sides:

           hyper('*', $left, $right, dwim=>1)

       The upgrade never happens on the non-dwim end of a "hyper". If you
       write

           hyper('*', $bigger, $smaller, dwim_left=>1)
           hyper('*', $smaller, $bigger, dwim_right=>1)

       an exception is thrown, and if you write

           hyper('*', $foo, $bar)

       you are requiring the shapes to be identical, or an exception will
       be thrown.

       For all hyper dwimminess, if a scalar is found where the other side
       expects an array, the scalar is considered to be an array of one
       element.

       Once we have two lists to process, we have to decide how to put the
       elements into correspondence. If both sides are dwimmy, the short
       list will have to be repeated as many times as necessary to make the
       appropriate number of elements.

       If only one side is dwimmy, then the list on that side only will be
       grown or truncated to fit the list on the non-dwimmy side.

       This produces an array the same length as the corresponding
       dimension on the other side. The original operator is then
       recursively applied to each corresponding pair of elements, in case
       there are more dimensions to handle.

       Here are some examples:

           hyper('+', [1,2,3,4], [1,2]               ) # always error
           hyper('+', [1,2,3,4], [1,2], dwim=>1      ) # (2,4,4,6) rhs dwims to 1,2,1,2
           hyper('+', [1,2,3],   [1,2], dwim=>1      ) # (2,4,4)   rhs dwims to 1,2,1
           hyper('+', [1,2,3,4], [1,2], dwim_left=>1 ) # (2,4)     lhs dwims to 1,2
           hyper('+', [1,2,3,4], [1,2], dwim_right=>1) # (2,4,4,6) rhs dwims to 1,2,1,2
           hyper('+', [1,2,3],   [1,2], dwim_right=>1) # (2,4,4)   rhs dwims to 1,2,1
           hyper('+', [1,2,3],   1,     dwim_right=>1) # (2,3,4)   rhs dwims to 1,1,1

       Another way to look at it is that the dwimmy array's elements are
       indexed modulo its number of elements so as to produce as many or as
       few elements as necessary.

       Note that each element of a dwimmy list may in turn be expanded into
       another dimension if necessary, so you can, for instance, add one to
       all the elements of a matrix regardless of its dimensionality:

           hyper('+=', \@fancy, 1, dwim_right=>1)

       On the non-dwimmy side, any scalar value will be treated as an array
       of one element, and for infix operators must be matched by an
       equivalent one-element array on the other side. That is, "hyper" is
       guaranteed to degenerate to the corresponding scalar operation when
       all its arguments are non-array arguments.

       When using a unary operator no dwimmery is ever needed:

            @negatives = hyper('prefix:-', \@positives)

            hyper('postfix:++', \@positions)              # increment each
            hyper('->', \@objects, 'run', dwim_right=>1)  # call ->run() on each
            hyper('length', ['f','oo','bar'])             # (1, 2, 3)

       Note that method calls are infix operators with a string used for
       the method name.

       Hyper operators are defined recursively on nested arrays, so:

           hyper('prefix:-', [[1, 2], 3])  # ([-1, -2], -3)

       Likewise the dwimminess of dwimmy infixes propagates:

           hyper('+', [[1, 2], 3], [4, [5, 6]], dwim=>1)  # [[5, 6], [8, 9]]

       "hyper" may be applied to hashes as well as to arrays. In this case
       "dwimminess" says whether to ignore keys that do not exist in the
       other hash, while "non-dwimminess" says to use all keys that are in
       either hash. That is,

           hyper('+', \%foo, \%bar, dwim=>1)

       gives you the intersection of the keys, while

           hyper('+', \%foo, \%bar)

       gives you the union of the keys. Asymmetrical hypers are also
       useful; for instance, if you say:

           hyper('+', \%outer, \%inner, dwim_right=>1)

       only the %inner keys that already exist in %outer will occur in the
       result. Note, however, that you want

           hyper('+=', \%outer, \%inner)

       in order to pass accumulated statistics up a tree, assuming you want
       %outer to have the union of keys.

       Unary hash hypers and binary hypers that have only one hash operand
       will apply the hyper operator to just the values but return a new
       hash value with the same set of keys as the original hash.

            hyper('prefix:-' {a => 1, b => 2, c => 3})  # (a => -1, b => -2, c => -3)

 Other utils
   applyop OPSTRING, OPERAND1, OPERAND2
   applyop OPSTRING, OPERAND
       If three arguments are provided to "applyop", apply the binary
       operator OPSTRING to the operands OPERAND1 and OPERAND2. If two
       arguments are provided, apply the unary operator OPSTRING to the
       operand OPERAND. The unary form defaults to using prefix operators,
       so 'prefix:' may be omitted, e.g., '++' instead of 'prefix:++';

           applyop '.', 'foo', 'bar'  # foobar
           applyop '++', 5            # 6

   reverseop OPSTRING, OPERAND1, OPERAND2
       "reverseop" provides the same functionality as "applyop" except that
       OPERAND1 and OPERAND2 are reversed.

           reverseop '.', 'foo', 'bar'  # barfoo

       If an unary opstring is used, "reverseop" has the same functionality
       as "applyop".

   The optional named-argument "flat" can be passed to "reduce", "zip",
   "cross", and "hyper". It defaults to 1, which causes the function to
   return a flat list. When set to 0, it causes the return value from each
   operator to be stored in an array ref, resulting in a "list of lists"
   being returned from the function.

       zip [1..3], ['a'..'c']             # 1, 'a', 2, 'b', 3, 'c'
       zip [1..3], ['a'..'c'], flat => 0  # [1, 'a'], [2, 'b'], [3, 'c']

TODO
   *   Allow more than two arrayrefs with "zip", "cross", and "hyper"

   *   Support default operator with "zip" and "cross"

   *   Support multi-dimensional distribution with "hyper"

   *   Support hashes with "hyper"

   *   Support the "flat => 0" option

   *   Support chaining "reduce"

   *   Add special cases for zero- and one-element arrays with "reduce"

   *   Add "warn"ings on errors instead of simply "return"ing

   *   Add named unary operators such as "uc" and "lc"

   *   Support meta-operator literals such as "Z" and "X"

   *   Should the first argument optionally be a subroutine ref instead of
       an operator string?

   *   Should the "flat => 0" option be changed to "lol => 1"?

   *   Convert tests to TestML

SEE ALSO
   *   perlop

   *   "pairwise" in List::MoreUtils is similar to "zip" except that its
       first argument is a block instead of an operator string and the
       remaining arguments are arrays instead of array refs:

           pairwise { $a + $b }, @array1, @array2  # List::MoreUtils
           zip '+', \@array1, \@array2             # Operator::Util

   *   "mesh" a.k.a. "zip" in List::MoreUtils is similar to "zip" when
       using the default operator ',' except that the arguments are arrays
       instead of array refs:

           mesh @array1, @array2   # List::MoreUtils
           zip \@array1, \@array2  # Operator::Util

   *   Set::CrossProduct is an object-oriented alternative to "cross" when
       using the default operator ","

   *   The "Meta operators" section of Synopsis 3: Perl 6 Operators
       (<http://perlcabal.org/syn/S03.html#Meta_operators>) is the
       inspiration for this module

AUTHOR
   Nick Patch <[email protected]>

ACKNOWLEDGEMENTS
   *   This module is based on the Perl 6 specification, as described in
       the Synopsis and implemented in Rakudo

   *   Much of the documentation is taken directly from Synopsis 3: Perl 6
       Operators (<http://perlcabal.org/syn/S03.html>)

   *   The tests were forked from the Official Perl 6 Test Suite
       (<https://github.com/perl6/roast>)

COPYRIGHT AND LICENSE
   Copyright 2010, 2011 Nick Patch

   This library is free software; you can redistribute it and/or modify it
   under the same terms as Perl itself.