You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2010/12/28 03:12:28 UTC

svn commit: r1053242 - in /tapestry/tapestry5/trunk/tapestry-func/src: main/java/org/apache/tapestry5/func/ test/java/org/apache/tapestry5/func/

Author: hlship
Date: Tue Dec 28 02:12:27 2010
New Revision: 1053242

URL: http://svn.apache.org/viewvc?rev=1053242&view=rev
Log:
TAP5-1390: Functional programming improvements

Squashed commit of the following:

commit cb241ca26a621f763cca45998edc6b2bec4c8bdc
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 18:05:28 2010 -0800

    Add quick way to remove nulls from a flow

commit c0fd7b0d406ab21c563067a951995a2538567acc
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 17:55:26 2010 -0800

    Simplify part of ZippedFlowImpl

commit a443fac51ac1a41982d0a1f7617576793f945ff5
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 17:54:10 2010 -0800

    Add method to map the tuples of a ZippedFlow to tuples of a new ZippedFlow, and extract the firsts and seconds of the tuples into flows

commit 1e43c42d263008202ca692c7d5e9f8c0c7173c11
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 09:48:05 2010 -0800

    Move reduce() from Flow to FlowOperations

commit 2dca8c17f4689a216669dd9c63dfba8fe101a659
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 09:41:03 2010 -0800

    Test ability to concat to a ZippedFlow

commit ce889e0e5584fb855620a69619f4a36f161b6217
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 09:40:51 2010 -0800

    Describe how the values from the two flows are combined in the Tuple

commit 776b3042af34ddf81060279b5b8936fa95a4d86c
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Mon Dec 27 09:40:34 2010 -0800

    Move TupleTests to correct package

commit 9bc84c2bad9b5fafa1be082638235f5c7f87d2da
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Sun Dec 26 10:11:25 2010 -0800

    Fill in more tests

commit d2c9a713d65b5e0d5baa82faaaaf4317d0078690
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Sun Dec 26 10:11:14 2010 -0800

    Add missing copyright

commit 631c091fd498d6c53df9f7bef8b7959c397bcfd6
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Sun Dec 26 09:21:29 2010 -0800

    Create a distinct interface for ZippedFlows and factor out common operations into FlowOperations

commit f8b08b4b8fe26e5b1372cb36b08401011d32e8bd
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Sat Dec 25 14:35:23 2010 -0800

    Introduce a basic ZippedFlow interface and implementation

commit 2032c175c255a3862f7ef5a9c350153fb57d11db
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Thu Dec 23 17:19:32 2010 -0800

    Flesh out the Javadoc on a number of methods

commit 265a6adb83b4279380e864c1fbf218bfc23233c9
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Thu Dec 23 17:19:14 2010 -0800

    Add some additional tests on Tuple

commit ec9515193e44ce6a202cd997a05785a271ce3756
Author: Howard M. Lewis Ship <hl...@gmail.com>
Date:   Thu Dec 23 11:22:18 2010 -0800

    Add a Tuple class and Flow.zipWith() to zip two flows together into a flow of Tuples

Added:
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/FlowOperations.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZip.java
      - copied, changed from r1052930, tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZipValue.java
      - copied, changed from r1052930, tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlow.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlowImpl.java
    tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java
    tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/ZippedFlowTests.java
Modified:
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/AbstractFlow.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/EmptyFlow.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/F.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Flow.java
    tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java
    tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/FuncTest.java
    tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/MapperTest.java
    tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TakeDropTests.java

Modified: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/AbstractFlow.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/AbstractFlow.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/AbstractFlow.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/AbstractFlow.java Tue Dec 28 02:12:27 2010
@@ -24,8 +24,8 @@ import java.util.List;
 import java.util.Set;
 
 /**
- * Abstract base class for implementations of {@link Flow}. Subclasses typically override some methods
- * for either efficiency, or for the concern they embrace.
+ * Abstract base class for implementations of {@link Flow}. Subclasses typically override some
+ * methods for either efficiency, or for the concern they embrace.
  * 
  * @since 5.2.0
  */
@@ -246,4 +246,18 @@ abstract class AbstractFlow<T> implement
         return Collections.unmodifiableSet(set);
     }
 
+    public <X> ZippedFlow<T, X> zipWith(Flow<X> otherFlow)
+    {
+        assert otherFlow != null;
+
+        Flow<Tuple<T, X>> tupleFlow = F.lazy(new LazyZip<T, X>(this, otherFlow));
+
+        return ZippedFlowImpl.create(tupleFlow);
+    }
+
+    public Flow<T> removeNulls()
+    {
+        return remove(F.isNull());
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/EmptyFlow.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/EmptyFlow.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/EmptyFlow.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/EmptyFlow.java Tue Dec 28 02:12:27 2010
@@ -127,5 +127,9 @@ class EmptyFlow<T> extends AbstractFlow<
         return Collections.emptySet();
     }
 
-    
+    @Override
+    public Flow<T> removeNulls()
+    {
+        return this;
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/F.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/F.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/F.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/F.java Tue Dec 28 02:12:27 2010
@@ -18,15 +18,17 @@ import java.util.Collection;
 import java.util.Iterator;
 
 /**
- * Functional operations on collections with generics support. The core interface is {@link Flow} to which operations
+ * Functional operations on collections with generics support. The core interface is {@link Flow} to
+ * which operations
  * and transformations
- * (in terms of {@link Predicate}s, {@link Mapper}s and {@link Reducer}s) to create new Flows. Flows are initially
+ * (in terms of {@link Predicate}s, {@link Mapper}s and {@link Reducer}s) to create new Flows. Flows
+ * are initially
  * created
  * using {@link #flow(Collection)} and {@link #flow(Object...)}.
  * <p>
- * F will be used a bit, thus it has a short name (for those who don't like static imports). It provides a base set of
- * Predicate, Mapper and Reducer factories. A good development pattern for applications is to provide a similar,
- * application-specific, set of such factories.
+ * F will be used a bit, thus it has a short name (for those who don't like static imports). It
+ * provides a base set of Predicate, Mapper and Reducer factories. A good development pattern for
+ * applications is to provide a similar, application-specific, set of such factories.
  * 
  * @since 5.2.0
  */
@@ -41,6 +43,9 @@ public class F
         return (Flow<T>) EMPTY_FLOW;
     }
 
+    /**
+     * A Predicate factory for equality against a specified value.
+     */
     public static <T> Predicate<T> eql(final T value)
     {
         return new Predicate<T>()
@@ -52,6 +57,10 @@ public class F
         };
     }
 
+    /**
+     * A Predicate factory for comparison of a Number against a fixed value; true
+     * if the Number equals the value.
+     */
     public static Predicate<Number> eq(final long value)
     {
         return new Predicate<Number>()
@@ -63,11 +72,19 @@ public class F
         };
     }
 
+    /**
+     * A Predicate factory for comparison of a Number against a fixed value; true
+     * if the Number does not equal the value.
+     */
     public static Predicate<Number> neq(long value)
     {
         return eq(value).invert();
     }
 
+    /**
+     * A Predicate factory for comparison of a Number against a fixed value; true
+     * if the number is greater than the value.
+     */
     public static Predicate<Number> gt(final long value)
     {
         return new Predicate<Number>()
@@ -79,21 +96,36 @@ public class F
         };
     }
 
+    /**
+     * A Predicate factory for comparison of a Number against a fixed value; true
+     * if the Number is greater than or equal to the value.
+     */
     public static Predicate<Number> gteq(long value)
     {
         return eq(value).or(gt(value));
     }
 
+    /**
+     * A Predicate factory for comparison of a Number against a fixed value; true
+     * if the Number is less than the value.
+     */
     public static Predicate<Number> lt(long value)
     {
         return gteq(value).invert();
     }
 
+    /**
+     * A Predicate factory for comparison of a Number against a fixed value; true
+     * if the Number is less than or equal to the value.
+     */
     public static Predicate<Number> lteq(long value)
     {
         return gt(value).invert();
     }
 
+    /**
+     * A Predicate factory; returns true if the value from the Flow is null.
+     */
     public static <T> Predicate<T> isNull()
     {
         return new Predicate<T>()
@@ -105,6 +137,9 @@ public class F
         };
     }
 
+    /**
+     * A Predicate factory; returns true if the value from the Flow is not null.
+     */
     public static <T> Predicate<T> notNull()
     {
         Predicate<T> isNull = isNull();
@@ -112,6 +147,10 @@ public class F
         return isNull.invert();
     }
 
+    /**
+     * A Mapper factory that gets the string value of the flow value using
+     * {@link String#valueOf(Object)}.
+     */
     public static <T> Mapper<T, String> stringValueOf()
     {
         return new Mapper<T, String>()
@@ -123,7 +162,10 @@ public class F
         };
     }
 
-    /** Returns a Mapper that ignores its input value and always returns a predetermined result. */
+    /**
+     * A Mapper factory; the returned Mapper ignores its input value and always returns a
+     * predetermined result.
+     */
     public static <S, T> Mapper<S, T> always(final T fixedResult)
     {
         return new Mapper<S, T>()
@@ -136,8 +178,8 @@ public class F
     }
 
     /**
-     * Mapper factory that combines a Predicate with two {@link Mapper}s; evaluating the predicate selects one of the
-     * two mappers.
+     * A Mapper factory that combines a Predicate with two {@link Mapper}s; evaluating the predicate
+     * selects one of the two mappers.
      * 
      * @param predicate
      *            evaluated to selected a coercion
@@ -165,7 +207,8 @@ public class F
     }
 
     /**
-     * Override of {@link #select(Predicate, Mapper, Mapper)} where rejected values are replaced with null.
+     * Override of {@link #select(Predicate, Mapper, Mapper)} where rejected values are replaced
+     * with null.
      */
     public static <S, T> Mapper<S, T> select(Predicate<? super S> predicate, Mapper<S, T> ifAccepted)
     {
@@ -173,7 +216,8 @@ public class F
     }
 
     /**
-     * Override of {@link #select(Predicate, Mapper)} where rejected values are replaced with a fixed value.
+     * Override of {@link #select(Predicate, Mapper)} where rejected values are replaced with a
+     * fixed value.
      */
     public static <S, T> Mapper<S, T> select(Predicate<? super S> predicate, Mapper<S, T> ifAccepted, T ifRejectedValue)
     {
@@ -182,7 +226,7 @@ public class F
         return select(predicate, ifAccepted, rejectedMapper);
     }
 
-    /** The identity mapper simply returns the input unchanged. */
+    /** A Mapper factory; the Mapper returns the the flow value unchanged. */
     public static <S> Mapper<S, S> identity()
     {
         return new Mapper<S, S>()
@@ -208,6 +252,9 @@ public class F
         };
     }
 
+    /**
+     * A Reducer that operates on a Flow of Integers and is used to sum the values.
+     */
     public static Reducer<Integer, Integer> SUM_INTS = new Reducer<Integer, Integer>()
     {
         public Integer reduce(Integer accumulator, Integer value)
@@ -216,6 +263,10 @@ public class F
         };
     };
 
+    /**
+     * A two-input Mapper used to add the values from two Flows of Integers into a Flow of Integer
+     * sums.
+     */
     public static Mapper2<Integer, Integer, Integer> ADD_INTS = new Mapper2<Integer, Integer, Integer>()
     {
         public Integer map(Integer first, Integer second)
@@ -252,8 +303,9 @@ public class F
 
     /**
      * Creates a lazy Flow from the {@link Iterator} obtained from the iterable. The Flow
-     * will be threadsafe as long as the iterable yields a new Iterator on each invocation <em>and</em> the underlying
-     * iterable object is not modified while the Flow is evaluating. In other words, not extremely threadsafe.
+     * will be threadsafe as long as the iterable yields a new Iterator on each invocation
+     * <em>and</em> the underlying iterable object is not modified while the Flow is evaluating.
+     * In other words, not extremely threadsafe.
      */
     public static <T> Flow<T> flow(Iterable<T> iterable)
     {
@@ -306,7 +358,8 @@ public class F
 
     /**
      * Creates a lazy, infinte Flow consisting of the initial value, then the result of passing
-     * the initial value through the Mapper, and so forth, which each step value passed through the mapper
+     * the initial value through the Mapper, and so forth, which each step value passed through the
+     * mapper
      * to form the next step value.
      */
     public static <T> Flow<T> iterate(final T initial, final Mapper<T, T> mapper)
@@ -323,6 +376,9 @@ public class F
         });
     }
 
+    /**
+     * A Worker factory; the returnedWorker adds the values to a provided collection.
+     */
     public static <T> Worker<T> addToCollection(final Collection<T> coll)
     {
         return new Worker<T>()

Modified: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Flow.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Flow.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Flow.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Flow.java Tue Dec 28 02:12:27 2010
@@ -14,38 +14,39 @@
 
 package org.apache.tapestry5.func;
 
-import java.util.Comparator;
 import java.util.List;
-import java.util.Set;
 
 /**
- * A Flow is a a functional interface for working with an ordered collection of values.
- * A given Flow contains only values of a particular type. Standard operations allow for
- * filtering the Flow, or appending values to the Flow. Since Flows are immutable, all operations
- * on Flows return new immutable Flows. Flows are thread safe (to the extent that the {@link Mapper}s, {@link Predicate}
- * s, {@link Worker}s and {@link Reducer}s applied to the Flow are).
- * Flows are <em>lazy</em>: filtering, mapping, and concatenating Flows will do so with no, or a minimum, of evaluation.
- * However, converting a Flow into a {@link List} will force a realization of the entire Flow.
+ * A flow is a a functional interface for working with an ordered collection of elements.
+ * A given Flow contains only elements of a particular type. Standard operations allow for
+ * filtering the flow, or appending elements to the Flow. Since flows are immutable, all operations
+ * on flows return new immutable flows. Flows are thread safe (to the extent that the {@link Mapper}
+ * , {@link Predicate}, {@link Worker} and {@link Reducer} objects applied to the flow are).
+ * Flows are <em>lazy</em>: filtering, mapping, and concatenating flows will do so with no, or a
+ * minimum, of evaluation. However, converting a Flow into a {@link List} (or other collection) will
+ * force a realization of the entire flow.
  * <p>
- * In some cases, a Flow may be an infinite, lazily evaluated sequence. Operations that iterate over all values (such as
- * {@link #count()} or {@link #reduce(Reducer, Object)}) may become infinite loops.
+ * In some cases, a flow may be an infinite, lazily evaluated sequence. Operations that iterate over
+ * all elements (such as {@link #count()} or {@link #reduce(Reducer, Object)}) may become infinite
+ * loops.
  * <p>
- * Using Flows allows for a very fluid interface.
+ * Using flows allows for a very fluid interface.
  * <p>
- * Flows are initially created using {@link F#flow(java.util.Collection)} or {@link F#flow(Object...)}.
+ * Flows are initially created using {@link F#flow(java.util.Collection)}, {@link F#flow(Object...)}
+ * or {@link F#flow(Iterable)}.
  * 
  * @since 5.2.0
  * @see F#lazy(LazyFunction)
  */
-public interface Flow<T> extends Iterable<T>
+public interface Flow<T> extends FlowOperations<T, Flow<T>>
 {
     /** Maps a Flow into a new Flow with different type values. Mapping is a lazy operation. */
     <X> Flow<X> map(Mapper<T, X> mapper);
 
     /**
-     * Combines two Flows using a two-parameter Mapper. Each value of
-     * this Flow, and the corresponding value of the other flow are passed through the Mapper
-     * to provide the values of the output Flow. The length of the result Flow is
+     * Combines two Flows using a two-parameter Mapper. Each element of
+     * this Flow, and the corresponding element of the other flow are passed through the Mapper
+     * to provide the elements of the output Flow. The length of the result Flow is
      * the smaller of the lengths of the two input Flows. Mapping is a lazy operation.
      */
     <X, Y> Flow<Y> map(Mapper2<T, X, Y> mapper, Flow<? extends X> flow);
@@ -57,66 +58,26 @@ public interface Flow<T> extends Iterabl
     <X> Flow<X> mapcat(Mapper<T, Flow<X>> mapper);
 
     /**
-     * Filters values, keeping only values where the predicate is true, returning a new Flow with just
-     * the retained values.
+     * Converts the Flow into an array of values (due to type erasure, you have to remind the Flow
+     * about the type).
      */
-    Flow<T> filter(Predicate<? super T> predicate);
-
-    /** Removes values where the predicate returns true, returning a new Flow with just the remaining values. */
-    Flow<T> remove(Predicate<? super T> predicate);
-
-    /**
-     * Applies a Reducer to the values of the Flow. The Reducer is passed the initial value
-     * and the first value from the Flow. The result is captured as the accumulator and passed
-     * to the Reducer with the next value from the Flow, and so on. The final accumulator
-     * value is returned. If the flow is empty, the initial value is returned.
-     * <p>
-     * Reducing is a non-lazy operation; it will fully realize the values of the Flow.
-     */
-    <A> A reduce(Reducer<A, T> reducer, A initial);
-
-    /**
-     * Applies the worker to each value in the Flow, then returns the flow for further behaviors.
-     * <p>
-     * Each is a non-lazy operation; it will fully realize the values of the Flow.
-     */
-    Flow<T> each(Worker<? super T> worker);
-
-    /**
-     * Converts the Flow into an unmodifiable list of values. This is a non-lazy operation that will fully realize
-     * the values of the Flow.
-     */
-    List<T> toList();
+    T[] toArray(Class<T> type);
 
     /**
-     * Converts the Flow into an unmodifiable set of values. This is a non-lazy operation that will fully realize
-     * the values of the Flow.
+     * Returns a new Flow with the other Flow's elements appended to this Flow's. This is a lazy
+     * operation.
      */
-    Set<T> toSet();
+    Flow<T> concat(Flow<? extends T> other);
 
     /**
-     * Converts the Flow into an array of values (due to type erasure, you have to remind the Flow about the
-     * type).
+     * Appends any number of type compatible values to the end of this Flow. This is a lazy
+     * operation.
      */
-    T[] toArray(Class<T> type);
-
-    /** Returns a new flow with the same elements but in reverse order. */
-    Flow<T> reverse();
-
-    /** Returns true if the Flow contains no values. This <em>may</em> realize the first value in the Flow. */
-    boolean isEmpty();
-
-    /** Returns a new Flow with the other Flow's elements appended to this Flow's. This is a lazy operation. */
-    Flow<T> concat(Flow<? extends T> other);
-
-    /** Returns a new Flow with the values in the list appended to this Flow. This is a lazy operation. */
-    Flow<T> concat(List<? extends T> list);
-
-    /** Appends any number of type compatible values to the end of this Flow. This is a lazy operation. */
     <V extends T> Flow<T> append(V... values);
 
     /**
-     * Sorts this Flow, forming a new Flow. This is a non-lazy operation; it will fully realize the values of the Flow.
+     * Sorts this Flow, forming a new Flow. This is a non-lazy operation; it will fully realize the
+     * values of the Flow.
      * 
      * @throws ClassCastException
      *             if type <T> does not extend {@link Comparable}
@@ -124,44 +85,17 @@ public interface Flow<T> extends Iterabl
     Flow<T> sort();
 
     /**
-     * Sorts this Flow using the comparator, forming a new Flow. This is a non-lazy operation; it will fully realize the
-     * values of the Flow.
-     */
-    Flow<T> sort(Comparator<? super T> comparator);
-
-    /**
-     * Returns the first value in the Flow. Returns null for empty flows, but remember that null is a valid
-     * value within a flow, so use {@link #isEmpty() to determine if a flow is actually empty. The first value can be
-     * realized without realizing the full Flow.
-     */
-    T first();
-
-    /**
-     * Returns a new Flow containing all but the first value in this Flow. If this Flow has only a single item,
-     * or is empty, this will return an empty Flow.
-     */
-    Flow<T> rest();
-
-    /**
-     * Returns the number of values in the Flow. This forces the realization of much of the Flow (i.e., because
-     * each value will need to be passed through any {@link Predicate}s).
-     */
-    int count();
-
-    /**
-     * Returns a new Flow containing just the first values from
-     * this Flow.
-     * 
-     * @param length
-     *            maximum number of values in the Flow
-     */
-    Flow<T> take(int length);
-
-    /**
-     * Returns a new Flow with the first values omitted.
+     * Zips this Flow together with another flow to form a Flow of {@link Tuple}s. The resulting
+     * flow is the length of the shorter of the two input flows.
+     * <p>
+     * The elements of this flow become the {@linkplain Tuple#first} value in each Tuple, the
+     * elements of the other flow become the {@linkplain Tuple#second} value in each Tuple.
      * 
-     * @param length
-     *            number of values to drop
+     * @param <B>
+     * @param otherFlow
+     *            contains values to match with values in this flow
+     * @return flow of tuples combining values from this flow with values form the other flow
+     * @since 5.3.0
      */
-    Flow<T> drop(int length);
+    <X> ZippedFlow<T, X> zipWith(Flow<X> otherFlow);
 }

Added: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/FlowOperations.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/FlowOperations.java?rev=1053242&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/FlowOperations.java (added)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/FlowOperations.java Tue Dec 28 02:12:27 2010
@@ -0,0 +1,121 @@
+package org.apache.tapestry5.func;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @param <T>
+ *            the type of data in the flow
+ * @param <FT>
+ *            the type of flow (either Flow<T> or ZippedFlow<Tuple<T, ?>)
+ * @since 5.3.0
+ */
+public interface FlowOperations<T, FT> extends Iterable<T>
+{
+    /**
+     * Filters values, keeping only values where the predicate is true, returning a new Flow with
+     * just the retained values.
+     */
+    FT filter(Predicate<? super T> predicate);
+
+    /**
+     * Removes values where the predicate returns true, returning a new Flow with just the remaining
+     * values.
+     */
+    FT remove(Predicate<? super T> predicate);
+
+    /**
+     * Applies the worker to each element in the Flow, then returns the flow for further behaviors.
+     * <p>
+     * Each is a non-lazy operation; it will fully realize the values of the Flow.
+     */
+    FT each(Worker<? super T> worker);
+
+    /**
+     * Converts the Flow into an unmodifiable list of values. This is a non-lazy operation that will
+     * fully realize the values of the Flow.
+     */
+    List<T> toList();
+
+    /**
+     * Converts the Flow into an unmodifiable set of values. This is a non-lazy operation that will
+     * fully realize the values of the Flow.
+     */
+    Set<T> toSet();
+
+    /** Returns a new flow with the same elements but in reverse order. */
+    FT reverse();
+
+    /**
+     * Returns true if the Flow contains no values. This <em>may</em> realize the first value in the
+     * Flow.
+     */
+    boolean isEmpty();
+
+    /**
+     * Returns the first element in the Flow. Returns null for empty flows, but remember that null
+     * is a valid element within a flow, so use {@link #isEmpty() to determine if a flow is actually
+     * empty. The first element can be realized without realizing the full Flow.
+     */
+    T first();
+
+    /**
+     * Returns a new Flow containing all but the first element in this flow. If this flow has only a
+     * single element, or is empty, this will return an empty Flow.
+     */
+    FT rest();
+
+    /**
+     * Returns the number of values in this flow. This forces the realization of much of the flow
+     * (i.e., because each value will need to be passed through any {@link Predicate}s).
+     */
+    int count();
+
+    /**
+     * Sorts this flow using the comparator, forming a new flow. This is a non-lazy operation; it
+     * will fully realize the values of the Flow.
+     */
+    FT sort(Comparator<? super T> comparator);
+
+    /**
+     * Returns a new flow containing just the first values from
+     * this Flow.
+     * 
+     * @param length
+     *            maximum number of values in the Flow
+     */
+    FT take(int length);
+
+    /**
+     * Returns a new flow with the first values omitted.
+     * 
+     * @param length
+     *            number of values to drop
+     */
+    FT drop(int length);
+
+    /**
+     * Returns a new Flow with the values in the list appended to this Flow. This is a lazy
+     * operation.
+     */
+    FT concat(List<? extends T> list);
+
+    /**
+     * Applies a Reducer to the values of the Flow. The Reducer is passed the initial value
+     * and the first element from the Flow. The result is captured as the accumulator and passed
+     * to the Reducer with the next value from the Flow, and so on. The final accumulator
+     * value is returned. If the flow is empty, the initial value is returned.
+     * <p>
+     * Reducing is a non-lazy operation; it will fully realize the values of the Flow.
+     */
+    <A> A reduce(Reducer<A, T> reducer, A initial);
+
+    /**
+     * Removes null elements from the flow (null tuples from a ZippedFlow), leaving just the
+     * non-null elements. This is a lazy operation.
+     * 
+     * @since 5.3.0
+     */
+    FT removeNulls();
+}

Copied: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZip.java (from r1052930, tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZip.java?p2=tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZip.java&p1=tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java&r1=1052930&r2=1053242&rev=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZip.java Tue Dec 28 02:12:27 2010
@@ -14,15 +14,25 @@
 
 package org.apache.tapestry5.func;
 
-/**
- * A reducer takes an accumulator value and a single value from a collection and computes a new
- * accumulator value.
- * <A> type of accumulator
- * <T> type of collection value
- * 
- * @since 5.2.0
- */
-public interface Reducer<A, T>
+class LazyZip<A, B> implements LazyFunction<Tuple<A, B>>
 {
-    A reduce(A accumulator, T value);
+    private final Flow<A> aFlow;
+
+    private final Flow<B> bFlow;
+
+    public LazyZip(Flow<A> aFlow, Flow<B> bFlow)
+    {
+        this.aFlow = aFlow;
+        this.bFlow = bFlow;
+    }
+
+    public LazyContinuation<Tuple<A, B>> next()
+    {
+        if (aFlow.isEmpty() || bFlow.isEmpty())
+            return null;
+
+        LazyZipValue<A, B> nextValue = new LazyZipValue<A, B>(aFlow, bFlow);
+
+        return new LazyContinuation<Tuple<A, B>>(nextValue, new LazyZip<A, B>(aFlow.rest(), bFlow.rest()));
+    }
 }

Copied: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZipValue.java (from r1052930, tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZipValue.java?p2=tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZipValue.java&p1=tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java&r1=1052930&r2=1053242&rev=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/LazyZipValue.java Tue Dec 28 02:12:27 2010
@@ -14,15 +14,20 @@
 
 package org.apache.tapestry5.func;
 
-/**
- * A reducer takes an accumulator value and a single value from a collection and computes a new
- * accumulator value.
- * <A> type of accumulator
- * <T> type of collection value
- * 
- * @since 5.2.0
- */
-public interface Reducer<A, T>
+public class LazyZipValue<A, B> implements LazyValue<Tuple<A, B>>
 {
-    A reduce(A accumulator, T value);
+    private final Flow<A> aFlow;
+
+    private final Flow<B> bFlow;
+
+    public LazyZipValue(Flow<A> aFlow, Flow<B> bFlow)
+    {
+        this.aFlow = aFlow;
+        this.bFlow = bFlow;
+    }
+
+    public Tuple<A, B> get()
+    {
+        return new Tuple<A, B>(aFlow.first(), bFlow.first());
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Reducer.java Tue Dec 28 02:12:27 2010
@@ -24,5 +24,9 @@ package org.apache.tapestry5.func;
  */
 public interface Reducer<A, T>
 {
+    /**
+     * Run a computation using the current value of the accumulator and a value (from a Flow),
+     * and return the new accumulator.
+     */
     A reduce(A accumulator, T value);
 }

Added: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java?rev=1053242&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java (added)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java Tue Dec 28 02:12:27 2010
@@ -0,0 +1,104 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.func;
+
+/**
+ * A Tuple holds two values of two different types.
+ * 
+ * @param <A>
+ * @param <B>
+ * @since 5.3.0
+ */
+public class Tuple<A, B>
+{
+    public final A first;
+
+    public final B second;
+
+    public Tuple(A first, B second)
+    {
+        this.first = first;
+        this.second = second;
+    }
+
+    public static <X, Y> Tuple<X, Y> create(X first, Y second)
+    {
+        return new Tuple<X, Y>(first, second);
+    }
+
+    /**
+     * Returns the values of the tuple, separated by commas, enclosed in parenthesis. Example:
+     * <code>("Ace", "Spades")</code>.
+     */
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder("(");
+
+        builder.append(String.valueOf(first));
+        builder.append(", ");
+        builder.append(String.valueOf(second));
+
+        extendDescription(builder);
+
+        return builder.append(")").toString();
+    }
+
+    /**
+     * Overriden in subclasses to write additional values into the
+     * description.
+     * 
+     * @param builder
+     */
+    protected void extendDescription(StringBuilder builder)
+    {
+    }
+
+    /** Utility for comparing two values, either of which may be null. */
+    static boolean isEqual(Object left, Object right)
+    {
+        return left == right || (left != null && left.equals(right));
+    }
+
+    /**
+     * Compares this Tuple to another object. Equality is defined by: other object is not null,
+     * is same class as this Tuple, and all values are themselves equal.
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+            return true;
+
+        if (obj == null || !(obj.getClass() == getClass()))
+            return false;
+
+        return isMatch(obj);
+    }
+
+    /**
+     * The heart of {@link #equals(Object)}; the other object is the same class as this object.
+     * 
+     * @param other
+     *            other tuple to compare
+     * @return true if all values stored in tuple match
+     */
+    protected boolean isMatch(Object other)
+    {
+        Tuple<?, ?> tuple = (Tuple<?, ?>) other;
+
+        return isEqual(first, tuple.first) && isEqual(second, tuple.second);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlow.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlow.java?rev=1053242&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlow.java (added)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlow.java Tue Dec 28 02:12:27 2010
@@ -0,0 +1,51 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.func;
+
+/**
+ * The result of the {@link Flow#zipWith(Flow)} method, a Flow of combined {@link Tuple} values
+ * (that can be deconstructed, eventually, using {@link #unzip()}). Each operation of {@link Flow}
+ * has a corresponding implementation here, on the Tuple values.
+ * 
+ * @param <A>
+ * @param <B>
+ * @since 5.3.0
+ */
+public interface ZippedFlow<A, B> extends FlowOperations<Tuple<A, B>, ZippedFlow<A, B>>
+{
+    /**
+     * Mapping for zipped flows; a mapper is used to map tuples of this zipped flow into new tuples
+     * with a new type, forming the resulting zipped flow.
+     */
+    <X, Y> ZippedFlow<X, Y> mapTuples(Mapper<Tuple<A, B>, Tuple<X, Y>> mapper);
+
+    /**
+     * A ZippedFlow is a Flow of Tuples; this inverts that, splitting each Tuple into
+     * a Flow of values, then assembling the result as a Tuple of two values.
+     * 
+     * @return two flows of unzipped Tuples
+     */
+    Tuple<Flow<A>, Flow<B>> unzip();
+
+    /**
+     * Returns a flow of the first values of the tuples of the zipped flow.
+     */
+    Flow<A> firsts();
+
+    /**
+     * Returns a flow of the second values of the tuples of the zipped flow.
+     */
+    Flow<B> seconds();
+}

Added: tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlowImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlowImpl.java?rev=1053242&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlowImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-func/src/main/java/org/apache/tapestry5/func/ZippedFlowImpl.java Tue Dec 28 02:12:27 2010
@@ -0,0 +1,171 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.func;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The single implementation of {@link ZippedFlow}, that operates by wrapping around an ordinary
+ * {@link Flow} of the {@link Tuples} of the zipped flow. In the future, we may create an
+ * EmptyZippedFlow implementation as well.
+ * 
+ * @param <A>
+ *            type of first value in tuple
+ * @param <B>
+ *            type of second value in tuple
+ */
+class ZippedFlowImpl<A, B> implements ZippedFlow<A, B>
+{
+    private final Flow<Tuple<A, B>> tupleFlow;
+
+    private ZippedFlowImpl(Flow<Tuple<A, B>> tupleFlow)
+    {
+        this.tupleFlow = tupleFlow;
+    }
+
+    public Tuple<Flow<A>, Flow<B>> unzip()
+    {
+        return Tuple.create(firsts(), seconds());
+    }
+
+    static <X, Y> ZippedFlow<X, Y> create(Flow<Tuple<X, Y>> wrappedTupleFlow)
+    {
+        assert wrappedTupleFlow != null;
+
+        return new ZippedFlowImpl<X, Y>(wrappedTupleFlow);
+    }
+
+    public ZippedFlow<A, B> filter(Predicate<? super Tuple<A, B>> predicate)
+    {
+        return create(tupleFlow.filter(predicate));
+    }
+
+    public ZippedFlow<A, B> remove(Predicate<? super Tuple<A, B>> predicate)
+    {
+        return create(tupleFlow.remove(predicate));
+    }
+
+    public ZippedFlow<A, B> each(Worker<? super Tuple<A, B>> worker)
+    {
+        tupleFlow.each(worker);
+
+        return this;
+    }
+
+    public List<Tuple<A, B>> toList()
+    {
+        return tupleFlow.toList();
+    }
+
+    public Set<Tuple<A, B>> toSet()
+    {
+        return tupleFlow.toSet();
+    }
+
+    public ZippedFlow<A, B> reverse()
+    {
+        return create(tupleFlow.reverse());
+    }
+
+    public boolean isEmpty()
+    {
+        return tupleFlow.isEmpty();
+    }
+
+    public Tuple<A, B> first()
+    {
+        return tupleFlow.first();
+    }
+
+    public ZippedFlow<A, B> rest()
+    {
+        return create(tupleFlow.rest());
+    }
+
+    public int count()
+    {
+        return tupleFlow.count();
+    }
+
+    public ZippedFlow<A, B> sort(Comparator<? super Tuple<A, B>> comparator)
+    {
+        return create(tupleFlow.sort(comparator));
+    }
+
+    public ZippedFlow<A, B> take(int length)
+    {
+        return create(tupleFlow.take(length));
+    }
+
+    public ZippedFlow<A, B> drop(int length)
+    {
+        return create(tupleFlow.drop(length));
+    }
+
+    public ZippedFlow<A, B> concat(List<? extends Tuple<A, B>> list)
+    {
+        return create(tupleFlow.concat(list));
+    }
+
+    public Iterator<Tuple<A, B>> iterator()
+    {
+        return tupleFlow.iterator();
+    }
+
+    public <O> O reduce(Reducer<O, Tuple<A, B>> reducer, O initial)
+    {
+        return tupleFlow.reduce(reducer, initial);
+    }
+
+    public <X, Y> ZippedFlow<X, Y> mapTuples(Mapper<Tuple<A, B>, Tuple<X, Y>> mapper)
+    {
+        return create(tupleFlow.map(mapper));
+    }
+
+    public Flow<A> firsts()
+    {
+        return tupleFlow.map(new Mapper<Tuple<A, B>, A>()
+        {
+
+            @Override
+            public A map(Tuple<A, B> value)
+            {
+                return value.first;
+            }
+        });
+    }
+
+    public ZippedFlow<A, B> removeNulls()
+    {
+        return create(tupleFlow.removeNulls());
+    }
+
+    public Flow<B> seconds()
+    {
+        return tupleFlow.map(new Mapper<Tuple<A, B>, B>()
+        {
+
+            @Override
+            public B map(Tuple<A, B> value)
+            {
+                return value.second;
+            }
+        });
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/FuncTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/FuncTest.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/FuncTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/FuncTest.java Tue Dec 28 02:12:27 2010
@@ -546,7 +546,7 @@ public class FuncTest extends BaseFuncTe
         Flow<String> flow = F.flow("Mary", "had", "a", "little", "lamb");
 
         assertEquals(flow.filter(F.isNull()).count(), 0);
-        assertEquals(flow.filter(F.notNull()).count(), 5);
+        assertEquals(flow.removeNulls().count(), 5);
     }
 
     @Test

Modified: tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/MapperTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/MapperTest.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/MapperTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/MapperTest.java Tue Dec 28 02:12:27 2010
@@ -16,7 +16,6 @@ package org.apache.tapestry5.func;
 
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.testng.annotations.Test;

Modified: tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TakeDropTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TakeDropTests.java?rev=1053242&r1=1053241&r2=1053242&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TakeDropTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TakeDropTests.java Tue Dec 28 02:12:27 2010
@@ -45,7 +45,8 @@ public class TakeDropTests extends BaseF
     @Test
     public void take_and_drop()
     {
-        // This can go much, much larger but starts taking a while. Don't hold a reference to the start
+        // This can go much, much larger but starts taking a while. Don't hold a reference to the
+        // start
         // of the series or it can run out of memory.
         int length = 100000;
 

Added: tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java?rev=1053242&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java (added)
+++ tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java Tue Dec 28 02:12:27 2010
@@ -0,0 +1,58 @@
+package org.apache.tapestry5.func;
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import org.apache.tapestry5.func.Tuple;
+import org.testng.annotations.Test;
+
+public class TupleTests extends BaseFuncTest
+{
+    private Tuple<String, Integer> t = Tuple.create("tapestry", 5);
+
+    @Test
+    public void tuple_to_string()
+    {
+        assertEquals(t.toString(), "(tapestry, 5)");
+    }
+
+    @Test
+    public void not_equal_null()
+    {
+        assertFalse(t.equals(null));
+    }
+
+    @Test
+    public void not_equal_anything_else()
+    {
+        assertFalse(t.equals("a string"));
+    }
+
+    @Test
+    public void values_must_be_equal()
+    {
+        assertFalse(t.equals(Tuple.create("tapestry", 4)));
+    }
+
+    @Test
+    void identity_is_equal()
+    {
+        assertTrue(t.equals(t));
+    }
+
+    @Test
+    public void equivalent_tuples_are_equal()
+    {
+        assertTrue(t.equals(Tuple.create("tapestry", 5)));
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/ZippedFlowTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/ZippedFlowTests.java?rev=1053242&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/ZippedFlowTests.java (added)
+++ tapestry/tapestry5/trunk/tapestry-func/src/test/java/org/apache/tapestry5/func/ZippedFlowTests.java Tue Dec 28 02:12:27 2010
@@ -0,0 +1,180 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.func;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.lang.StringUtils;
+import org.testng.annotations.Test;
+
+public class ZippedFlowTests extends BaseFuncTest
+{
+    Flow<Integer> numbers = F.flow(1, 2, 3);
+
+    Flow<String> names = F.flow("fred", "barney", "wilma", "betty");
+
+    ZippedFlow<Integer, String> zipped = numbers.zipWith(names);
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void zip_flows_together()
+    {
+        assertListsEquals(zipped.toList(), Tuple.create(1, "fred"), Tuple.create(2, "barney"), Tuple.create(3, "wilma"));
+    }
+
+    @Test
+    public void unzip_zipped_flow()
+    {
+        Tuple<Flow<Integer>, Flow<String>> unzipped = zipped.drop(1).unzip();
+        Flow<Integer> unzippedNumbers = unzipped.first;
+        Flow<String> unzippedNames = unzipped.second;
+
+        assertListsEquals(unzippedNumbers.toList(), 2, 3);
+        assertListsEquals(unzippedNames.toList(), "barney", "wilma");
+    }
+
+    @Test
+    public void first_tuple_from_zipped_flow()
+    {
+        assertEquals(zipped.drop(2).first(), Tuple.create(3, "wilma"));
+    }
+
+    @Test
+    public void is_zipped_flow_empty()
+    {
+        assertFalse(zipped.isEmpty());
+
+        assertTrue(zipped.filter(F.isNull()).isEmpty());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void removeNulls()
+    {
+        Tuple<Integer, String> pebbles = Tuple.create(9, "pebbles");
+        ZippedFlow<Integer, String> extendedFlow = zipped.concat(Arrays.asList(null, pebbles, null));
+        ZippedFlow<Integer, String> noNulls = extendedFlow.removeNulls();
+
+        assertEquals(extendedFlow.count(), 6);
+        assertEquals(noNulls.count(), 4);
+
+        assertEquals(noNulls.reverse().seconds().first(), "pebbles");
+    }
+
+    @Test
+    public void rest_of_zipped_flow()
+    {
+        assertEquals(zipped.rest().first().second, "barney");
+    }
+
+    @Test
+    public void count_of_zipped_flow()
+    {
+        assertEquals(zipped.count(), 3);
+    }
+
+    @Test
+    public void take_from_zipped_flow()
+    {
+        assertEquals(zipped.take(2).reverse().first().second, "barney");
+    }
+
+    @Test
+    public void zipped_worker()
+    {
+        final AtomicInteger count = new AtomicInteger();
+
+        zipped.each(new Worker<Tuple<Integer, String>>()
+        {
+            @Override
+            public void work(Tuple<Integer, String> value)
+            {
+                count.addAndGet(value.second.length());
+            }
+
+        });
+
+        assertEquals(count.get(), 15);
+    }
+
+    @Test
+    public void reduce_zipped_flow()
+    {
+        int totalLength = zipped.reduce(new Reducer<Integer, Tuple<Integer, String>>()
+        {
+            public Integer reduce(Integer accumulator, Tuple<Integer, String> value)
+            {
+                return accumulator + value.second.length();
+            }
+
+        }, 0);
+
+        assertEquals(totalLength, 15);
+    }
+
+    @Test
+    public void remove_from_zipped_flow()
+    {
+        assertEquals(zipped.remove(F.notNull()).count(), 0);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void concat_a_zipped_flow()
+    {
+        Tuple<Integer, String> bambam = Tuple.create(4, "bam-bam");
+
+        List<Tuple<Integer, String>> asList = Arrays.asList(bambam);
+
+        ZippedFlow<Integer, String> zipped2 = zipped.concat(asList);
+
+        assertEquals(zipped2.count(), 4);
+
+        assertEquals(zipped2.reverse().seconds().first(), "bam-bam");
+    }
+
+    @Test
+    public void firsts()
+    {
+        assertEquals(zipped.reverse().firsts().first(), (Integer) 3);
+    }
+
+    @Test
+    public void seconds()
+    {
+        assertEquals(zipped.seconds().first(), "fred");
+    }
+
+    @Test
+    public void mapTuples()
+    {
+        Tuple<String, String> firstTuple = zipped.mapTuples(new Mapper<Tuple<Integer, String>, Tuple<String, String>>()
+        {
+
+            @Override
+            public Tuple<String, String> map(Tuple<Integer, String> value)
+            {
+                return Tuple.create(StringUtils.reverse(value.second),
+                        String.format("%d-%d", value.first, value.second.length()));
+            }
+
+        }).first();
+
+        assertEquals(firstTuple.first, "derf");
+        assertEquals(firstTuple.second, "1-4");
+    }
+}